Files
tripz-llc/backend/src/modules/payments/entities/payment.entity.ts
T
Hamza-AyedandClaude Fable 5 b1a060c5ed feat: P0/P3/P5 — دفترا المستأجر ورسم العملية وتوجيه المال
الأساس المالي حسب docs/24: فصل ما نملكه عمّا نحتفظ به لغيرنا.

- جدولان منفصلان (`tenant_revenue_ledger` · `tenant_pending_ledger`) لا جدول
  واحد بعمود نوع: استعلامٌ ينسى الشرط كان يجعل المالك يسحب من مال الركّاب.
  مضافان فقط، والرصيد مشتقّ لا حقل يُحدَّث.
- فهرس فريد (tenant_id, ref) = حارس التسوية المزدوجة. التصادم يُلتقط من
  القاعدة لا بالفحص المسبق وحده — نداءان متزامنان يمرّان معاً قبل أي كتابة.
- رسم العملية من مصدر واحد (35 ل.س · 5 ج.م · 0.20 د.أ) قابل للتجاوز من إعداد
  المستأجر، مع قصّه عند المبلغ حتى لا يخرج المستخدم بصافٍ سالب.
- توجيه الدفع: شحن السائق ← إيراد + رصيده التشغيلي · شحن الراكب ← أمانة ·
  الرسم ← إيراد. وفُتح مسار شحن السائق الذي لم يكن له مدخل إطلاقاً.
- ثغرة سُدّت: `purpose` يصل من الجسم، فراكب كان يستطيع إرسال `credit_topup`
  فيُسجَّل مالُه إيراداً ويُشحن حساب سائق لا يملكه. الدور الآن من التوكن.
- `/admin/overview`: الإيراد من الدفتر، وعمولة الرحلات حقل منفصل عنه.
- `/admin/wallet/summary` و`/revenue-by-reason` لأدمن المستأجر.

25 اختباراً جديداً (188 إجمالاً، كلها خضراء). أحدها أمسك عطلاً فعلياً: صافٍ
صفري كان ينادي الدفتر بصفر فيرمي خطأً بعد قيد الرسم — عملية نصف مطبَّقة.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 15:04:45 +03:00

71 lines
1.8 KiB
TypeScript

import {
Column,
CreateDateColumn,
Entity,
Index,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
export type PaymentStatus = 'pending' | 'success' | 'failed' | 'refunded';
/**
* `topup` = شحن محفظة الراكب (**أمانة** — تبقى ملكه).
* `credit_topup` = شحن السائق رصيده التشغيلي (**إيراد** المستأجر — docs/24 §3.1).
* `trip` = دفع أجرة رحلة.
* التمييز بين الأولين جوهري: خلطهما يجعل تقرير الأرباح كاذباً.
*/
export type PaymentPurpose = 'topup' | 'credit_topup' | 'trip';
/**
* معاملة دفع. الجدول: tripz_pay_payments («schema الدفع» منطقياً — قابل للفصل).
*/
@Entity('pay_payments')
@Index(['tenant_id', 'user_id'])
@Index(['tenant_id', 'status'])
export class Payment {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
tenant_id: string;
@Column({ type: 'uuid' })
user_id: string;
@Column({ type: 'uuid', nullable: true })
trip_id: string | null;
@Column({ default: 'topup' })
purpose: PaymentPurpose;
@Column()
provider: string; // cash | cliq | zaincash | syriatel | mtn | shamcash | paymob | fawry
@Column({ type: 'varchar', nullable: true })
method: string | null;
@Column({ type: 'numeric', precision: 12, scale: 3 })
amount: number;
@Column({ default: 'JOD' })
currency: string;
@Column({ type: 'varchar', default: 'pending' })
status: PaymentStatus;
@Column({ type: 'varchar', nullable: true })
tx_ref: string | null;
@Column({ type: 'varchar', nullable: true })
redirect_url: string | null;
@Column({ type: 'jsonb', default: {} })
meta: Record<string, any>;
@CreateDateColumn()
created_at: Date;
@UpdateDateColumn()
updated_at: Date;
}