import { Injectable, Logger } from '@nestjs/common'; import { WaafiProvider, WaafiWebhookPayload, ProviderPaymentStatus, } from '@edr/payment-providers'; import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; import { PrismaService } from '../../../common/prisma.service'; import { PaymentsService } from '../payments.service'; @Injectable() export class WaafiWebhookService { private readonly logger = new Logger(WaafiWebhookService.name); constructor( private prisma: PrismaService, private paymentsService: PaymentsService, private waafiProvider: WaafiProvider, ) {} async handleWebhook(payload: WaafiWebhookPayload): Promise<{ received: boolean }> { this.logger.log( `Waafi webhook received: event=${payload.eventType} ref=${payload.params?.referenceId}`, ); const signatureValid = this.waafiProvider.verifyWebhookSignature( payload as unknown as Record, ); const merchantOrderId = payload.params?.referenceId; const transactionId = payload.params?.transactionId; const state = payload.params?.state; await this.prisma.paymentWebhookEvent.create({ data: { provider: PaymentMethodType.WAAFI, externalEventId: payload.requestId, merchantOrderId, providerTxnId: transactionId, signatureValid, status: state || 'UNKNOWN', payload: payload as any, }, }); if (!signatureValid) { this.logger.warn(`Waafi webhook signature invalid for ref=${merchantOrderId}`); return { received: true }; } if (!merchantOrderId) { this.logger.error('Waafi webhook missing referenceId'); return { received: true }; } const intent = await this.prisma.paymentIntent.findFirst({ where: { merchantOrderId }, }); if (!intent) { this.logger.warn(`No PaymentIntent found for merchantOrderId=${merchantOrderId}`); return { received: true }; } const mappedStatus = this.waafiProvider.mapState(state); if (mappedStatus === ProviderPaymentStatus.SUCCEEDED) { await this.paymentsService.finalizePaymentSuccess({ intentId: intent.id, providerTxnId: transactionId, }); this.logger.log(`Waafi payment succeeded: intent=${intent.id} txn=${transactionId}`); } else if (mappedStatus === ProviderPaymentStatus.FAILED) { await this.paymentsService.markPaymentFailed({ intentId: intent.id, failureCode: state, failureMessage: payload.params?.description, }); this.logger.log(`Waafi payment failed: intent=${intent.id} state=${state}`); } else { await this.prisma.paymentIntent.update({ where: { id: intent.id }, data: { status: mappedStatus as unknown as PaymentIntentStatus, providerTxnId: transactionId, }, }); this.logger.log(`Waafi payment status updated: intent=${intent.id} status=${mappedStatus}`); } return { received: true }; } }