feat: ( payment ) implement cbe payment

This commit is contained in:
Abubeker
2026-07-31 07:50:10 +00:00
parent e4a2c61224
commit 37855b0a83
52 changed files with 2244 additions and 368 deletions

View File

@@ -0,0 +1,45 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* CBE Unified Bill Payment (docs/cbe/CBE_IMPLEMENTATION_PLAN.md §4.1, §5):
* - `bill_reference` — the short numeric Bill_Id CBE presents back to us; unique, null for
* every non-CBE intent.
* - `payer_name` — payer snapshot captured at initiate; fallback for /cbe/query Full_Name.
* - `cbe_bill_reference_seq` — backs the 11-digit sequence body of the bill reference.
*
* DATA SAFETY: purely additive — new nullable columns and a new sequence; no existing rows
* or values are touched.
*/
export class AddCbeBillReference1782300000000 implements MigrationInterface {
name = "AddCbeBillReference1782300000000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "edr_payment"."payment_intent" ADD COLUMN IF NOT EXISTS "bill_reference" varchar(32)`,
);
await queryRunner.query(
`ALTER TABLE "edr_payment"."payment_intent" ADD COLUMN IF NOT EXISTS "payer_name" varchar(128)`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX IF NOT EXISTS "uq_payment_intent_bill_reference" ON "edr_payment"."payment_intent" ("bill_reference") WHERE "bill_reference" IS NOT NULL`,
);
await queryRunner.query(
`CREATE SEQUENCE IF NOT EXISTS "edr_payment"."cbe_bill_reference_seq" START 10000001`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP SEQUENCE IF EXISTS "edr_payment"."cbe_bill_reference_seq"`,
);
await queryRunner.query(
`DROP INDEX IF EXISTS "edr_payment"."uq_payment_intent_bill_reference"`,
);
await queryRunner.query(
`ALTER TABLE "edr_payment"."payment_intent" DROP COLUMN IF EXISTS "payer_name"`,
);
await queryRunner.query(
`ALTER TABLE "edr_payment"."payment_intent" DROP COLUMN IF EXISTS "bill_reference"`,
);
}
}

View File

@@ -0,0 +1,53 @@
import { MigrationInterface, QueryRunner } from "typeorm";
/**
* CBE-protocol audit/idempotency ledger for the Unified Bill Payment integration
* (docs/cbe/CBE_IMPLEMENTATION_PLAN.md §4.2).
*
* - UNIQUE (end_to_end_txn_id, operation): DB-level backstop for the application idempotency
* checks — a concurrent duplicate of the same CBE attempt cannot create two rows.
* - Partial UNIQUE (cbe_txn_ref) on settled PAYMENTs: blocks Cbe_Txn_Ref replay across bills.
*
* DATA SAFETY: new table only; nothing existing is touched.
*/
export class CreateCbeBillOperation1782400000000 implements MigrationInterface {
name = "CreateCbeBillOperation1782400000000";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "edr_payment"."cbe_bill_operation" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
"operation" varchar(16) NOT NULL,
"bill_id" varchar(32) NOT NULL,
"end_to_end_txn_id" varchar(128) NOT NULL,
"cbe_txn_ref" varchar(128),
"destination_api_name" varchar(64),
"intent_id" uuid,
"trade_status" varchar(16) NOT NULL,
"failure_class" varchar(16),
"response_code" varchar(8),
"response_description" text,
"request_payload" jsonb NOT NULL,
"response_payload" jsonb,
"created_at" timestamptz NOT NULL DEFAULT now(),
"updated_at" timestamptz NOT NULL DEFAULT now(),
"deleted_at" timestamptz
)
`);
await queryRunner.query(
`CREATE UNIQUE INDEX IF NOT EXISTS "uq_cbe_bill_operation_e2e" ON "edr_payment"."cbe_bill_operation" ("end_to_end_txn_id", "operation")`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX IF NOT EXISTS "uq_cbe_bill_operation_txn_ref" ON "edr_payment"."cbe_bill_operation" ("cbe_txn_ref") WHERE "operation" = 'PAYMENT' AND "trade_status" = 'SUCCESS'`,
);
await queryRunner.query(
`CREATE INDEX IF NOT EXISTS "idx_cbe_bill_operation_bill" ON "edr_payment"."cbe_bill_operation" ("bill_id", "operation")`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP TABLE IF EXISTS "edr_payment"."cbe_bill_operation"`,
);
}
}