diff --git a/apps/edr-passenger-api/src/modules/payments/payments.service.ts b/apps/edr-passenger-api/src/modules/payments/payments.service.ts index 9e43139c4..4f8003f32 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.service.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.ts @@ -338,7 +338,14 @@ export class PaymentsService { }; return this.prisma.paymentIntent.upsert({ where: { bookingId }, - update: data, + // amountMinor/currency are refreshed on update too: a cross-currency method switch + // (e.g. Waafi/USD → Telebirr/ETB) re-initiates over the same row, and the projection + // must reflect the currency the new provider actually charges — not the first one's. + update: { + ...data, + amountMinor: snapshot.amountMinor, + currency: snapshot.currency, + }, create: { bookingId, amountMinor: snapshot.amountMinor, diff --git a/apps/edr-payment-api/src/modules/intents/intents.service.ts b/apps/edr-payment-api/src/modules/intents/intents.service.ts index aebb65ebb..ccd10f508 100644 --- a/apps/edr-payment-api/src/modules/intents/intents.service.ts +++ b/apps/edr-payment-api/src/modules/intents/intents.service.ts @@ -77,8 +77,27 @@ export class IntentsService { request.referenceId, ); if (existing) { - const reusable = await this.reuseOrRetire(existing); - if (reusable) return this.toSnapshot(reusable); + // Payer switched method (e.g. Waafi → Telebirr) on an uncharged session: retire the + // open intent and fall through to open a fresh one for the new provider. Only safe + // while REQUIRES_ACTION — PROCESSING/SUCCEEDED intents may have money in flight, so + // they keep the reuse path (the switch is silently refused until they resolve). + const switchingProvider = + existing.provider !== request.provider && + existing.status === ProviderPaymentStatus.REQUIRES_ACTION; + if (switchingProvider) { + await this.intentsRepository.update(existing.id, { + status: ProviderPaymentStatus.CANCELLED, + failureCode: "METHOD_CHANGED", + failureMessage: `Payer switched from ${existing.provider} to ${request.provider}`, + }); + this.logger.log( + `intent ${existing.id} retired (METHOD_CHANGED ${existing.provider} → ${request.provider}) for ` + + `${request.service}/${request.referenceType}/${request.referenceId}`, + ); + } else { + const reusable = await this.reuseOrRetire(existing); + if (reusable) return this.toSnapshot(reusable); + } } const provider = this.providers.get(request.provider);