mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 00:45:41 +00:00
fix: ( payments ) stop reconcile-before-cancel deferring bookings forever
This commit is contained in:
@@ -22,6 +22,13 @@ export interface PaymentDiagnostic {
|
||||
provider: ProviderStatus | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a settlement check came back `unverifiable` (mirrors the payment service's
|
||||
* ReconcileUnverifiableReason). `IN_FLIGHT` means money is actually moving and must be waited out;
|
||||
* `PROVIDER_ERROR` can be a permanently unreachable gateway, which a sweep may eventually give up on.
|
||||
*/
|
||||
export type SettlementUnverifiableReason = "IN_FLIGHT" | "PROVIDER_ERROR";
|
||||
|
||||
/** Settlement check from POST /payments/reconcile (verify-before-cancel). */
|
||||
export interface SettlementResult {
|
||||
/** At least one intent for the order is paid (incl. a late capture just registered). */
|
||||
@@ -31,6 +38,8 @@ export interface SettlementResult {
|
||||
/** Settlement could not be confirmed — a provider query errored, a payment is in flight, OR the
|
||||
* payment service was unreachable. The caller MUST NOT cancel the order. */
|
||||
unverifiable: boolean;
|
||||
/** Set whenever `unverifiable` — which of the two causes applies. */
|
||||
reason?: SettlementUnverifiableReason;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +126,9 @@ export class PaymentClientService {
|
||||
this.logger.warn(
|
||||
`reconcile ${referenceType}/${referenceId} failed: ${message}; treating as unverifiable (will not cancel)`,
|
||||
);
|
||||
return { paid: false, unverifiable: true };
|
||||
// The payment service itself is unreachable — indistinguishable from a dead gateway, and
|
||||
// like one it may never recover, so it is a PROVIDER_ERROR (give-up-able), not IN_FLIGHT.
|
||||
return { paid: false, unverifiable: true, reason: "PROVIDER_ERROR" };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,9 +184,6 @@ export class PaymentClientService {
|
||||
body?: unknown,
|
||||
): Promise<T> {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
this.logger.log("=====================================================================");
|
||||
this.logger.log(`URL ${url}`);
|
||||
this.logger.log("=====================================================================");
|
||||
try {
|
||||
const response = await firstValueFrom(
|
||||
this.http.request<T>({
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
import {
|
||||
PaymentClientService,
|
||||
PaymentDiagnostic,
|
||||
SettlementUnverifiableReason,
|
||||
} from "./payment-client.service";
|
||||
import { CurrencyService } from "../currency/currency.service";
|
||||
import { AuditService } from "../../common/audit.service";
|
||||
@@ -1116,10 +1117,17 @@ export class PaymentsService {
|
||||
* - not paid → verified unpaid; a cancellation caller may proceed.
|
||||
* - unverifiable (provider query errored, in-flight, or payment service unreachable) → a
|
||||
* cancellation caller must NOT cancel this cycle; defer and retry later.
|
||||
*
|
||||
* When unverifiable, `reason` says WHY, and the two are not interchangeable: `IN_FLIGHT` is a
|
||||
* payment actually moving (defer forever — this is the case the guard exists for), while
|
||||
* `PROVIDER_ERROR` may be a gateway that never comes back, which a sweep is allowed to give up
|
||||
* on after a grace window rather than retry once a minute in perpetuity.
|
||||
*/
|
||||
async reconcileAndConfirmIfPaid(
|
||||
bookingId: string,
|
||||
): Promise<{ paid: boolean; verified: boolean }> {
|
||||
async reconcileAndConfirmIfPaid(bookingId: string): Promise<{
|
||||
paid: boolean;
|
||||
verified: boolean;
|
||||
reason?: SettlementUnverifiableReason;
|
||||
}> {
|
||||
const current = await this.prisma.booking.findUnique({
|
||||
where: { id: bookingId },
|
||||
select: { status: true },
|
||||
@@ -1134,10 +1142,11 @@ export class PaymentsService {
|
||||
);
|
||||
|
||||
if (settlement.unverifiable) {
|
||||
const reason = settlement.reason ?? "PROVIDER_ERROR";
|
||||
this.logger.warn(
|
||||
`reconcile-before-cancel: settlement UNVERIFIABLE for booking ${bookingId} — not cancelling`,
|
||||
`reconcile-before-cancel: settlement UNVERIFIABLE (${reason}) for booking ${bookingId} — not cancelling`,
|
||||
);
|
||||
return { paid: false, verified: false };
|
||||
return { paid: false, verified: false, reason };
|
||||
}
|
||||
|
||||
if (settlement.paid) {
|
||||
|
||||
Reference in New Issue
Block a user