From 1c508d23ae337f32861b3d47f25d5d33b525e364 Mon Sep 17 00:00:00 2001 From: ghost2023 Date: Sat, 1 Aug 2026 11:31:30 +0300 Subject: [PATCH] fix: hardcoded payment api url --- .../modules/payment/payment-client.service.ts | 231 +++++++++--------- 1 file changed, 122 insertions(+), 109 deletions(-) diff --git a/apps/edr-freight-api/src/modules/payment/payment-client.service.ts b/apps/edr-freight-api/src/modules/payment/payment-client.service.ts index c0fc6fc09..26a0cf5ed 100644 --- a/apps/edr-freight-api/src/modules/payment/payment-client.service.ts +++ b/apps/edr-freight-api/src/modules/payment/payment-client.service.ts @@ -1,17 +1,17 @@ import { - BadGatewayException, - BadRequestException, - Injectable, - Logger, + BadGatewayException, + BadRequestException, + Injectable, + Logger, } from "@nestjs/common"; import { HttpService } from "@nestjs/axios"; import { AxiosError } from "axios"; import { firstValueFrom } from "rxjs"; import { - InitiatePaymentRequest, - PaymentIntentSnapshot, - PaymentReferenceType, - PaymentService, + InitiatePaymentRequest, + PaymentIntentSnapshot, + PaymentReferenceType, + PaymentService, } from "@edr/types"; /** @@ -21,112 +21,125 @@ import { */ @Injectable() export class PaymentClientService { - private readonly logger = new Logger(PaymentClientService.name); - private readonly baseUrl = ( - // process.env.PAYMENT_API_URL ?? - "https://paymentcallback.triaplc.com" - // "http://localhost:3003" - ).replace(/\/$/, ""); - private readonly serviceToken = process.env.SERVICE_AUTH_TOKEN ?? ""; + private readonly logger = new Logger(PaymentClientService.name); + private readonly baseUrl = ( + process.env.PAYMENT_API_URL ?? "https://paymentcallback.triaplc.com" + ) + // "http://localhost:3003" + .replace(/\/$/, ""); + private readonly serviceToken = process.env.SERVICE_AUTH_TOKEN ?? ""; - constructor(private readonly http: HttpService) { } + constructor(private readonly http: HttpService) { } - /** POST /payments/initiate — idempotent per (service, referenceType, referenceId). */ - async initiate(request: InitiatePaymentRequest): Promise { - return this.call("POST", "/payments/initiate", request); + /** POST /payments/initiate — idempotent per (service, referenceType, referenceId). */ + async initiate( + request: InitiatePaymentRequest, + ): Promise { + return this.call("POST", "/payments/initiate", request); + } + + /** + * POST /payments/reconcile — settlement check for a domain order + * (reconcile-before-cancel). Live-queries every non-failed intent at the + * provider and registers any late capture found (flips it to SUCCEEDED and + * emits payment.succeeded). `unverifiable: true` = could not confirm + * "not paid" — the caller must NOT cancel/expire the order. + */ + async reconcileReference( + referenceType: PaymentReferenceType, + referenceId: string, + ): Promise<{ paid: boolean; unverifiable: boolean }> { + return this.call("POST", "/payments/reconcile", { + service: PaymentService.FREIGHT, + referenceType, + referenceId, + }); + } + + /** GET /payments/intents?… — active intent by domain reference; null when none exists. */ + async getIntentByReference( + referenceType: PaymentReferenceType, + referenceId: string, + ): Promise { + const query = new URLSearchParams({ + service: PaymentService.FREIGHT, + referenceType, + referenceId, + }); + try { + return await this.call("GET", `/payments/intents?${query.toString()}`); + } catch (err) { + if (err instanceof AxiosError && err.response?.status === 404) + return null; + throw err; } + } - /** - * POST /payments/reconcile — settlement check for a domain order - * (reconcile-before-cancel). Live-queries every non-failed intent at the - * provider and registers any late capture found (flips it to SUCCEEDED and - * emits payment.succeeded). `unverifiable: true` = could not confirm - * "not paid" — the caller must NOT cancel/expire the order. - */ - async reconcileReference( - referenceType: PaymentReferenceType, - referenceId: string, - ): Promise<{ paid: boolean; unverifiable: boolean }> { - return this.call("POST", "/payments/reconcile", { - service: PaymentService.FREIGHT, - referenceType, - referenceId, - }); + /** + * POST /payments/intents/:id/confirm — submit an OTP for a COLLECT_OTP provider + * (CAC Bank). A wrong/expired OTP comes back as 400 from the payment service; + * surface that as a BadRequest (retryable) rather than a 502, so the payer can + * re-enter the code. + */ + async confirmOtp( + intentId: string, + otp: string, + ): Promise { + try { + return await this.call( + "POST", + `/payments/intents/${intentId}/confirm`, + { otp }, + ); + } catch (err) { + // `call` re-throws raw 404s and masks every other 4xx as BadGateway; an + // unknown intent or a bad OTP is client-fixable, so translate both to 400. + if (err instanceof AxiosError && err.response?.status === 404) { + throw new BadRequestException("PaymentIntent not found"); + } + if (err instanceof BadGatewayException) { + const detail = err.message.replace(/^Payment service error: /, ""); + throw new BadRequestException(detail); + } + throw err; } + } - /** GET /payments/intents?… — active intent by domain reference; null when none exists. */ - async getIntentByReference( - referenceType: PaymentReferenceType, - referenceId: string, - ): Promise { - const query = new URLSearchParams({ - service: PaymentService.FREIGHT, - referenceType, - referenceId, - }); - try { - return await this.call("GET", `/payments/intents?${query.toString()}`); - } catch (err) { - if (err instanceof AxiosError && err.response?.status === 404) return null; - throw err; - } - } - - /** - * POST /payments/intents/:id/confirm — submit an OTP for a COLLECT_OTP provider - * (CAC Bank). A wrong/expired OTP comes back as 400 from the payment service; - * surface that as a BadRequest (retryable) rather than a 502, so the payer can - * re-enter the code. - */ - async confirmOtp(intentId: string, otp: string): Promise { - try { - return await this.call( - "POST", - `/payments/intents/${intentId}/confirm`, - { otp }, - ); - } catch (err) { - // `call` re-throws raw 404s and masks every other 4xx as BadGateway; an - // unknown intent or a bad OTP is client-fixable, so translate both to 400. - if (err instanceof AxiosError && err.response?.status === 404) { - throw new BadRequestException("PaymentIntent not found"); - } - if (err instanceof BadGatewayException) { - const detail = err.message.replace(/^Payment service error: /, ""); - throw new BadRequestException(detail); - } - throw err; - } - } - - private async call(method: "GET" | "POST", path: string, body?: unknown): Promise { - const url = `${this.baseUrl}${path}`; - try { - const response = await firstValueFrom( - this.http.request({ - method, - url, - data: body, - headers: this.serviceToken - ? { "x-service-token": this.serviceToken } - : {}, - }), - ); - return response.data; - } catch (err) { - if (err instanceof AxiosError && err.response) { - if (err.response.status === 404) throw err; - const detail = - (err.response.data as { message?: string | string[] })?.message ?? - err.message; - this.logger.error( - `payment service ${method} ${path} → ${err.response.status}: ${detail}`, - ); - throw new BadGatewayException(`Payment service error: ${detail}`); - } - const message = err instanceof Error && err.message ? err.message : String(err); - this.logger.error(`payment service unreachable (${method} ${path}): ${message}`); - throw new BadGatewayException("Payment service unreachable"); - } + private async call( + method: "GET" | "POST", + path: string, + body?: unknown, + ): Promise { + const url = `${this.baseUrl}${path}`; + try { + const response = await firstValueFrom( + this.http.request({ + method, + url, + data: body, + headers: this.serviceToken + ? { "x-service-token": this.serviceToken } + : {}, + }), + ); + return response.data; + } catch (err) { + if (err instanceof AxiosError && err.response) { + if (err.response.status === 404) throw err; + const detail = + (err.response.data as { message?: string | string[] })?.message ?? + err.message; + this.logger.error( + `payment service ${method} ${path} → ${err.response.status}: ${detail}`, + ); + throw new BadGatewayException(`Payment service error: ${detail}`); + } + const message = + err instanceof Error && err.message ? err.message : String(err); + this.logger.error( + `payment service unreachable (${method} ${path}): ${message}`, + ); + throw new BadGatewayException("Payment service unreachable"); } + } }