mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 22:25:42 +00:00
feat: ( payment ) specific CBE query descriptions + Payment_Reason
This commit is contained in:
@@ -2,30 +2,82 @@ import { Injectable, Logger } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { HttpService } from "@nestjs/axios";
|
||||
import { firstValueFrom } from "rxjs";
|
||||
import { PaymentService } from "@edr/types";
|
||||
import { PaymentReferenceType, PaymentService } from "@edr/types";
|
||||
import { PaymentIntent } from "../intents/entities/payment-intent.entity";
|
||||
import { CbeBillError } from "./mappers/cbe-error.mapper";
|
||||
|
||||
/**
|
||||
* Why a bill is no longer payable. Shared vocabulary between both domain apps and the local
|
||||
* intent check, so one mapper produces every Response_Description CBE sees.
|
||||
* `NOT_PAYABLE` is the catch-all for domain states with no better name (booking DRAFT/BOARDED,
|
||||
* invoice DRAFT) — it must stay last-resort, never a substitute for a specific reason.
|
||||
*/
|
||||
export type BillNotPayableReason =
|
||||
| "ALREADY_PAID"
|
||||
| "CANCELLED"
|
||||
| "REFUNDED"
|
||||
| "EXPIRED"
|
||||
| "NOT_FOUND"
|
||||
| "NOT_PAYABLE";
|
||||
|
||||
/** Contract of POST /internal/payments/bill-query on the domain apps (plan Phase 4). */
|
||||
export interface BillQueryResult {
|
||||
stillPayable: boolean;
|
||||
payerName?: string | null;
|
||||
currentAmountMinor?: number | null;
|
||||
currency?: string | null;
|
||||
/** When stillPayable=false: "CANCELLED" | "ALREADY_PAID" | "EXPIRED". */
|
||||
reason?: string | null;
|
||||
/** When stillPayable=false — see {@link BillNotPayableReason}. */
|
||||
reason?: BillNotPayableReason | string | null;
|
||||
/**
|
||||
* What the payer is paying for, shown on CBE's confirmation screen next to the amount —
|
||||
* the domain's own human reference (booking ref / invoice number), not our internal ids.
|
||||
*/
|
||||
paymentReason?: string | null;
|
||||
}
|
||||
|
||||
const REASON_DESCRIPTIONS: Record<string, string> = {
|
||||
CANCELLED: "Bill has been cancelled.",
|
||||
ALREADY_PAID: "Bill already paid.",
|
||||
EXPIRED: "Bill has expired.",
|
||||
};
|
||||
/**
|
||||
* Payment_Reason when the domain app sends none (older build, or an order with no human
|
||||
* reference). Generic but never blank: CBE renders this field to the payer, and a bill with
|
||||
* an amount and no stated purpose is what a customer refuses to confirm.
|
||||
*/
|
||||
export function defaultPaymentReason(
|
||||
referenceType: PaymentReferenceType,
|
||||
): string {
|
||||
return referenceType === PaymentReferenceType.BOOKING
|
||||
? "Train ticket booking"
|
||||
: "Freight invoice";
|
||||
}
|
||||
|
||||
export function reasonToDescription(reason?: string | null): string {
|
||||
return (
|
||||
(reason && REASON_DESCRIPTIONS[reason]) || "Bill is not payable."
|
||||
);
|
||||
/**
|
||||
* CBE reads Response_Description back to the payer at the counter or in the USSD prompt, so it
|
||||
* has to name the thing they are actually holding — a passenger booking or a freight invoice —
|
||||
* rather than our internal "bill" abstraction (plan §6.6).
|
||||
*/
|
||||
function subjectOf(referenceType: PaymentReferenceType): string {
|
||||
return referenceType === PaymentReferenceType.BOOKING ? "booking" : "invoice";
|
||||
}
|
||||
|
||||
export function reasonToDescription(
|
||||
reason: string | null | undefined,
|
||||
referenceType: PaymentReferenceType,
|
||||
): string {
|
||||
const subject = subjectOf(referenceType);
|
||||
switch (reason) {
|
||||
case "ALREADY_PAID":
|
||||
return `This ${subject} has already been paid.`;
|
||||
case "CANCELLED":
|
||||
return `This ${subject} has been cancelled.`;
|
||||
case "REFUNDED":
|
||||
return `This ${subject} has been refunded.`;
|
||||
case "EXPIRED":
|
||||
return `This ${subject} has expired and can no longer be paid.`;
|
||||
// A bill reference we issued whose order has since vanished from the domain app. Same
|
||||
// wording as an unknown Bill_Id — from the teller's side it is the same situation.
|
||||
case "NOT_FOUND":
|
||||
return "Bill not found.";
|
||||
default:
|
||||
return `This ${subject} is no longer payable.`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user