From b5f64bc9ad6719700224e73192178ba336a564dc Mon Sep 17 00:00:00 2001 From: Eyosiyas Date: Sat, 6 Jun 2026 11:32:14 +0300 Subject: [PATCH] providers --- .../bookings/booking-payment.service.ts | 60 +++++++++++-------- .../src/modules/bookings/bookings.module.ts | 2 + .../src/modules/bookings/pay.controller.ts | 27 ++++----- .../src/modules/payment/payment.controller.ts | 46 +++++++------- .../src/modules/payment/payment.module.ts | 6 +- .../src/modules/payment/payment.service.ts | 36 +++++++---- .../strategies/payment.telebirr.strategy.ts | 1 - .../webhooks/providers/telebirr.service.ts | 8 ++- 8 files changed, 107 insertions(+), 79 deletions(-) diff --git a/apps/edr-freight-api/src/modules/bookings/booking-payment.service.ts b/apps/edr-freight-api/src/modules/bookings/booking-payment.service.ts index 4697728e7..ecc410320 100644 --- a/apps/edr-freight-api/src/modules/bookings/booking-payment.service.ts +++ b/apps/edr-freight-api/src/modules/bookings/booking-payment.service.ts @@ -3,45 +3,57 @@ import { BookingsRepository } from './bookings.repository'; import { Booking } from './entities/booking.entity'; import { assertBookingStatus } from './booking-status.util'; import { InAppPaymentReceiptDto } from './dto/pay-booking.dto'; +import { PaymentService } from '../payment/payment.service'; -export interface InAppPaymentReceipt extends InAppPaymentReceiptDto {} +export interface InAppPaymentReceipt extends InAppPaymentReceiptDto { } @Injectable() export class BookingPaymentService { - constructor(private readonly bookingsRepository: BookingsRepository) {} + constructor(private readonly bookingsRepository: BookingsRepository, private readonly paymentService: PaymentService) { } async pay( bookingId: string, - ): Promise<{ booking: Booking; receipt: InAppPaymentReceipt }> { + ): Promise<{ redirectUrl: string }> { const booking = await this.requireBooking(bookingId); assertBookingStatus(booking, ['FULLY_EXECUTED']); - const receipt = this.buildMockReceipt(booking); + // const receipt = this.buildMockReceipt(booking); - const updated = await this.bookingsRepository.update(bookingId, { - status: 'PAID', - paymentStatus: 'PAID', - } as never); - - return { booking: updated!, receipt }; - } - - private buildMockReceipt(booking: Booking): InAppPaymentReceipt { - const timestamp = Date.now(); - const isEtb = booking.paymentCurrency === 'ETB'; - const prefix = isEtb ? 'TB' : 'CARD'; - const provider = isEtb ? 'TELEBIRR' : 'CARD'; + // const updated = await this.bookingsRepository.update(bookingId, { + // status: 'PAID', + // paymentStatus: 'PAID', + // } as never); + const resp = await this.paymentService.pay(booking.totalAmount, "ETB", "telebirr", "payment for booking", 'booking', (_) => { + return new Promise((resp, _) => { + resp({ + id: booking.id, + type: "booking" + }) + }); + }) return { - success: true, - provider, - providerRef: `${prefix}-${booking.reference}-${timestamp}`, - amount: booking.totalAmount, - currency: booking.paymentCurrency, - paidAt: new Date().toISOString(), - }; + redirectUrl: resp.clientAction.type == "REDIRECT" ? `http://localhost:3001/api/payments/telebirr/${booking.id}` : "" + } + } + // private buildMockReceipt(booking: Booking): InAppPaymentReceipt { + // const timestamp = Date.now(); + // const isEtb = booking.paymentCurrency === 'ETB'; + // const prefix = isEtb ? 'TB' : 'CARD'; + // const provider = isEtb ? 'TELEBIRR' : 'CARD'; + + // return { + // success: true, + // provider, + // providerRef: `${prefix}-${booking.reference}-${timestamp}`, + // amount: booking.totalAmount, + // currency: booking.paymentCurrency, + // paidAt: new Date().toISOString(), + // }; + // } + private async requireBooking(id: string): Promise { const booking = await this.bookingsRepository.findById(id); if (!booking) throw new NotFoundException(`Booking ${id} not found`); diff --git a/apps/edr-freight-api/src/modules/bookings/bookings.module.ts b/apps/edr-freight-api/src/modules/bookings/bookings.module.ts index 8fdaff7d1..c230a187f 100644 --- a/apps/edr-freight-api/src/modules/bookings/bookings.module.ts +++ b/apps/edr-freight-api/src/modules/bookings/bookings.module.ts @@ -28,6 +28,7 @@ import { ContractPricingScheduleBuilder } from '../../contracts/contract-pricing import { ContractRendererService } from '../../contracts/contract-renderer.service'; import { ContractTemplateResolver } from '../../contracts/contract-template.resolver'; import { ContractViewModelBuilder } from '../../contracts/contract-view-model.builder'; +import { PaymentModule } from '../payment/payment.module'; @Module({ imports: [ @@ -40,6 +41,7 @@ import { ContractViewModelBuilder } from '../../contracts/contract-view-model.bu BookingReviewNote, BookingContractSignature, ]), + PaymentModule, FilesModule, MinioModule, CompaniesModule, diff --git a/apps/edr-freight-api/src/modules/bookings/pay.controller.ts b/apps/edr-freight-api/src/modules/bookings/pay.controller.ts index de66afac5..25f1927d3 100644 --- a/apps/edr-freight-api/src/modules/bookings/pay.controller.ts +++ b/apps/edr-freight-api/src/modules/bookings/pay.controller.ts @@ -2,10 +2,10 @@ import { Controller, Param, ParseUUIDPipe, Post } from '@nestjs/common'; import { ApiBearerAuth, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; import { BookingPaymentService } from './booking-payment.service'; -import { BookingTransitionService } from './booking-transition.service'; -import { InAppPaymentReceiptDto } from './dto/pay-booking.dto'; -import { Booking } from './entities/booking.entity'; -import { BookingNextStep } from './booking-next-step.util'; +// import { BookingTransitionService } from './booking-transition.service'; +// import { InAppPaymentReceiptDto } from './dto/pay-booking.dto'; +// import { Booking } from './entities/booking.entity'; +// import { BookingNextStep } from './booking-next-step.util'; @ApiTags('payments') @ApiBearerAuth() @@ -13,22 +13,15 @@ import { BookingNextStep } from './booking-next-step.util'; export class PayController { constructor( private readonly paymentService: BookingPaymentService, - private readonly transitionService: BookingTransitionService, - ) {} + // private readonly transitionService: BookingTransitionService, + ) { } @Post(':id/payment/pay') @ApiOperation({ summary: 'Complete in-app payment (mock)' }) @ApiOkResponse({ description: 'Enriched booking with ephemeral payment receipt' }) - async pay(@Param('id', ParseUUIDPipe) id: string): Promise< - Booking & { - latestChangeRequestNote?: string | null; - contractSummary?: string | null; - nextStep: BookingNextStep | null; - paymentReceipt: InAppPaymentReceiptDto; - } - > { - const { booking, receipt } = await this.paymentService.pay(id); - const abstract = await this.transitionService.enrichBookingResponse(booking); - return { ...abstract, paymentReceipt: receipt }; + async pay(@Param('id', ParseUUIDPipe) id: string) { + return await this.paymentService.pay(id); + // const abstract = await this.transitionService.enrichBookingResponse(booking); + // return { ...abstract, paymentReceipt: receipt }; } } diff --git a/apps/edr-freight-api/src/modules/payment/payment.controller.ts b/apps/edr-freight-api/src/modules/payment/payment.controller.ts index f4237fc16..296c60520 100644 --- a/apps/edr-freight-api/src/modules/payment/payment.controller.ts +++ b/apps/edr-freight-api/src/modules/payment/payment.controller.ts @@ -1,36 +1,42 @@ import { Controller, Get, NotFoundException, Param, ParseUUIDPipe, Post, Res } from "@nestjs/common"; import { PaymentService } from "./payment.service"; import { Public } from "@edr/api-common"; -import { randomUUID } from "crypto"; +// import { randomUUID } from "crypto"; import { Response } from "express" @Public() @Controller("payments") export class PaymentController { - constructor(private readonly paymentService: PaymentService) { } + constructor(private readonly paymentService: PaymentService,) { } - @Get("/receipts/:orderId/html") - async genReceipt(@Param("orderId") orderId: string, @Res() res: Response) { - const filled = await this.paymentService.genReceiptHtml(orderId); - return res.send(filled) - } + // @Get("/receipts/:orderId/html") + // async genReceipt(@Param("orderId") orderId: string, @Res() res: Response) { + // const filled = await this.paymentService.genReceiptHtml(orderId); + // return res.send(filled) + // } - @Post("/initiate/booking") - async initiatePayment() { + // @Post("/initiate/booking") + // async initiatePayment() { - //Only for testing.. - const description = "booking" - const data = await this.paymentService.pay(20, "ETB", "telebirr", description, "booking", (_) => { - return new Promise((resp, _) => { - resp({ - id: randomUUID(), - type: "booking" - }) - }); - }) + // //Only for testing.. + // const description = "Booking for contact" + // const price = 2000 + // const data = await this.paymentService.pay(price, "ETB", "telebirr", description, "booking", (_) => { + // return new Promise((resp, _) => { + // resp({ + // id: randomUUID(), + // type: "booking" + // }) + // }); + // }) - return data + // return data + // } + + @Post("/bookings/check-payment/:orderId") + checkPayment(@Param("orderId", ParseUUIDPipe) orderId: string) { + return this.paymentService.checkStatusAndUpdate(orderId) } diff --git a/apps/edr-freight-api/src/modules/payment/payment.module.ts b/apps/edr-freight-api/src/modules/payment/payment.module.ts index 1c0605259..ff6a50943 100644 --- a/apps/edr-freight-api/src/modules/payment/payment.module.ts +++ b/apps/edr-freight-api/src/modules/payment/payment.module.ts @@ -7,11 +7,11 @@ import { ConfigModule } from "@nestjs/config"; import { PaymentRepository } from "./payment.repository"; import { WebhookController } from "./webhooks/webhook.controller"; import { TelebirrWebhookService } from "./webhooks/providers/telebirr.service"; -import { BookingsModule } from "../bookings/bookings.module"; @Module({ - imports: [HttpModule, ConfigModule, BookingsModule], + imports: [HttpModule, ConfigModule], providers: [PaymentRepository, PaymentTelebirrStrategy, PaymentService, TelebirrWebhookService], - controllers: [PaymentController, WebhookController] + controllers: [PaymentController, WebhookController], + exports: [PaymentService] }) export class PaymentModule { } \ No newline at end of file diff --git a/apps/edr-freight-api/src/modules/payment/payment.service.ts b/apps/edr-freight-api/src/modules/payment/payment.service.ts index e89fd5157..997a0e18f 100644 --- a/apps/edr-freight-api/src/modules/payment/payment.service.ts +++ b/apps/edr-freight-api/src/modules/payment/payment.service.ts @@ -10,6 +10,8 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import * as path from 'path'; import * as Handlebars from 'handlebars'; +import { ConfigService } from "@nestjs/config"; +import { Booking } from "../bookings/entities/booking.entity"; type PaymentMethod = PaymentEntity["method"] @@ -20,6 +22,7 @@ export class PaymentService { private strategies: Map; constructor( + private readonly configService: ConfigService, private readonly datasource: DataSource, private readonly paymentRepo: PaymentRepository, private readonly telebirrPaymentStategy: PaymentTelebirrStrategy) { @@ -47,7 +50,8 @@ export class PaymentService { let redirectUrl: string; switch (type) { case "booking": - redirectUrl = `http://localhost:3001/api/payments/receipts/${orderId}/html` + const url = this.configService.get("TELEBIRR_SUCCESS_REDIRECT_BASE_URL") + redirectUrl = `${url}/check-status/${orderId}` break; } @@ -132,17 +136,27 @@ export class PaymentService { return html; } - // const templatePath = path.join( - // process.cwd(), - // 'src/modules/payment/templates/receipt.hbs', - // ); + async checkStatusAndUpdate(orderId: string) { + const resp = await this.paymentRepo.findOneBy({ merchantOrderId: orderId }) + if (!resp) { + throw new NotFoundException("order id not found") + } + const result = await this.telebirrPaymentStategy.queryStatus(resp.merchantOrderId) + const bizContent = result.rawResponse.biz_content as { + order_status: string; + }; - - // console.log(templatePath) - - // const source = fs.readFileSync(templatePath, 'utf8'); - // const template = Handlebars.compile(source); - // return this.getReceiptTemplate(); + const ordersStatus = bizContent.order_status + if (ordersStatus == "PAY_SUCCESS") { + await this.datasource.transaction(async (mg) => { + await mg.update(Booking, { id: resp.refId }, { status: "PAID" }) + await mg.update(PaymentEntity, { id: resp.id }, { status: "success" }) + }) + } + return { + status: result.status + } + } } diff --git a/apps/edr-freight-api/src/modules/payment/strategies/payment.telebirr.strategy.ts b/apps/edr-freight-api/src/modules/payment/strategies/payment.telebirr.strategy.ts index a292863d8..de1768bc6 100644 --- a/apps/edr-freight-api/src/modules/payment/strategies/payment.telebirr.strategy.ts +++ b/apps/edr-freight-api/src/modules/payment/strategies/payment.telebirr.strategy.ts @@ -301,5 +301,4 @@ export class PaymentTelebirrStrategy implements PaymentStrategy { return this.config.get('telebirr.publicKey') ?? ''; } - } \ No newline at end of file diff --git a/apps/edr-freight-api/src/modules/payment/webhooks/providers/telebirr.service.ts b/apps/edr-freight-api/src/modules/payment/webhooks/providers/telebirr.service.ts index a57c79982..3e450c2a0 100644 --- a/apps/edr-freight-api/src/modules/payment/webhooks/providers/telebirr.service.ts +++ b/apps/edr-freight-api/src/modules/payment/webhooks/providers/telebirr.service.ts @@ -2,15 +2,16 @@ import { Injectable, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import * as crypto from "crypto" import { TelebirrDto } from '../dto/telebirr.dto'; -import { BookingsRepository } from 'src/modules/bookings/bookings.repository'; import { PaymentRepository } from '../../payment.repository'; +import { DataSource } from 'typeorm'; +import { Booking } from 'src/modules/bookings/entities/booking.entity'; @Injectable() export class TelebirrWebhookService { // private readonly logger = new Logger(TelebirrWebhookService.name); constructor( + private readonly datasource: DataSource, private readonly config: ConfigService, - private readonly bookingRepo: BookingsRepository, private readonly paymentRepo: PaymentRepository, ) { } @@ -57,7 +58,8 @@ export class TelebirrWebhookService { await this.paymentRepo.update({ id: payment.id }, { status: "success", paidAt: new Date() }) switch (payment.type) { case "booking": - await this.bookingRepo.update(payment.refId, { paymentStatus: "PAID", }) + await this.datasource.manager.update(Booking, { id: payment.refId }, { paymentStatus: "PAID", }) + // await this.bookingRepo.update(payment.refId, { paymentStatus: "PAID", }) break; } break;