diff --git a/packages/payment-providers/src/providers/cac-bank/cac-bank.provider.ts b/packages/payment-providers/src/providers/cac-bank/cac-bank.provider.ts index f72f42ba2..827570492 100644 --- a/packages/payment-providers/src/providers/cac-bank/cac-bank.provider.ts +++ b/packages/payment-providers/src/providers/cac-bank/cac-bank.provider.ts @@ -1,4 +1,4 @@ -import { Injectable, Logger } from "@nestjs/common"; +import { Injectable, Logger, OnModuleInit } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { HttpService } from "@nestjs/axios"; import { @@ -23,7 +23,7 @@ import type { } from "./cac-bank.types"; @Injectable() -export class CacBankProvider implements PaymentProvider { +export class CacBankProvider implements PaymentProvider, OnModuleInit { readonly method = ProviderMethod.CAC_BANK; private readonly logger = new Logger(CacBankProvider.name); private auth: CacBankAuth | null = null; @@ -33,6 +33,35 @@ export class CacBankProvider implements PaymentProvider { private readonly http: HttpService, ) {} + /** Log the effective CAC Bank config once at startup (secrets masked) so misconfig is visible. */ + onModuleInit(): void { + this.logger.log( + `CAC Bank config resolved: ${JSON.stringify(this.effectiveConfig())}`, + ); + } + + /** Snapshot of every resolved CAC env value; secret fields are masked, not printed raw. */ + private effectiveConfig(): Record { + return { + CAC_BASE_URL: this.baseUrl || "(empty)", + CAC_USERNAME: this.username || "(empty)", + CAC_PASSWORD: this.mask(this.password), + CAC_APP_KEY: this.mask(this.appKey), + CAC_API_KEY: this.mask(this.apiKey), + CAC_COMPANY_SERVICES_ID: this.companyServicesId, + CAC_CURRENCY: this.defaultCurrency, + CAC_TOKEN_TTL_MS: this.tokenTtlMs, + CAC_OTP_EXPIRY_MS: this.otpExpiryMs, + }; + } + + /** Mask a secret to `set(len=N,…abcd)` / `(empty)` so presence & length are visible but not the value. */ + private mask(value: string): string { + if (!value) return "(empty)"; + const tail = value.length > 4 ? value.slice(-4) : ""; + return `set(len=${value.length},…${tail})`; + } + async initiate( input: ProviderInitiationInput, ): Promise { @@ -51,12 +80,25 @@ export class CacBankProvider implements PaymentProvider { company_services_id: this.companyServicesId, }; + this.logger.log( + `CAC Bank initiate → ${this.baseUrl}/paymentapi/PaymentInitiateRequest | currency=${requestBody.currency} amount=${requestBody.amount} (amountMinorIn=${input.amountMinor}) mobile=${input.payerAccount} ref=${input.merchantOrderId}`, + ); + this.logger.debug( + `CAC Bank initiate request body: ${JSON.stringify(this.sanitizeKeys(requestBody))}`, + ); + this.logger.debug( + `CAC Bank effective config: ${JSON.stringify(this.effectiveConfig())}`, + ); + const response = await this.postJson( "/paymentapi/PaymentInitiateRequest", requestBody, ); if (response.paymentRequestId == null) { + this.logger.error( + `CAC Bank initiate rejected — sent currency=${requestBody.currency} amount=${requestBody.amount} mobile=${input.payerAccount} | full response: ${JSON.stringify(response)}`, + ); throw new Error( `CAC Bank initiate failed: ${JSON.stringify(response)}`, ); @@ -92,6 +134,10 @@ export class CacBankProvider implements PaymentProvider { otp, }; + this.logger.log( + `CAC Bank confirm → paymentRequestId=${paymentRequestId} otp=${"*".repeat(otp?.length ?? 0)}`, + ); + try { const response = await this.postJson( "/paymentapi/PaymentConfirmationRequest", @@ -99,6 +145,9 @@ export class CacBankProvider implements PaymentProvider { ); if (response.confirmReference == null && !response.reference) { + this.logger.warn( + `CAC Bank confirm rejected — paymentRequestId=${paymentRequestId} | full response: ${JSON.stringify(response)}`, + ); return { status: "FAILED", failureCode: "CONFIRM_REJECTED",