Files
edr-platform/apps/edr-payment-api/src/migrations/1782400000000-CreateCbeBillOperation.ts
2026-07-31 07:50:10 +00:00

54 lines
2.2 KiB
TypeScript

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"`,
);
}
}