import { Body, Controller, Get, HttpStatus, Param, Post, Query, Res, } from "@nestjs/common"; import { ApiTags, ApiOperation, ApiQuery, ApiOkResponse, ApiProduces, } from "@nestjs/swagger"; import { Response } from "express"; import { Public } from "@edr/api-common"; import { PaymentService } from "./payment.service"; import { InitiatePaymentDto, InitiateResponseDto, IntentStatusDto, PaymentMethodTypeEnum, PaymentPlatformDto, RefundDto, } from "./payments.dto"; @ApiTags("Payment") @Controller("payments") export class PaymentController { constructor(private readonly paymentService: PaymentService) { } @Get("all") @ApiOperation({ summary: "Get all payments with filters (staff/admin only)" }) @ApiQuery({ name: "search", required: false }) @ApiQuery({ name: "status", required: false }) @ApiQuery({ name: "method", required: false }) @ApiQuery({ name: "page", required: false }) @ApiQuery({ name: "pageSize", required: false }) async getAll( @Query("search") search?: string, @Query("status") status?: string, @Query("method") method?: string, @Query("page") page?: string, @Query("pageSize") pageSize?: string, ) { return this.paymentService.getAll({ search, status, method, page: page ? parseInt(page) : 1, pageSize: pageSize ? parseInt(pageSize) : 10, }); } @Post("initiate") @ApiOperation({ summary: "Initiate payment for a freight booking", description: `Initiates payment via the central payment microservice.\n\n**Supported methods:**\n- TELEBIRR — Ethiopian mobile money\n- CBE_BIRR — Commercial Bank of Ethiopia\n- EBIRR — Electronic payment gateway\n- WAAFI — Djibouti mobile money\n- CARD — Visa/Mastercard\n- DMONEY — Djibouti D-money\n- CAC_BANK — CAC Int Bank (OTP)`, }) @ApiOkResponse({ type: InitiateResponseDto }) initiatePayment(@Body() dto: InitiatePaymentDto) { return this.paymentService.initiatePayment(dto); } @Get("intents/:bookingId") @ApiOperation({ summary: "Get payment intent status for a booking" }) @ApiOkResponse({ type: IntentStatusDto }) getIntent(@Param("bookingId") bookingId: string) { return this.paymentService.getIntentByBookingId(bookingId); } @Post("refund") @ApiOperation({ summary: "Refund a paid booking (staff/admin only)" }) refund(@Body() dto: RefundDto) { return this.paymentService.refund(dto); } @Get("checkout") @Public() @ApiOperation({ summary: "Browser checkout redirect", description: "Initiates payment and returns an HTML page that auto-redirects to the provider checkout URL. Open directly in a browser tab.", }) @ApiQuery({ name: "bookingId", required: true }) @ApiQuery({ name: "method", enum: PaymentMethodTypeEnum, required: true }) @ApiQuery({ name: "platform", enum: ["web", "mobile"], required: false }) @ApiProduces("text/html") async checkout( @Query("bookingId") bookingId: string, @Query("method") method: PaymentMethodTypeEnum, @Query("platform") platform: PaymentPlatformDto = "web", @Res() res: Response, ) { if (!bookingId) { return res .status(HttpStatus.BAD_REQUEST) .type("html") .send(this.buildErrorHtml("Missing required query parameter: bookingId")); } if (!method || !Object.values(PaymentMethodTypeEnum).includes(method)) { return res .status(HttpStatus.BAD_REQUEST) .type("html") .send(this.buildErrorHtml("Missing or invalid query parameter: method")); } try { const result = await this.paymentService.initiatePayment({ bookingId, method, platform }); const url = result.clientAction?.type === "REDIRECT" ? result.clientAction.url : undefined; if (url) { return res.status(HttpStatus.OK).type("html").send(this.buildRedirectHtml(url)); } return res .status(HttpStatus.OK) .type("html") .send(this.buildStatusHtml(result.status, result.intentId)); } catch (err: unknown) { const message = err instanceof Error ? err.message : "An unexpected error occurred"; return res.status(HttpStatus.OK).type("html").send(this.buildErrorHtml(message)); } } @Get("receipt/:orderId") @Public() @ApiOperation({ summary: "Generate a payment receipt HTML page" }) @ApiProduces("text/html") async receipt(@Param("orderId") orderId: string, @Res() res: Response) { const html = await this.paymentService.genReceiptHtml(orderId); return res.status(HttpStatus.OK).type("html").send(html); } private buildRedirectHtml(url: string): string { const escaped = url.replace(/\"/g, """); return `
Redirecting to payment provider…
${message}