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) — the passenger app's * side of the Phase 6 cutover (docs/payment-service §10). Domain validation stays here; * 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 ?? "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.PASSENGER, 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}`; this.logger.log("====================================================================="); this.logger.log(`URL ${url}`); this.logger.log("====================================================================="); 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) { // 4xx/5xx from the payment service: propagate 404 to callers that handle it; // everything else is a gateway-level failure from the client's perspective. 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"); } } }