P3: payments + payouts (multi-country adapter pattern, wallet-integrated)

- payments: cash instant + gateway intent/redirect + webhook confirm; topup credits wallet
- payouts: driver request (holds wallet.debit) + admin complete/fail (fail refunds)
- tables tripz_pay_payments / tripz_pay_payouts (logical payment schema, extractable later)
- providers: cash/cliq/zaincash/syriatel/mtn/shamcash/paymob/fawry (structure ready, creds later)
- migration InitPayments; wired into app.module

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 19:05:49 +03:00
co-authored by Claude Opus 4.8
parent e0ccd2efb7
commit bb6a7bc7ec
9 changed files with 447 additions and 0 deletions
@@ -0,0 +1,50 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
/**
* جداول الدفع والسحب (schema الدفع منطقياً — بادئة tripz_pay_، قابلة للفصل).
*/
export class InitPayments1721600000000 implements MigrationInterface {
public async up(q: QueryRunner): Promise<void> {
await q.query(`
CREATE TABLE IF NOT EXISTS tripz_pay_payments (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id uuid NOT NULL,
user_id uuid NOT NULL,
trip_id uuid,
purpose varchar NOT NULL DEFAULT 'topup',
provider varchar NOT NULL,
method varchar,
amount numeric(12,3) NOT NULL,
currency varchar NOT NULL DEFAULT 'JOD',
status varchar NOT NULL DEFAULT 'pending',
tx_ref varchar,
redirect_url varchar,
meta jsonb NOT NULL DEFAULT '{}',
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
)`);
await q.query(`CREATE INDEX IF NOT EXISTS "IDX_tripz_pay_payments_user" ON tripz_pay_payments (tenant_id, user_id)`);
await q.query(`CREATE INDEX IF NOT EXISTS "IDX_tripz_pay_payments_status" ON tripz_pay_payments (tenant_id, status)`);
await q.query(`
CREATE TABLE IF NOT EXISTS tripz_pay_payouts (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
tenant_id uuid NOT NULL,
driver_user_id uuid NOT NULL,
amount numeric(12,3) NOT NULL,
currency varchar NOT NULL DEFAULT 'JOD',
channel varchar NOT NULL,
destination jsonb NOT NULL DEFAULT '{}',
status varchar NOT NULL DEFAULT 'requested',
ref varchar,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
)`);
await q.query(`CREATE INDEX IF NOT EXISTS "IDX_tripz_pay_payouts_driver" ON tripz_pay_payouts (tenant_id, driver_user_id)`);
}
public async down(q: QueryRunner): Promise<void> {
await q.query(`DROP TABLE IF EXISTS tripz_pay_payouts`);
await q.query(`DROP TABLE IF EXISTS tripz_pay_payments`);
}
}