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 a0f516628..bfc23cad6 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.service.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.ts @@ -279,22 +279,35 @@ export class PaymentsService { return this.formatIntentResponse(intent); } + // A web↔mobile switch needs a different clientAction (Telebirr: REDIRECT for web + // vs LAUNCH_APP for the native app). Detect the open session's platform from its + // clientAction shape so a platform change is NOT blocked below: it must flow + // through to re-initiate, where the payment service retires the stale session and + // opens a fresh one with the correct launch method for the requested platform. + const requestedMobile = (dto.platform ?? "web") === "mobile"; + const storedAction = (snapshot?.clientAction ?? + existingIntent.clientAction) as unknown as ClientAction | null; + const platformChanged = + (storedAction?.type === "LAUNCH_APP") !== requestedMobile; + // Still pending at the provider (REQUIRES_ACTION/PROCESSING) — or the payment // service was unreachable and the local status is non-terminal. Block the switch: // return the existing intent so the payer completes or waits out the open attempt - // rather than opening a second concurrent charge. + // rather than opening a second concurrent charge. A platform switch is exempt — it + // falls through so a session with the correct clientAction is opened for it. if ( - !snapshot || - snapshot.status === ProviderPaymentStatus.REQUIRES_ACTION || - snapshot.status === ProviderPaymentStatus.PROCESSING + !platformChanged && + (!snapshot || + snapshot.status === ProviderPaymentStatus.REQUIRES_ACTION || + snapshot.status === ProviderPaymentStatus.PROCESSING) ) { const intent = snapshot ? await this.syncIntentProjection(booking.id, snapshot) : existingIntent; return this.formatIntentResponse(intent); } - // Otherwise the provider reports FAILED/CANCELLED — fall through and initiate - // the newly selected method below. + // Otherwise the provider reports FAILED/CANCELLED, or the payer switched platform — + // fall through and initiate the newly selected method below. } const { returnUrl, failureUrl } = this.resolveReturnUrls(