Initial commit - WASL Digital Wallet
This commit is contained in:
154
Backend/app/Models/Transaction.php
Normal file
154
Backend/app/Models/Transaction.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\TransactionStatus;
|
||||
use App\Enums\TransactionType;
|
||||
use App\Models\Traits\HasMinorUnits;
|
||||
use App\Models\Traits\HasUuid;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
||||
/**
|
||||
* transactions — IMMUTABLE LEDGER
|
||||
*
|
||||
* Once status = 'completed', this row is NEVER updated.
|
||||
* Reversals are new rows with type = 'refund'.
|
||||
*
|
||||
* @property string $uuid
|
||||
* @property string $reference_code WASL-XXXXXXXX (user-visible)
|
||||
* @property string $type TransactionType enum
|
||||
* @property string $status TransactionStatus enum
|
||||
* @property int|null $debit_wallet_id
|
||||
* @property int|null $credit_wallet_id
|
||||
* @property int $amount_minor BIGINT — never float/decimal
|
||||
* @property int $fee_minor BIGINT
|
||||
* @property string $currency_code
|
||||
* @property string|null $description
|
||||
* @property array|null $metadata
|
||||
* @property string $idempotency_key
|
||||
* @property string|null $initiator_ip
|
||||
* @property string|null $device_id
|
||||
*/
|
||||
class Transaction extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
use HasUuid;
|
||||
use HasMinorUnits;
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'reference_code',
|
||||
'type',
|
||||
'status',
|
||||
'debit_wallet_id',
|
||||
'credit_wallet_id',
|
||||
'amount_minor',
|
||||
'fee_minor',
|
||||
'currency_code',
|
||||
'description',
|
||||
'metadata',
|
||||
'idempotency_key',
|
||||
'initiator_ip',
|
||||
'device_id',
|
||||
'initiated_at',
|
||||
'completed_at',
|
||||
'failure_reason',
|
||||
];
|
||||
|
||||
protected $casts = [
|
||||
'type' => 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user