diff --git a/apps/edr-passenger-api/.env.example b/apps/edr-passenger-api/.env.example index 1a9c71990..b2500160d 100644 --- a/apps/edr-passenger-api/.env.example +++ b/apps/edr-passenger-api/.env.example @@ -88,6 +88,19 @@ WAAFI_INSECURE_TLS=false # Payment Configuration PAYMENT_PROVIDERS_ENABLED=TELEBIRR,CBE_BIRR,EBIRR,CARD,WALLET,WAAFI +# Browser return targets after a hosted payment page (UX only — payment is confirmed by the +# webhook/queryStatus, never this redirect). Global fallback used when a method-specific URL +# below is unset. Most providers use a single redirect; Waafi takes separate success/failure. +PAYMENT_RETURN_URL= +PAYMENT_FAILURE_URL= +TELEBIRR_RETURN_URL= +WAAFI_SUCCESS_REDIRECT= +WAAFI_FAIL_REDIRECT= +DMONEY_RETURN_URL= +CBE_RETURN_URL= +EBIRR_RETURN_URL= +CARD_RETURN_URL= + # Session Configuration SESSION_INACTIVITY_MINUTES=30 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 59438c4ad..c017c6332 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.service.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.ts @@ -42,13 +42,6 @@ const NON_TERMINAL_STATUSES: PaymentIntentStatus[] = [ @Injectable() export class PaymentsService { private readonly logger = new Logger(PaymentsService.name); - - /** - * DEMO ONLY: when true, a WALLET "payment" is treated as instantly successful — the wallet - * balance check and debit are skipped and the booking is confirmed + ticket issued as if fully - * paid. Lets the happy-path be demoed while a real provider (e.g. Telebirr) is unavailable. - * Never enable in production. Toggle with WALLET_DEMO_AUTO_SUCCEED in the env. - */ private readonly walletDemoAutoSucceed = true; constructor( @@ -136,9 +129,7 @@ export class PaymentsService { return this.initiateWalletPayment(booking); } - // Provider methods go through the payment microservice (docs/payment-service §7.1): - // it owns the intent, the provider session, and the single webhook per provider. - // Re-initiating is safe — the service returns the existing active intent (idempotent). + const { returnUrl, failureUrl } = this.resolveReturnUrls(method); const snapshot = await this.paymentClient.initiate({ service: PaymentServiceEnum.PASSENGER, referenceType: PaymentReferenceType.BOOKING, @@ -148,10 +139,8 @@ export class PaymentsService { currency: booking.currency, provider: method as unknown as ProviderMethod, platform: dto.platform, - // PASSENGER-owned browser bounce-back after the hosted page (freight passes its own). - // UX only — payment is confirmed by the webhook/mark-paid event, never this redirect. - returnUrl: process.env.PAYMENT_RETURN_URL || undefined, - failureUrl: process.env.PAYMENT_FAILURE_URL || undefined, + returnUrl, + failureUrl, }); let intent = await this.syncIntentProjection(booking.id, snapshot); @@ -168,6 +157,40 @@ export class PaymentsService { } return this.formatIntentResponse(intent); } + private resolveReturnUrls(method: PaymentMethodType): { + returnUrl?: string; + failureUrl?: string; + } { + const perMethod: Partial< + Record + > = { + [PaymentMethodType.TELEBIRR]: { + returnUrl: process.env.TELEBIRR_RETURN_URL, + }, + [PaymentMethodType.WAAFI]: { + returnUrl: process.env.WAAFI_SUCCESS_REDIRECT, + failureUrl: process.env.WAAFI_FAIL_REDIRECT, + }, + [PaymentMethodType.DMONEY]: { + returnUrl: process.env.DMONEY_RETURN_URL, + }, + [PaymentMethodType.CBE_BIRR]: { + returnUrl: process.env.CBE_RETURN_URL, + }, + [PaymentMethodType.EBIRR]: { + returnUrl: process.env.EBIRR_RETURN_URL, + }, + [PaymentMethodType.CARD]: { + returnUrl: process.env.CARD_RETURN_URL, + }, + }; + + const m = perMethod[method] ?? {}; + const returnUrl = m.returnUrl || process.env.PAYMENT_RETURN_URL || undefined; + const failureUrl = + m.failureUrl || process.env.PAYMENT_FAILURE_URL || returnUrl; + return { returnUrl, failureUrl }; + } private async syncIntentProjection( bookingId: string,