mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 17:38:12 +00:00
125 lines
5.5 KiB
TypeScript
125 lines
5.5 KiB
TypeScript
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
|
|
/**
|
|
* Initial edr_payment schema: payment_intent, payment_webhook_event, notification_outbox.
|
|
*
|
|
* Enum-valued columns are varchar on purpose (values mirror the @edr/types enums) so new
|
|
* providers/statuses never need an ALTER TYPE. uuid defaults use gen_random_uuid() (built into
|
|
* Postgres 13+; no extension required).
|
|
*/
|
|
export class InitPaymentSchema1781136000000 implements MigrationInterface {
|
|
name = "InitPaymentSchema1781136000000";
|
|
|
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
// Defensive — bootstrap (ensure-schema) normally creates this before migrations run.
|
|
await queryRunner.query(`CREATE SCHEMA IF NOT EXISTS "edr_payment"`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE "edr_payment"."payment_intent" (
|
|
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
"deleted_at" timestamptz,
|
|
"service" varchar(16) NOT NULL,
|
|
"reference_type" varchar(16) NOT NULL,
|
|
"reference_id" varchar(64) NOT NULL,
|
|
"merchant_order_id" varchar(64) NOT NULL,
|
|
"provider" varchar(16) NOT NULL,
|
|
"provider_order_id" varchar(128),
|
|
"provider_txn_id" varchar(128),
|
|
"amount_minor" integer NOT NULL,
|
|
"confirmed_amount_minor" integer,
|
|
"currency" varchar(8) NOT NULL,
|
|
"status" varchar(24) NOT NULL DEFAULT 'REQUIRES_ACTION',
|
|
"client_action" jsonb,
|
|
"failure_code" varchar(64),
|
|
"failure_message" text,
|
|
"idempotency_key" varchar(128),
|
|
"expires_at" timestamptz,
|
|
"paid_at" timestamptz,
|
|
"raw_initiation" jsonb,
|
|
CONSTRAINT "pk_payment_intent" PRIMARY KEY ("id"),
|
|
CONSTRAINT "uq_payment_intent_merchant_order_id" UNIQUE ("merchant_order_id")
|
|
)
|
|
`);
|
|
// One ACTIVE intent per domain order; terminal-failed attempts remain as audit rows.
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX "uq_payment_intent_active_reference"
|
|
ON "edr_payment"."payment_intent" ("service", "reference_type", "reference_id")
|
|
WHERE status NOT IN ('FAILED','CANCELLED') AND deleted_at IS NULL
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX "idx_payment_intent_provider_txn"
|
|
ON "edr_payment"."payment_intent" ("provider_txn_id")
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX "idx_payment_intent_sweep"
|
|
ON "edr_payment"."payment_intent" ("status", "updated_at")
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX "idx_payment_intent_idempotency"
|
|
ON "edr_payment"."payment_intent" ("service", "idempotency_key")
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE "edr_payment"."payment_webhook_event" (
|
|
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
"deleted_at" timestamptz,
|
|
"provider" varchar(16) NOT NULL,
|
|
"external_event_id" varchar(191) NOT NULL,
|
|
"merchant_order_id" varchar(64),
|
|
"provider_txn_id" varchar(128),
|
|
"signature_valid" boolean NOT NULL DEFAULT false,
|
|
"status" varchar(64),
|
|
"payload" jsonb NOT NULL,
|
|
"received_at" timestamptz NOT NULL DEFAULT now(),
|
|
"processed_at" timestamptz,
|
|
"processing_error" text,
|
|
CONSTRAINT "pk_payment_webhook_event" PRIMARY KEY ("id")
|
|
)
|
|
`);
|
|
// The webhook dedupe key: duplicate provider deliveries hit this and short-circuit.
|
|
await queryRunner.query(`
|
|
CREATE UNIQUE INDEX "uq_payment_webhook_event_external"
|
|
ON "edr_payment"."payment_webhook_event" ("provider", "external_event_id")
|
|
`);
|
|
|
|
await queryRunner.query(`
|
|
CREATE TABLE "edr_payment"."notification_outbox" (
|
|
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
"deleted_at" timestamptz,
|
|
"event_type" varchar(32) NOT NULL,
|
|
"service" varchar(16) NOT NULL,
|
|
"intent_id" uuid NOT NULL,
|
|
"reference_type" varchar(16) NOT NULL,
|
|
"reference_id" varchar(64) NOT NULL,
|
|
"payload" jsonb NOT NULL,
|
|
"status" varchar(16) NOT NULL DEFAULT 'PENDING',
|
|
"attempts" integer NOT NULL DEFAULT 0,
|
|
"next_retry_at" timestamptz,
|
|
"last_error" text,
|
|
"sent_at" timestamptz,
|
|
CONSTRAINT "pk_notification_outbox" PRIMARY KEY ("id")
|
|
)
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX "idx_notification_outbox_relay"
|
|
ON "edr_payment"."notification_outbox" ("status", "next_retry_at")
|
|
`);
|
|
await queryRunner.query(`
|
|
CREATE INDEX "idx_notification_outbox_intent"
|
|
ON "edr_payment"."notification_outbox" ("intent_id")
|
|
`);
|
|
}
|
|
|
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
await queryRunner.query(`DROP TABLE "edr_payment"."notification_outbox"`);
|
|
await queryRunner.query(`DROP TABLE "edr_payment"."payment_webhook_event"`);
|
|
await queryRunner.query(`DROP TABLE "edr_payment"."payment_intent"`);
|
|
}
|
|
}
|