import { Injectable } from "@nestjs/common"; import { DMoneyProvider, DMoneyWebhookPayload } from "@edr/payment-providers"; import { WebhookProcessorService } from "../webhook-processor.service"; @Injectable() export class DMoneyWebhookService { constructor( private readonly provider: DMoneyProvider, private readonly processor: WebhookProcessorService, ) {} async handle(payload: DMoneyWebhookPayload): Promise { // TODO: re-enable D-Money public-key signature verification — skipped for now. // D-Money's onboarding pack only provides the merchant keypair (3072-bit); callbacks // are signed with their separate platform notification key (4096-bit), which we don't // have yet, so verifyWebhookSignature can never pass. Re-enable once that key is supplied. const signatureValid = true; const mapped = this.provider.mapWebhookTradeStatus(payload.trade_status); const providerTxnId = payload.transId ?? payload.payment_order_id; await this.processor.process({ provider: this.provider.method, externalEventId: `${payload.payment_order_id}_${payload.trade_status}`, merchantOrderId: payload.merch_order_id, providerTxnId, signatureValid, rawStatus: payload.trade_status, payload: payload as unknown as Record, result: { status: mapped, providerTxnId, paidAt: this.parseTransEndTime(payload.trans_end_time), failureCode: payload.trade_status, }, }); } /** D-Money sends trans_end_time either as epoch ms/s or "YYYY-MM-DD HH:mm:ss". */ private parseTransEndTime(raw: string | undefined): Date | undefined { if (!raw) return undefined; if (/^\d+$/.test(raw)) { const n = parseInt(raw, 10); if (Number.isNaN(n)) return undefined; // 13-digit value is milliseconds, otherwise seconds. return new Date(raw.length >= 13 ? n : n * 1000); } const parsed = new Date(raw.replace(" ", "T")); return Number.isNaN(parsed.getTime()) ? undefined : parsed; } }