diff --git a/apps/edr-passenger-api/src/app.module.ts b/apps/edr-passenger-api/src/app.module.ts index f0edd6cba..757432c3b 100644 --- a/apps/edr-passenger-api/src/app.module.ts +++ b/apps/edr-passenger-api/src/app.module.ts @@ -75,7 +75,7 @@ import { CurrenciesModule } from './modules/currencies/currencies.module'; PaymentsModule, TicketsModule, PassengersModule, - NotificationsModule.register(), + NotificationsModule, LoyaltyModule, WalletModule, PromosModule, diff --git a/apps/edr-passenger-api/src/modules/notifications/dtos/email.dto.ts b/apps/edr-passenger-api/src/modules/notifications/dtos/email.dto.ts index 63bbc4a5a..a435fa4ce 100644 --- a/apps/edr-passenger-api/src/modules/notifications/dtos/email.dto.ts +++ b/apps/edr-passenger-api/src/modules/notifications/dtos/email.dto.ts @@ -1,8 +1,57 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; + export class SendEmail { + @ApiProperty() + @IsEmail() + @IsNotEmpty() to: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + sourceId?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + sourceName?: string; + + @ApiProperty() + @IsNotEmpty() + @IsString() subject: string; - body: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() html?: string; - templateKey?: string; - context?: Record; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + text?: string; + + @ApiPropertyOptional() + @IsOptional() + body?: string; + + @ApiPropertyOptional() + @IsOptional() + context?: Record; + + @ApiPropertyOptional() + @IsOptional() + @IsString() + templateName?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsEmail() + from?: string; + + @ApiPropertyOptional() + @IsOptional() + @IsEmail() + replyTo?: string; } diff --git a/apps/edr-passenger-api/src/modules/notifications/dtos/sms.dto.ts b/apps/edr-passenger-api/src/modules/notifications/dtos/sms.dto.ts index 1897f7794..2841597fc 100644 --- a/apps/edr-passenger-api/src/modules/notifications/dtos/sms.dto.ts +++ b/apps/edr-passenger-api/src/modules/notifications/dtos/sms.dto.ts @@ -1,9 +1,28 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty, IsOptional, IsString, ValidateNested } from 'class-validator'; +import { Type } from 'class-transformer'; + export class SendMessage { + @ApiProperty() + @IsNotEmpty() + @IsString() to: string; + + @ApiProperty() + @IsNotEmpty() + @IsString() message: string; + + @ApiPropertyOptional() + @IsOptional() + @IsString() from?: string; } export class BulkMessagesDto { + @ApiProperty({ type: [SendMessage] }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => SendMessage) messages: SendMessage[]; } diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts index 9fd69830c..0b7798a04 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.controller.ts @@ -7,7 +7,7 @@ import { TestNotificationDto } from './notifications.dto'; import { EmailClientService } from './email-client.service'; import { SmsClientService } from './sms-client.service'; import { SendEmail } from './dtos/email.dto'; -import { SendMessage } from './dtos/sms.dto'; +import { BulkMessagesDto, SendMessage } from './dtos/sms.dto'; @ApiTags('Notifications') @Controller('notifications') @@ -56,6 +56,15 @@ export class NotificationsController { return this.smsClient.sendSms(dto); } + @Post('send/sms/bulk') + @UseGuards(IamGuard) + @IamRoles('ADMIN', 'STAFF') + @ApiOperation({ summary: 'Send bulk SMS messages via the SMS microservice' }) + @ApiBody({ type: BulkMessagesDto }) + sendBulkSms(@Body() dto: BulkMessagesDto) { + return this.smsClient.sendBulkMessages(dto); + } + @Post('test') @UseGuards(IamGuard) @IamRoles('ADMIN', 'STAFF') diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts index 21dfaf159..c72552015 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts @@ -1,6 +1,5 @@ -import { DynamicModule, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; -import { ConfigModule, ConfigService } from '@nestjs/config'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { NotificationsController } from './notifications.controller'; import { NotificationsService } from './notifications.service'; @@ -8,64 +7,39 @@ import { EmailAdapter, SmsAdapter, PushAdapter } from './notification.adapters'; import { EmailClientService } from './email-client.service'; import { SmsClientService } from './sms-client.service'; -const rmqClientsModule = ClientsModule.registerAsync([ - { - name: 'EMAIL_SERVICE', - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService) => ({ - transport: Transport.RMQ, - options: { - urls: [config.get('RABBITMQ_URL') ?? 'amqp://localhost:5672'], - queue: config.get('EMAIL_QUEUE') ?? 'email_queue', - queueOptions: { durable: true }, - noAck: true, +@Module({ + imports: [ + HttpModule.register({ timeout: 10_000 }), + ClientsModule.register([ + { + name: 'EMAIL_SERVICE', + transport: Transport.RMQ, + options: { + urls: [process.env.RABBITMQ_URL as string], + queue: process.env.EMAIL_QUEUE ?? 'email_queue', + queueOptions: { durable: true }, + }, }, - }), - }, - { - name: 'SMS_SERVICE', - imports: [ConfigModule], - inject: [ConfigService], - useFactory: (config: ConfigService) => ({ - transport: Transport.RMQ, - options: { - urls: [config.get('RABBITMQ_URL') ?? 'amqp://localhost:5672'], - queue: config.get('SMS_QUEUE') ?? 'sms_queue', - queueOptions: { durable: true }, - noAck: true, + { + name: 'SMS_SERVICE', + transport: Transport.RMQ, + options: { + urls: [process.env.RABBITMQ_URL as string], + queue: process.env.SMS_QUEUE ?? 'sms_queue', + queueOptions: { durable: true }, + }, }, - }), - }, -]); - -@Module({}) -export class NotificationsModule { - static register(): DynamicModule { - const rmqEnabled = process.env.RABBITMQ_ENABLED !== 'false'; - - return { - module: NotificationsModule, - imports: [ - HttpModule.register({ timeout: 10_000 }), - ...(rmqEnabled ? [rmqClientsModule] : []), - ], - controllers: [NotificationsController], - providers: [ - NotificationsService, - EmailAdapter, - SmsAdapter, - PushAdapter, - ...(rmqEnabled - ? [EmailClientService, SmsClientService] - : [ - { provide: 'EMAIL_SERVICE', useValue: null }, - { provide: 'SMS_SERVICE', useValue: null }, - EmailClientService, - SmsClientService, - ]), - ], - exports: [NotificationsService, EmailClientService, SmsClientService], - }; - } -} + ]), + ], + controllers: [NotificationsController], + providers: [ + NotificationsService, + EmailAdapter, + SmsAdapter, + PushAdapter, + EmailClientService, + SmsClientService, + ], + exports: [NotificationsService, EmailClientService, SmsClientService], +}) +export class NotificationsModule {} diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts index 68d76572c..595633e93 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts @@ -20,7 +20,7 @@ export class NotificationsService { private pushAdapter: PushAdapter, ) { this.channels = new Map([ - ['EMAIL', { send: (to, subject, body) => this.emailClient.sendEmail({ to, subject, body }).then(() => true) }], + ['EMAIL', { send: (to, subject, body) => this.emailClient.sendEmail({ to, subject, text: body }).then(() => true) }], ['SMS', { send: (to, _subject, body) => this.smsClient.sendSms({ to, message: body }).then(() => true) }], ['PUSH', this.pushAdapter as NotificationChannel], ]); @@ -107,7 +107,7 @@ export class NotificationsService { await this.emailClient.sendEmail({ to: passenger.user.email, subject: this.sanitize(dto.title), - body: this.sanitize(dto.body), + text: this.sanitize(dto.body), }); } diff --git a/apps/edr-passenger-api/src/modules/notifications/sms-client.service.ts b/apps/edr-passenger-api/src/modules/notifications/sms-client.service.ts index fa19d1514..7b18212cb 100644 --- a/apps/edr-passenger-api/src/modules/notifications/sms-client.service.ts +++ b/apps/edr-passenger-api/src/modules/notifications/sms-client.service.ts @@ -1,4 +1,9 @@ -import { Inject, Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; +import { + Inject, + Injectable, + Logger, + OnApplicationBootstrap, +} from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { BulkMessagesDto, SendMessage } from './dtos/sms.dto'; @@ -8,7 +13,7 @@ export class SmsClientService implements OnApplicationBootstrap { constructor( @Inject('SMS_SERVICE') - private readonly smsClient: ClientProxy, + private smsClient: ClientProxy, ) {} private readonly enabled = process.env.RABBITMQ_ENABLED !== 'false'; @@ -17,8 +22,12 @@ export class SmsClientService implements OnApplicationBootstrap { if (!this.enabled) return; this.smsClient .connect() - .then(() => this.logger.log('Connected to SMS service')) - .catch((err) => this.logger.error('Error connecting to SMS service', err)); + .then(() => { + this.logger.log('connected to SMS service'); + }) + .catch((err) => { + console.error('Error happened at SMS service', err); + }); } async sendSms(dto: SendMessage) { diff --git a/apps/edr-passenger-api/src/modules/payments/payments.controller.ts b/apps/edr-passenger-api/src/modules/payments/payments.controller.ts index 373e10513..a64c4ae9c 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.controller.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.controller.ts @@ -79,6 +79,28 @@ export class PaymentsController { return this.service.getIntentByBookingId(bookingId); } + @Get("waafi/return") + @ApiOperation({ + summary: + "DEMO ONLY — confirm a Waafi payment from the browser-return params and return JSON for the " + + "UI to display. The frontend success page forwards the Waafi query params here. Gated by " + + "WAAFI_DEMO_TRUST_RETURN (INSECURE; real confirmation is the webhook/HPP_GETTRANINFO).", + }) + @ApiQuery({ name: "referenceId", required: true }) + @ApiQuery({ name: "state", required: true }) + @ApiQuery({ name: "transactionId", required: false }) + waafiReturn( + @Query("referenceId") referenceId: string, + @Query("state") state: string, + @Query("transactionId") transactionId: string, + ) { + return this.service.confirmWaafiReturnDemo({ + referenceId, + state, + transactionId, + }); + } + @Post("refund") @UseGuards(JwtGuard, RolesGuard) @Roles(UserRole.ADMIN, UserRole.STAFF, UserRole.AGENT) 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 c017c6332..c8d0580bf 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.service.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.service.ts @@ -44,6 +44,8 @@ export class PaymentsService { private readonly logger = new Logger(PaymentsService.name); private readonly walletDemoAutoSucceed = true; + private readonly waafiDemoTrustReturn = true; + constructor( private prisma: PrismaService, private seatsService: SeatsService, @@ -192,6 +194,41 @@ export class PaymentsService { return { returnUrl, failureUrl }; } + async confirmWaafiReturnDemo(params: { + referenceId?: string; + state?: string; + transactionId?: string; + }): Promise<{ confirmed: boolean; bookingId?: string; reason?: string }> { + if (!this.waafiDemoTrustReturn) { + return { confirmed: false, reason: "demo-disabled" }; + } + if ((params.state ?? "").toUpperCase() !== "APPROVED") { + return { confirmed: false, reason: `not-approved (${params.state})` }; + } + if (!params.referenceId) { + return { confirmed: false, reason: "missing-referenceId" }; + } + + const intent = await this.prisma.paymentIntent.findFirst({ + where: { merchantOrderId: params.referenceId }, + }); + if (!intent) { + this.logger.warn( + `waafi demo return: no local intent for referenceId ${params.referenceId}`, + ); + return { confirmed: false, reason: "intent-not-found" }; + } + + this.logger.warn( + `WAAFI_DEMO_TRUST_RETURN enabled — confirming booking ${intent.bookingId} from browser return (INSECURE, demo only)`, + ); + await this.finalizePaymentSuccess({ + intentId: intent.id, + providerTxnId: params.transactionId, + }); + return { confirmed: true, bookingId: intent.bookingId }; + } + private async syncIntentProjection( bookingId: string, snapshot: PaymentIntentSnapshot, @@ -453,6 +490,30 @@ export class PaymentsService { }); } + /** + * Guard against an implausible paidAt from a provider event (e.g. a Telebirr epoch parsed as + * ms×1000 → year 58429), which Prisma/Postgres rejects and would otherwise dead-letter the + * whole confirmation. Falls back to "now" for missing/invalid/far-future/ancient values so the + * booking still confirms. + */ + private sanitizePaidAt(value?: Date): Date { + const now = new Date(); + if (!value) return now; + const t = value.getTime(); + const oneDayMs = 86_400_000; + if ( + Number.isNaN(t) || + t > now.getTime() + oneDayMs || + t < Date.UTC(2000, 0, 1) + ) { + this.logger.warn( + `finalizePaymentSuccess: implausible paidAt (epoch=${t}); using current time instead`, + ); + return now; + } + return value; + } + async finalizePaymentSuccess(input: { intentId: string; providerTxnId?: string; @@ -477,7 +538,7 @@ export class PaymentsService { }); if (!booking) throw new NotFoundException("Booking not found"); - const paidAt = input.paidAt ?? new Date(); + const paidAt = this.sanitizePaidAt(input.paidAt); await this.prisma.$transaction(async (tx) => { await tx.paymentIntent.update({ where: { id: intent.id }, diff --git a/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx index 389123510..def68279c 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx @@ -238,52 +238,61 @@ export default function PaymentPage() { {/* Flight-style timeline */} -
-
+
+ {/* Left column: Timeline with dots and line */} +
+ {/* Origin dot */} +
+ {/* Vertical line */} +
+ {/* Destination dot */} +
+
- {/* Origin */} -
-
-
- {outboundSchedule?.departureTime ? format(new Date(outboundSchedule.departureTime), 'HH:mm') : '--:--'} -
-
- {outboundSchedule?.departureTime ? format(new Date(outboundSchedule.departureTime), 'EEE, MMM d') : 'N/A'} -
-
- {outboundSchedule?.origin} -
-
- - {/* Journey Info */} -
-
-
- - - - {outboundSchedule?.duration} + {/* Right column: Content */} +
+ {/* Origin */} +
+
+ {outboundSchedule?.departureTime ? format(new Date(outboundSchedule.departureTime), 'HH:mm') : '--:--'}
-
- - - - Train {outboundSchedule?.trainNumber} +
+ {outboundSchedule?.departureTime ? format(new Date(outboundSchedule.departureTime), 'EEE, MMM d') : 'N/A'} +
+
+ {outboundSchedule?.origin}
-
- {/* Destination */} -
-
-
- {outboundSchedule?.arrivalTime ? format(new Date(outboundSchedule.arrivalTime), 'HH:mm') : '--:--'} + {/* Journey Info */} +
+
+
+ + + + {outboundSchedule?.duration} +
+
+ + + + Train {outboundSchedule?.trainNumber} +
+
-
- {outboundSchedule?.arrivalTime ? format(new Date(outboundSchedule.arrivalTime), 'EEE, MMM d') : 'N/A'} -
-
- {outboundSchedule?.destination} + + {/* Destination */} +
+
+ {outboundSchedule?.arrivalTime ? format(new Date(outboundSchedule.arrivalTime), 'HH:mm') : '--:--'} +
+
+ {outboundSchedule?.arrivalTime ? format(new Date(outboundSchedule.arrivalTime), 'EEE, MMM d') : 'N/A'} +
+
+ {outboundSchedule?.destination} +
@@ -307,52 +316,61 @@ export default function PaymentPage() {
{/* Flight-style timeline */} -
-
+
+ {/* Left column: Timeline with dots and line */} +
+ {/* Origin dot */} +
+ {/* Vertical line */} +
+ {/* Destination dot */} +
+
- {/* Origin */} -
-
-
- {inboundSchedule?.departureTime ? format(new Date(inboundSchedule.departureTime), 'HH:mm') : '--:--'} -
-
- {inboundSchedule?.departureTime ? format(new Date(inboundSchedule.departureTime), 'EEE, MMM d') : 'N/A'} -
-
- {inboundSchedule?.origin} -
-
- - {/* Journey Info */} -
-
-
- - - - {inboundSchedule?.duration} + {/* Right column: Content */} +
+ {/* Origin */} +
+
+ {inboundSchedule?.departureTime ? format(new Date(inboundSchedule.departureTime), 'HH:mm') : '--:--'}
-
- - - - Train {inboundSchedule?.trainNumber} +
+ {inboundSchedule?.departureTime ? format(new Date(inboundSchedule.departureTime), 'EEE, MMM d') : 'N/A'} +
+
+ {inboundSchedule?.origin}
-
- {/* Destination */} -
-
-
- {inboundSchedule?.arrivalTime ? format(new Date(inboundSchedule.arrivalTime), 'HH:mm') : '--:--'} + {/* Journey Info */} +
+
+
+ + + + {inboundSchedule?.duration} +
+
+ + + + Train {inboundSchedule?.trainNumber} +
+
-
- {inboundSchedule?.arrivalTime ? format(new Date(inboundSchedule.arrivalTime), 'EEE, MMM d') : 'N/A'} -
-
- {inboundSchedule?.destination} + + {/* Destination */} +
+
+ {inboundSchedule?.arrivalTime ? format(new Date(inboundSchedule.arrivalTime), 'HH:mm') : '--:--'} +
+
+ {inboundSchedule?.arrivalTime ? format(new Date(inboundSchedule.arrivalTime), 'EEE, MMM d') : 'N/A'} +
+
+ {inboundSchedule?.destination} +
@@ -378,52 +396,61 @@ export default function PaymentPage() {
{/* Flight-style timeline */} -
-
+
+ {/* Left column: Timeline with dots and line */} +
+ {/* Origin dot */} +
+ {/* Vertical line */} +
+ {/* Destination dot */} +
+
- {/* Origin */} -
-
-
- {selectedSchedule?.departureTime ? format(new Date(selectedSchedule.departureTime), 'HH:mm') : '--:--'} -
-
- {selectedSchedule?.departureTime ? format(new Date(selectedSchedule.departureTime), 'EEE, MMM d') : 'N/A'} -
-
- {selectedSchedule?.origin} -
-
- - {/* Journey Info */} -
-
-
- - - - {selectedSchedule?.duration} + {/* Right column: Content */} +
+ {/* Origin */} +
+
+ {selectedSchedule?.departureTime ? format(new Date(selectedSchedule.departureTime), 'HH:mm') : '--:--'}
-
- - - - Train {selectedSchedule?.trainNumber} +
+ {selectedSchedule?.departureTime ? format(new Date(selectedSchedule.departureTime), 'EEE, MMM d') : 'N/A'} +
+
+ {selectedSchedule?.origin}
-
- {/* Destination */} -
-
-
- {selectedSchedule?.arrivalTime ? format(new Date(selectedSchedule.arrivalTime), 'HH:mm') : '--:--'} + {/* Journey Info */} +
+
+
+ + + + {selectedSchedule?.duration} +
+
+ + + + Train {selectedSchedule?.trainNumber} +
+
-
- {selectedSchedule?.arrivalTime ? format(new Date(selectedSchedule.arrivalTime), 'EEE, MMM d') : 'N/A'} -
-
- {selectedSchedule?.destination} + + {/* Destination */} +
+
+ {selectedSchedule?.arrivalTime ? format(new Date(selectedSchedule.arrivalTime), 'HH:mm') : '--:--'} +
+
+ {selectedSchedule?.arrivalTime ? format(new Date(selectedSchedule.arrivalTime), 'EEE, MMM d') : 'N/A'} +
+
+ {selectedSchedule?.destination} +
diff --git a/apps/edr-passenger-web/portal/src/app/globals.css b/apps/edr-passenger-web/portal/src/app/globals.css index 1da5e5f88..9714d54bc 100644 --- a/apps/edr-passenger-web/portal/src/app/globals.css +++ b/apps/edr-passenger-web/portal/src/app/globals.css @@ -30,7 +30,13 @@ } .btn-ghost:hover { - @apply bg-[rgb(20_113_76)] bg-opacity-10 dark:bg-[rgb(20_113_76)] dark:bg-opacity-20; + background-color: rgba(20, 113, 76, 0.1); + } + + @media (prefers-color-scheme: dark) { + .btn-ghost:hover { + background-color: rgba(20, 113, 76, 0.2); + } } .input-field { @@ -146,6 +152,17 @@ } } + @keyframes slide-up { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } + } + .animate-bounce-in { animation: bounce-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); } @@ -167,11 +184,6 @@ animation: slide-in-right 0.5s ease-out; } - @keyframes slide-up { - from { transform: translateY(100%); opacity: 0; } - to { transform: translateY(0); opacity: 1; } - } - .animate-slide-up { animation: slide-up 0.25s cubic-bezier(0.32, 0.72, 0, 1); } diff --git a/apps/edr-passenger-web/portal/tailwind.config.js b/apps/edr-passenger-web/portal/tailwind.config.js index 57df24cf5..f9ae9f63d 100644 --- a/apps/edr-passenger-web/portal/tailwind.config.js +++ b/apps/edr-passenger-web/portal/tailwind.config.js @@ -85,3 +85,4 @@ export default { }, }, plugins: [], +}; diff --git a/packages/payment-providers/src/providers/waafi/waafi.provider.ts b/packages/payment-providers/src/providers/waafi/waafi.provider.ts index 8551f6086..dbd04c642 100644 --- a/packages/payment-providers/src/providers/waafi/waafi.provider.ts +++ b/packages/payment-providers/src/providers/waafi/waafi.provider.ts @@ -86,6 +86,21 @@ export class WaafiProvider implements PaymentProvider { requestBody, ); + // Waafi returns transaction info (params.status) ONLY when responseCode is 2001. For an + // unpaid or not-yet-existing transaction it returns an error envelope (e.g. 5001 / E10206 + // "Failed to get transaction info") with no status. Treat that as still-pending (PROCESSING), + // never terminal — so the intent keeps waiting for the webhook / its expiry rather than being + // wrongly resolved off a "no info" response. + if (response.responseCode !== WAAFI_SUCCESS_CODE) { + this.logger.debug( + `Waafi HPP_GETTRANINFO ${merchantOrderId}: ${response.responseCode}/${response.errorCode} ${response.responseMsg} — treating as pending`, + ); + return { + status: ProviderPaymentStatus.PROCESSING, + rawResponse: response as unknown as Record, + }; + } + const rawState = response.params?.status ?? response.params?.tranStatusDesc; const transactionId = response.params?.transactionId; const mapped = this.mapStatus(rawState);