import { MigrationInterface, QueryRunner } from 'typeorm'; /** * أمان السحب + سجل التدقيق (docs/17 — I4/I5/I7). * - أعمدة إثبات هوية السحب على `pay_payouts`. * - `audit_log` — من فعل ماذا ومتى ومن أين (append-only). */ export class PayoutSecurityAndAudit1721870000000 implements MigrationInterface { public async up(q: QueryRunner): Promise { const add = (col: string, type: string) => q.query(`ALTER TABLE tripz_pay_payouts ADD COLUMN IF NOT EXISTS ${col} ${type}`); await add('otp_verified_at', 'timestamptz'); await add('biometric_method', 'varchar'); await add('biometric_at', 'timestamptz'); await add('device_id', 'varchar'); await add('request_ip', 'varchar'); // الافتراض الجديد للطلبات الجديدة: لا شيء يتحرك قبل التأكيد. await q.query(`ALTER TABLE tripz_pay_payouts ALTER COLUMN status SET DEFAULT 'pending_otp'`); // السحوبات القائمة أُنشئت قبل وجود OTP وقد حُجز مالها فعلاً — تُعتبر // مُتحقَّقة، وإلا رفض `complete` صرفها إلى الأبد. await q.query(` UPDATE tripz_pay_payouts SET otp_verified_at = COALESCE(otp_verified_at, created_at) WHERE status IN ('requested', 'processing', 'paid') `); await q.query(` CREATE TABLE IF NOT EXISTS tripz_audit_log ( id bigserial PRIMARY KEY, tenant_id uuid NOT NULL, actor_user_id uuid, actor_role varchar, action varchar NOT NULL, subject_type varchar, subject_id varchar, amount numeric(12,3), currency varchar, meta jsonb NOT NULL DEFAULT '{}', ip varchar, created_at TIMESTAMP NOT NULL DEFAULT now() ) `); await q.query(` CREATE INDEX IF NOT EXISTS "IDX_tripz_audit_log_tenant_time" ON tripz_audit_log (tenant_id, created_at) `); await q.query(` CREATE INDEX IF NOT EXISTS "IDX_tripz_audit_log_subject" ON tripz_audit_log (tenant_id, subject_type, subject_id) `); } public async down(q: QueryRunner): Promise { await q.query(`DROP TABLE IF EXISTS tripz_audit_log`); await q.query(`ALTER TABLE tripz_pay_payouts ALTER COLUMN status SET DEFAULT 'requested'`); for (const c of ['request_ip', 'device_id', 'biometric_at', 'biometric_method', 'otp_verified_at']) { await q.query(`ALTER TABLE tripz_pay_payouts DROP COLUMN IF EXISTS ${c}`); } } }