diff --git a/apps/edr-passenger-api/src/modules/payments/payments.dto.ts b/apps/edr-passenger-api/src/modules/payments/payments.dto.ts index adeee8b22..815569f24 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.dto.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.dto.ts @@ -1,13 +1,19 @@ -import { IsString, IsEnum, IsOptional } from 'class-validator'; +import { IsString, IsEnum, IsOptional, IsIn } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { PaymentIntentStatus } from '@prisma/client'; export enum PaymentMethodTypeEnum { TELEBIRR = 'TELEBIRR', CBE_BIRR = 'CBE_BIRR', EBIRR = 'EBIRR', CARD = 'CARD', WALLET = 'WALLET' } +export type PaymentPlatformDto = 'web' | 'mobile'; + export class InitiatePaymentDto { @ApiProperty() @IsString() bookingId: string; @ApiProperty({ enum: PaymentMethodTypeEnum }) @IsEnum(PaymentMethodTypeEnum) method: PaymentMethodTypeEnum; @ApiPropertyOptional() @IsOptional() @IsString() paymentMethodId?: string; + @ApiPropertyOptional({ enum: ['web', 'mobile'], default: 'web' }) + @IsOptional() + @IsIn(['web', 'mobile']) + platform?: PaymentPlatformDto; } export class RefundDto { @@ -23,8 +29,11 @@ export class AddPaymentMethodDto { } export class ClientActionDto { - @ApiProperty({ enum: ['REDIRECT'] }) type: 'REDIRECT'; - @ApiProperty() url: string; + @ApiProperty({ enum: ['REDIRECT', 'LAUNCH_APP'] }) type: 'REDIRECT' | 'LAUNCH_APP'; + @ApiPropertyOptional({ description: 'Set when type=REDIRECT (web flow)' }) url?: string; + @ApiPropertyOptional({ description: 'Set when type=LAUNCH_APP (mobile flow)' }) prepayId?: string; + @ApiPropertyOptional({ description: 'Set when type=LAUNCH_APP (mobile flow)' }) receiveCode?: string; + @ApiPropertyOptional({ description: 'Set when type=LAUNCH_APP (mobile flow)' }) shortCode?: string; } export class InitiateResponseDto { 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 9f1ae5341..e6c179049 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.service.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.ts @@ -5,7 +5,7 @@ import { TicketsService } from '../tickets/tickets.service'; import { EventEmitter2 } from '@nestjs/event-emitter'; import { Prisma, PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; import { InitiatePaymentDto, RefundDto, AddPaymentMethodDto, InitiateResponseDto, IntentStatusDto } from './payments.dto'; -import { PaymentProvider, ProviderStatus } from './payments.types'; +import { ClientAction, PaymentProvider, ProviderStatus } from './payments.types'; import { TelebirrProvider } from './providers/telebirr.provider'; import { CbeBirrProvider } from './providers/cbe-birr.provider'; import { EBirrProvider } from './providers/ebirr.provider'; @@ -66,7 +66,7 @@ export class PaymentsService { const provider = this.providers.get(method); if (provider) { - return this.initiateProviderPayment(booking, provider); + return this.initiateProviderPayment(booking, provider, dto.platform); } throw new BadRequestException(`Unsupported payment method: ${method}`); @@ -139,6 +139,7 @@ export class PaymentsService { private async initiateProviderPayment( booking: Prisma.BookingGetPayload<{ include: { seats: true } }>, provider: PaymentProvider, + platform: 'web' | 'mobile' | undefined, ): Promise { const merchantOrderId = createMerchantOrderId(); const result = await provider.initiate({ @@ -146,6 +147,7 @@ export class PaymentsService { bookingRef: booking.bookingRef, amountMinor: booking.totalMinor, currency: booking.currency, + platform, }); const intent = await this.prisma.paymentIntent.upsert({ @@ -184,7 +186,7 @@ export class PaymentsService { ): InitiateResponseDto { const clientAction = intent.clientAction && typeof intent.clientAction === 'object' - ? (intent.clientAction as unknown as { type: 'REDIRECT'; url: string }) + ? (intent.clientAction as unknown as ClientAction) : undefined; return { intentId: intent.id, diff --git a/apps/edr-passenger-api/src/modules/payments/payments.types.ts b/apps/edr-passenger-api/src/modules/payments/payments.types.ts index 336f4b511..e218c3e5c 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.types.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.types.ts @@ -1,15 +1,17 @@ import { PaymentIntentStatus, PaymentMethodType } from '@prisma/client'; -export interface ClientAction { - type: 'REDIRECT'; - url: string; -} +export type PaymentPlatform = 'web' | 'mobile'; + +export type ClientAction = + | { type: 'REDIRECT'; url: string } + | { type: 'LAUNCH_APP'; prepayId: string; receiveCode?: string; shortCode: string }; export interface ProviderInitiationInput { merchantOrderId: string; bookingRef: string; amountMinor: number; currency: string; + platform?: PaymentPlatform; } export interface ProviderInitiationResult { diff --git a/apps/edr-passenger-api/src/modules/payments/providers/telebirr.provider.ts b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.provider.ts index 21980a175..9984b5842 100644 --- a/apps/edr-passenger-api/src/modules/payments/providers/telebirr.provider.ts +++ b/apps/edr-passenger-api/src/modules/payments/providers/telebirr.provider.ts @@ -58,12 +58,21 @@ export class TelebirrProvider implements PaymentProvider { ); } - const checkoutUrl = this.buildCheckoutUrl(prepayId); const expiresAt = this.computeExpiresAt(requestBody.biz_content.timeout_express); + const platform = input.platform ?? 'web'; + const clientAction = + platform === 'mobile' + ? { + type: 'LAUNCH_APP' as const, + prepayId, + receiveCode: response.biz_content?.receiveCode, + shortCode: this.merchantCode, + } + : { type: 'REDIRECT' as const, url: this.buildCheckoutUrl(prepayId) }; return { providerOrderId: prepayId, - clientAction: { type: 'REDIRECT', url: checkoutUrl }, + clientAction, expiresAt, rawInitiation: { request: this.sanitize(requestBody),