whereKey($user->getKey()) ->lockForUpdate() ->firstOrFail(); if ($lockedUser->ai_credits_balance < $credits) { return false; } $lockedUser->decrement('ai_credits_balance', $credits); $lockedUser->refresh(); $this->record( user: $lockedUser, type: CreditLedgerEntryType::USAGE, credits: -$credits, source: $source, ); $user->forceFill([ 'ai_credits_balance' => $lockedUser->ai_credits_balance, ]); return true; }); } public function refund(User $user, int $credits = 1, ?string $source = null): CreditLedgerEntry { return $this->grant( user: $user, credits: $credits, type: CreditLedgerEntryType::REFUND, source: $source, ); } /** * @param array|null $metadata */ public function grant( User $user, int $credits, CreditLedgerEntryType $type, ?CreditProduct $product = null, ?string $source = null, ?string $stripeCheckoutSessionId = null, ?string $stripeInvoiceId = null, ?string $stripePaymentIntentId = null, ?string $stripeSubscriptionId = null, ?array $metadata = null, ): ?CreditLedgerEntry { return DB::transaction(function () use ( $credits, $metadata, $product, $source, $stripeCheckoutSessionId, $stripeInvoiceId, $stripePaymentIntentId, $stripeSubscriptionId, $type, $user, ): ?CreditLedgerEntry { if ($source && CreditLedgerEntry::query()->where('source', $source)->exists()) { return null; } $lockedUser = User::query() ->whereKey($user->getKey()) ->lockForUpdate() ->firstOrFail(); $lockedUser->increment('ai_credits_balance', $credits); $lockedUser->refresh(); $entry = $this->record( user: $lockedUser, type: $type, credits: $credits, product: $product, source: $source, stripeCheckoutSessionId: $stripeCheckoutSessionId, stripeInvoiceId: $stripeInvoiceId, stripePaymentIntentId: $stripePaymentIntentId, stripeSubscriptionId: $stripeSubscriptionId, metadata: $metadata, ); $user->forceFill([ 'ai_credits_balance' => $lockedUser->ai_credits_balance, ]); return $entry; }); } /** * @param array|null $metadata */ private function record( User $user, CreditLedgerEntryType $type, int $credits, ?CreditProduct $product = null, ?string $source = null, ?string $stripeCheckoutSessionId = null, ?string $stripeInvoiceId = null, ?string $stripePaymentIntentId = null, ?string $stripeSubscriptionId = null, ?array $metadata = null, ): CreditLedgerEntry { return CreditLedgerEntry::create([ 'user_id' => $user->getKey(), 'credit_product_id' => $product?->getKey(), 'type' => $type, 'credits' => $credits, 'balance_after' => $user->ai_credits_balance, 'source' => $source, 'stripe_checkout_session_id' => $stripeCheckoutSessionId, 'stripe_invoice_id' => $stripeInvoiceId, 'stripe_payment_intent_id' => $stripePaymentIntentId, 'stripe_subscription_id' => $stripeSubscriptionId, 'metadata' => $metadata, ]); } }