first(); if (! $user_credit) { $user_credit = UserCredit::create([ 'user_id' => $user_id, 'subscription_credits' => 0, 'alacarte_credits' => 0, 'spend_subscription_first' => true, ]); } return $user_credit; } // when user purchase annual subscription, need to 12x the deposit amount public static function deposit(int $user_id, int $amount, string $type, ?string $description = null): bool { if ($amount <= 0) { throw new \InvalidArgumentException('Amount must be positive'); } if (! in_array($type, [UserCredit::TYPE_SUBSCRIPTION, UserCredit::TYPE_ALACARTE])) { throw new \InvalidArgumentException('Type must be subscription or alacarte'); } return DB::transaction(function () use ($user_id, $amount, $type, $description) { $user_credit = self::getUserCredits($user_id); if ($type === UserCredit::TYPE_SUBSCRIPTION) { $user_credit->subscription_credits += $amount; } else { $user_credit->alacarte_credits += $amount; } $user_credit->save(); self::logTransaction($user_id, $type, 'deposit', $amount, $user_credit, $description); return true; }); } public static function spend(int $user_id, int $amount, ?string $description = null, ?bool $use_subscription_first = null): bool { if ($amount <= 0) { throw new \InvalidArgumentException('Amount must be positive'); } if (! self::canSpend($user_id, $amount)) { return false; } return DB::transaction(function () use ($user_id, $amount, $description, $use_subscription_first) { $user_credit = self::getUserCredits($user_id); $remaining = $amount; $subscription_first = $use_subscription_first ?? $user_credit->spend_subscription_first; $spend_order = $subscription_first ? [UserCredit::TYPE_SUBSCRIPTION, UserCredit::TYPE_ALACARTE] : [UserCredit::TYPE_ALACARTE, UserCredit::TYPE_SUBSCRIPTION]; foreach ($spend_order as $type) { if ($remaining <= 0) { break; } $available = $type === UserCredit::TYPE_SUBSCRIPTION ? $user_credit->subscription_credits : $user_credit->alacarte_credits; if ($available > 0) { $to_spend = min($remaining, $available); if ($type === UserCredit::TYPE_SUBSCRIPTION) { $user_credit->subscription_credits -= $to_spend; } else { $user_credit->alacarte_credits -= $to_spend; } self::logTransaction($user_id, $type, 'spend', -$to_spend, $user_credit, $description); $remaining -= $to_spend; } } $user_credit->save(); return true; }); } public static function canSpend(int $user_id, int $amount): bool { $user_credit = self::getUserCredits($user_id); return self::_getTotalBalance($user_credit) >= $amount; } public static function summary(int $user_id): array { $user_credit = self::getUserCredits($user_id); return [ 'subscription' => $user_credit->subscription_credits, 'alacarte' => $user_credit->alacarte_credits, 'total' => self::_getTotalBalance($user_credit), 'preference' => $user_credit->spend_subscription_first ? 'subscription_first' : 'alacarte_first', ]; } public static function balance(int $user_id, ?string $type = null): int { $user_credit = self::getUserCredits($user_id); return match ($type) { UserCredit::TYPE_SUBSCRIPTION => $user_credit->subscription_credits, UserCredit::TYPE_ALACARTE => $user_credit->alacarte_credits, default => self::_getTotalBalance($user_credit), }; } private static function _getTotalBalance(UserCredit $user_credit): int { return $user_credit->subscription_credits + $user_credit->alacarte_credits; } public static function setSpendSubscriptionFirst(int $user_id, bool $spendSubscriptionFirst): bool { $user_credit = self::getUserCredits($user_id); $user_credit->spend_subscription_first = $spendSubscriptionFirst; return $user_credit->save(); } public static function transactionHistory(int $user_id, int $limit = 20): \Illuminate\Database\Eloquent\Collection { $user_credit = self::getUserCredits($user_id); return $user_credit->transactions()->limit($limit)->get(); } private static function logTransaction(int $user_id, string $type, string $operation, int $amount, UserCredit $user_credit, ?string $description = null): void { UserCreditTransaction::create([ 'user_id' => $user_id, 'credit_type' => $type, 'operation' => $operation, 'amount' => $amount, 'subscription_balance_after' => $user_credit->subscription_credits, 'alacarte_balance_after' => $user_credit->alacarte_credits, 'description' => $description, ]); } }