'decimal:2', 'commission_rate' => 'decimal:2', 'commission_amount' => 'decimal:2', 'matures_at' => 'datetime', 'matured_at' => 'datetime', 'paid_at' => 'datetime', ]; // Relationships public function referral(): BelongsTo { return $this->belongsTo(Referral::class); } public function referrer(): BelongsTo { return $this->belongsTo(User::class, 'referrer_id'); } public function order(): BelongsTo { return $this->belongsTo(Order::class); } public function invoice(): BelongsTo { return $this->belongsTo(Invoice::class); } public function payout(): BelongsTo { return $this->belongsTo(ReferralPayout::class); } // Status helpers public function isPending(): bool { return $this->status === self::STATUS_PENDING; } public function isMatured(): bool { return $this->status === self::STATUS_MATURED; } public function isPaid(): bool { return $this->status === self::STATUS_PAID; } public function isCancelled(): bool { return $this->status === self::STATUS_CANCELLED; } public function canMature(): bool { return $this->isPending() && $this->matures_at && $this->matures_at->isPast(); } // Actions /** * Mark commission as matured. */ public function markMatured(): void { $this->update([ 'status' => self::STATUS_MATURED, 'matured_at' => now(), ]); } /** * Mark commission as paid (included in payout). */ public function markPaid(ReferralPayout $payout): void { $this->update([ 'status' => self::STATUS_PAID, 'payout_id' => $payout->id, 'paid_at' => now(), ]); } /** * Cancel commission (refund/chargeback). */ public function cancel(?string $reason = null): void { $this->update([ 'status' => self::STATUS_CANCELLED, 'notes' => $reason, ]); } // Static factory /** * Calculate commission for an order. */ public static function calculateForOrder( Referral $referral, Order $order, ?float $commissionRate = null ): array { $commissionRate = $commissionRate ?? self::DEFAULT_COMMISSION_RATE; // Calculate commission on net order amount (after discount, before tax) $netAmount = $order->subtotal - $order->discount_amount; $commissionAmount = round($netAmount * ($commissionRate / 100), 2); // Determine maturation date based on payment method $gateway = $order->gateway ?? 'stripe'; $maturationDays = in_array($gateway, ['btcpay', 'bitcoin', 'crypto']) ? self::MATURATION_CRYPTO : self::MATURATION_CARD; return [ 'referral_id' => $referral->id, 'referrer_id' => $referral->referrer_id, 'order_id' => $order->id, 'invoice_id' => $order->invoice?->id, 'order_amount' => $netAmount, 'commission_rate' => $commissionRate, 'commission_amount' => $commissionAmount, 'currency' => $order->currency, 'status' => self::STATUS_PENDING, 'matures_at' => now()->addDays($maturationDays), ]; } // Scopes public function scopePending($query) { return $query->where('status', self::STATUS_PENDING); } public function scopeMatured($query) { return $query->where('status', self::STATUS_MATURED); } public function scopePaid($query) { return $query->where('status', self::STATUS_PAID); } public function scopeWithdrawable($query) { return $query->where('status', self::STATUS_MATURED); } public function scopeReadyToMature($query) { return $query->pending()->where('matures_at', '<=', now()); } public function scopeForReferrer($query, int $userId) { return $query->where('referrer_id', $userId); } public function scopeUnpaid($query) { return $query->whereNull('payout_id'); } public function getActivitylogOptions(): LogOptions { return LogOptions::defaults() ->logOnly(['status', 'matured_at', 'payout_id', 'paid_at']) ->logOnlyDirty() ->dontSubmitEmptyLogs() ->setDescriptionForEvent(fn (string $eventName) => "Commission {$eventName}"); } }