'decimal:2', 'btc_amount' => 'decimal:8', 'btc_rate' => 'decimal:8', 'requested_at' => 'datetime', 'processed_at' => 'datetime', 'completed_at' => 'datetime', 'failed_at' => 'datetime', ]; // Relationships public function user(): BelongsTo { return $this->belongsTo(User::class); } public function commissions(): HasMany { return $this->hasMany(ReferralCommission::class, 'payout_id'); } public function processor(): BelongsTo { return $this->belongsTo(User::class, 'processed_by'); } // Status helpers public function isRequested(): bool { return $this->status === self::STATUS_REQUESTED; } public function isProcessing(): bool { return $this->status === self::STATUS_PROCESSING; } public function isCompleted(): bool { return $this->status === self::STATUS_COMPLETED; } public function isFailed(): bool { return $this->status === self::STATUS_FAILED; } public function isCancelled(): bool { return $this->status === self::STATUS_CANCELLED; } public function isPending(): bool { return in_array($this->status, [self::STATUS_REQUESTED, self::STATUS_PROCESSING]); } // Method helpers public function isBtcPayout(): bool { return $this->method === self::METHOD_BTC; } public function isAccountCredit(): bool { return $this->method === self::METHOD_ACCOUNT_CREDIT; } // Actions /** * Mark as processing. */ public function markProcessing(User $admin): void { $this->update([ 'status' => self::STATUS_PROCESSING, 'processed_at' => now(), 'processed_by' => $admin->id, ]); } /** * Mark as completed. */ public function markCompleted(?string $btcTxid = null, ?float $btcAmount = null, ?float $btcRate = null): void { $updates = [ 'status' => self::STATUS_COMPLETED, 'completed_at' => now(), ]; if ($btcTxid) { $updates['btc_txid'] = $btcTxid; } if ($btcAmount) { $updates['btc_amount'] = $btcAmount; $updates['btc_rate'] = $btcRate; } $this->update($updates); // Mark all commissions as paid $this->commissions()->update([ 'status' => ReferralCommission::STATUS_PAID, 'paid_at' => now(), ]); } /** * Mark as failed. */ public function markFailed(string $reason): void { $this->update([ 'status' => self::STATUS_FAILED, 'failed_at' => now(), 'failure_reason' => $reason, ]); // Return commissions to matured status $this->commissions()->update([ 'status' => ReferralCommission::STATUS_MATURED, 'payout_id' => null, ]); } /** * Cancel payout request. */ public function cancel(?string $reason = null): void { $this->update([ 'status' => self::STATUS_CANCELLED, 'notes' => $reason ?? $this->notes, ]); // Return commissions to matured status $this->commissions()->update([ 'status' => ReferralCommission::STATUS_MATURED, 'payout_id' => null, ]); } // Static helpers /** * Generate a unique payout number. */ public static function generatePayoutNumber(): string { $prefix = 'PAY'; $date = now()->format('Ymd'); $random = strtoupper(substr(md5(uniqid()), 0, 6)); return "{$prefix}-{$date}-{$random}"; } /** * Get minimum payout amount for a method. */ public static function getMinimumPayout(string $method): float { return match ($method) { self::METHOD_BTC => self::MINIMUM_BTC_PAYOUT, self::METHOD_ACCOUNT_CREDIT => self::MINIMUM_CREDIT_PAYOUT, default => self::MINIMUM_BTC_PAYOUT, }; } // Scopes public function scopeRequested($query) { return $query->where('status', self::STATUS_REQUESTED); } public function scopeProcessing($query) { return $query->where('status', self::STATUS_PROCESSING); } public function scopeCompleted($query) { return $query->where('status', self::STATUS_COMPLETED); } public function scopePending($query) { return $query->whereIn('status', [self::STATUS_REQUESTED, self::STATUS_PROCESSING]); } public function scopeForUser($query, int $userId) { return $query->where('user_id', $userId); } public function scopeByMethod($query, string $method) { return $query->where('method', $method); } public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() ->logOnly(['status', 'processed_at', 'completed_at', 'failed_at', 'btc_txid']) ->logOnlyDirty() ->dontSubmitEmptyLogs() ->setDescriptionForEvent(fn (string $eventName) => "Payout {$eventName}"); } }