import { BadGatewayException, Injectable, Logger } from "@nestjs/common"; import { HttpService } from "@nestjs/axios"; import { AxiosError } from "axios"; import { firstValueFrom } from "rxjs"; import { InitiatePaymentRequest, PaymentIntentSnapshot, PaymentReferenceType, PaymentService, } from "@edr/types"; /** * Thin HTTP client for the payment microservice (apps/edr-payment-api). * Domain validation stays in the freight API; provider calls, intents, * and webhooks live in the payment service. */ @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 ?? ""; 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); } /** 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; } } 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"); } } }