mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
fix:( payment ): make CAC Bank confirm work end-to-end and callback-safe
This commit is contained in:
@@ -211,15 +211,44 @@ export class IntentsService {
|
||||
providerTxnId: confirmResult.providerTxnId,
|
||||
paidAt: new Date(),
|
||||
});
|
||||
} else {
|
||||
await this.applyProviderResult(intent.id, {
|
||||
status: ProviderPaymentStatus.FAILED,
|
||||
failureCode: confirmResult.failureCode,
|
||||
failureMessage: confirmResult.failureMessage,
|
||||
});
|
||||
return this.snapshotOf(intent.id);
|
||||
}
|
||||
|
||||
const updated = await this.intentsRepository.findById(intent.id);
|
||||
// Confirm did not clearly succeed. CAC has no callback and the confirm response can be
|
||||
// lost after the customer was charged, so before failing anything verify the source of
|
||||
// truth by paymentRequestId (GetPaymentByReferenceRequest keys on it).
|
||||
const verified = await this.cacBankProvider
|
||||
.queryStatus(intent.providerOrderId)
|
||||
.catch((err: unknown) => {
|
||||
this.logger.warn(
|
||||
`CAC verify after failed confirm errored for intent ${intent.id}: ${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}`,
|
||||
);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (verified?.status === ProviderPaymentStatus.SUCCEEDED) {
|
||||
await this.applyProviderResult(intent.id, {
|
||||
status: ProviderPaymentStatus.SUCCEEDED,
|
||||
providerTxnId: verified.providerTxnId,
|
||||
paidAt: new Date(),
|
||||
});
|
||||
return this.snapshotOf(intent.id);
|
||||
}
|
||||
|
||||
// Genuinely not paid — almost always a wrong or expired OTP. Leave the intent in
|
||||
// REQUIRES_ACTION so the payer can re-enter the code, and do NOT emit payment.failed:
|
||||
// a mistyped OTP must not cancel the booking. The reconciliation sweep CANCELs the
|
||||
// intent once its OTP window (expiresAt) passes.
|
||||
throw new BadRequestException(
|
||||
confirmResult.failureMessage ??
|
||||
"OTP confirmation failed — please re-enter the code sent to your phone",
|
||||
);
|
||||
}
|
||||
|
||||
private async snapshotOf(intentId: string): Promise<PaymentIntentSnapshot> {
|
||||
const updated = await this.intentsRepository.findById(intentId);
|
||||
if (!updated) throw new NotFoundException("PaymentIntent not found");
|
||||
return this.toSnapshot(updated);
|
||||
}
|
||||
@@ -306,12 +335,12 @@ export class IntentsService {
|
||||
}
|
||||
|
||||
if (intent.provider === ProviderMethod.CAC_BANK) {
|
||||
const reference = (intent.rawInitiation as { reference?: string })
|
||||
?.reference;
|
||||
return this.cacBankProvider.queryStatus(
|
||||
intent.merchantOrderId,
|
||||
reference,
|
||||
);
|
||||
if (!intent.providerOrderId) {
|
||||
throw new Error(
|
||||
`CAC intent ${intent.id} has no providerOrderId to verify`,
|
||||
);
|
||||
}
|
||||
return this.cacBankProvider.queryStatus(intent.providerOrderId);
|
||||
}
|
||||
|
||||
return provider.queryStatus(intent.merchantOrderId);
|
||||
|
||||
@@ -87,8 +87,7 @@ export class ReconciliationService implements OnModuleInit, OnModuleDestroy {
|
||||
const status =
|
||||
intent.provider === ProviderMethod.CAC_BANK
|
||||
? await this.cacBankProvider.queryStatus(
|
||||
intent.merchantOrderId,
|
||||
(intent.rawInitiation as { reference?: string })?.reference,
|
||||
intent.providerOrderId ?? intent.merchantOrderId,
|
||||
)
|
||||
: await provider.queryStatus(intent.merchantOrderId);
|
||||
const result = this.intentsService.fromProviderStatus(status);
|
||||
|
||||
Reference in New Issue
Block a user