TransactionType::class, 'status' => TransactionStatus::class, 'amount_minor' => 'integer', 'fee_minor' => 'integer', 'metadata' => 'array', 'initiated_at' => 'datetime', 'completed_at' => 'datetime', ]; // ── Relationships ── public function debitWallet() { return $this->belongsTo(Wallet::class, 'debit_wallet_id'); } public function creditWallet() { return $this->belongsTo(Wallet::class, 'credit_wallet_id'); } public function entries() { return $this->hasMany(TransactionEntry::class); } public function fraudAlerts() { return $this->hasMany(FraudAlert::class); } // ── Scopes ── public function scopePending($query) { return $query->where('status', TransactionStatus::PENDING); } public function scopeCompleted($query) { return $query->where('status', TransactionStatus::COMPLETED); } public function scopeFailed($query) { return $query->where('status', TransactionStatus::FAILED); } public function scopeForWallet($query, int $walletId) { return $query->where('debit_wallet_id', $walletId) ->orWhere('credit_wallet_id', $walletId); } public function scopeInFlight($query) { return $query->whereIn('status', [ TransactionStatus::PENDING, TransactionStatus::PROCESSING, ]); } // ── Helpers ── public function formattedAmount(): string { return self::formatMoney($this->amount_minor, $this->currency_code); } public function formattedFee(): string { return self::formatMoney($this->fee_minor, $this->currency_code); } public function isFinal(): bool { return $this->status->isFinal(); } /** * Generate a unique reference code: WASL-XXXXXXXX * Uses random alphanumeric characters for human readability. */ public static function generateReferenceCode(): string { $prefix = config('wasl.reference.prefix', 'WASL'); $length = config('wasl.reference.length', 8); $chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // no 0/O/1/I to avoid confusion do { $code = $prefix . '-' . substr(str_shuffle($chars), 0, $length); } while (self::where('reference_code', $code)->exists()); return $code; } }