143 lines
4.3 KiB
PHP
143 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Enums\CreditLedgerEntryType;
|
|
use App\Models\CreditLedgerEntry;
|
|
use App\Models\CreditProduct;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class AiCreditService
|
|
{
|
|
public function consume(User $user, int $credits = 1, ?string $source = null): bool
|
|
{
|
|
return DB::transaction(function () use ($credits, $source, $user): bool {
|
|
$lockedUser = User::query()
|
|
->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<string, mixed>|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<string, mixed>|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,
|
|
]);
|
|
}
|
|
}
|