feat: clean credits users
This commit is contained in:
@@ -1,142 +0,0 @@
|
||||
<?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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class PaymentInvoiceEmailer
|
||||
|
||||
try {
|
||||
Mail::to($transaction->user->email)->send(
|
||||
new PaymentInvoiceMail($transaction->user, $transaction->loadMissing('creditProduct'))
|
||||
new PaymentInvoiceMail($transaction->user, $transaction->loadMissing('billingProduct'))
|
||||
);
|
||||
} catch (Throwable $exception) {
|
||||
$transaction->forceFill([
|
||||
|
||||
@@ -4,8 +4,7 @@ namespace App\Services;
|
||||
|
||||
use App\Enums\PaymentTransactionStatus;
|
||||
use App\Enums\PaymentTransactionType;
|
||||
use App\Models\CreditLedgerEntry;
|
||||
use App\Models\CreditProduct;
|
||||
use App\Models\BillingProduct;
|
||||
use App\Models\PaymentTransaction;
|
||||
use App\Models\User;
|
||||
|
||||
@@ -16,7 +15,7 @@ class PaymentTransactionRecorder
|
||||
*/
|
||||
public function recordCheckoutSession(
|
||||
?User $user,
|
||||
?CreditProduct $product,
|
||||
?BillingProduct $product,
|
||||
string $stripeCheckoutSessionId,
|
||||
PaymentTransactionStatus $status = PaymentTransactionStatus::PENDING,
|
||||
?string $stripeEventId = null,
|
||||
@@ -25,9 +24,6 @@ class PaymentTransactionRecorder
|
||||
?string $stripePaymentIntentId = null,
|
||||
?int $amount = null,
|
||||
?string $currency = null,
|
||||
?int $creditsExpected = null,
|
||||
?int $creditsGranted = null,
|
||||
?CreditLedgerEntry $ledgerEntry = null,
|
||||
?string $invoiceUrl = null,
|
||||
?string $invoicePdfUrl = null,
|
||||
?string $errorMessage = null,
|
||||
@@ -37,8 +33,7 @@ class PaymentTransactionRecorder
|
||||
'stripe_checkout_session_id' => $stripeCheckoutSessionId,
|
||||
], [
|
||||
'user_id' => $user?->getKey(),
|
||||
'credit_product_id' => $product?->getKey(),
|
||||
'credit_ledger_entry_id' => $ledgerEntry?->getKey(),
|
||||
'billing_product_id' => $product?->getKey(),
|
||||
'type' => PaymentTransactionType::CHECKOUT,
|
||||
'status' => $status,
|
||||
'stripe_event_id' => $stripeEventId,
|
||||
@@ -47,8 +42,6 @@ class PaymentTransactionRecorder
|
||||
'stripe_payment_intent_id' => $stripePaymentIntentId,
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'credits_expected' => $creditsExpected ?? $product?->credits,
|
||||
'credits_granted' => $creditsGranted ?? 0,
|
||||
'invoice_url' => $invoiceUrl,
|
||||
'invoice_pdf_url' => $invoicePdfUrl,
|
||||
'error_message' => $errorMessage,
|
||||
@@ -62,7 +55,7 @@ class PaymentTransactionRecorder
|
||||
*/
|
||||
public function recordInvoice(
|
||||
?User $user,
|
||||
?CreditProduct $product,
|
||||
?BillingProduct $product,
|
||||
string $stripeInvoiceId,
|
||||
?string $stripePriceId,
|
||||
PaymentTransactionStatus $status,
|
||||
@@ -73,9 +66,6 @@ class PaymentTransactionRecorder
|
||||
?string $stripeSubscriptionId = null,
|
||||
?int $amount = null,
|
||||
?string $currency = null,
|
||||
?int $creditsExpected = null,
|
||||
?int $creditsGranted = null,
|
||||
?CreditLedgerEntry $ledgerEntry = null,
|
||||
?string $invoiceUrl = null,
|
||||
?string $invoicePdfUrl = null,
|
||||
?string $errorMessage = null,
|
||||
@@ -86,8 +76,7 @@ class PaymentTransactionRecorder
|
||||
'stripe_price_id' => $stripePriceId,
|
||||
], [
|
||||
'user_id' => $user?->getKey(),
|
||||
'credit_product_id' => $product?->getKey(),
|
||||
'credit_ledger_entry_id' => $ledgerEntry?->getKey(),
|
||||
'billing_product_id' => $product?->getKey(),
|
||||
'type' => PaymentTransactionType::SUBSCRIPTION_INVOICE,
|
||||
'status' => $status,
|
||||
'stripe_event_id' => $stripeEventId,
|
||||
@@ -97,8 +86,6 @@ class PaymentTransactionRecorder
|
||||
'stripe_subscription_id' => $stripeSubscriptionId,
|
||||
'amount' => $amount,
|
||||
'currency' => $currency,
|
||||
'credits_expected' => $creditsExpected ?? $product?->credits,
|
||||
'credits_granted' => $creditsGranted ?? 0,
|
||||
'invoice_url' => $invoiceUrl,
|
||||
'invoice_pdf_url' => $invoicePdfUrl,
|
||||
'error_message' => $errorMessage,
|
||||
|
||||
+6
-6
@@ -3,12 +3,12 @@
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\BillingProductPurpose;
|
||||
use App\Models\CreditProduct;
|
||||
use App\Models\BillingProduct;
|
||||
use Laravel\Cashier\Cashier;
|
||||
|
||||
class StripeCreditProductSyncer
|
||||
class StripeBillingProductSyncer
|
||||
{
|
||||
public function sync(CreditProduct $product): CreditProduct
|
||||
public function sync(BillingProduct $product): BillingProduct
|
||||
{
|
||||
throw_if(
|
||||
$product->purpose === null,
|
||||
@@ -24,7 +24,7 @@ class StripeCreditProductSyncer
|
||||
'name' => $product->name,
|
||||
'description' => $product->description,
|
||||
'metadata' => [
|
||||
'credit_product_id' => $product->getKey(),
|
||||
'billing_product_id' => $product->getKey(),
|
||||
'billing_product_purpose' => $product->purpose?->value,
|
||||
],
|
||||
]);
|
||||
@@ -36,7 +36,7 @@ class StripeCreditProductSyncer
|
||||
'description' => $product->description,
|
||||
'active' => (bool) $product->is_active,
|
||||
'metadata' => [
|
||||
'credit_product_id' => $product->getKey(),
|
||||
'billing_product_id' => $product->getKey(),
|
||||
'billing_product_purpose' => $product->purpose?->value,
|
||||
],
|
||||
]);
|
||||
@@ -58,7 +58,7 @@ class StripeCreditProductSyncer
|
||||
'unit_amount' => $product->amount,
|
||||
'currency' => strtolower($product->currency),
|
||||
'metadata' => [
|
||||
'credit_product_id' => $product->getKey(),
|
||||
'billing_product_id' => $product->getKey(),
|
||||
'billing_product_purpose' => $product->purpose?->value,
|
||||
],
|
||||
];
|
||||
Reference in New Issue
Block a user