mirror of
https://github.com/coollabsio/coolify.git
synced 2026-06-14 03:19:51 +00:00
feat(subscription): add billing interval to price preview
Extract and return the billing interval (month/year) from subscription pricing data in fetchPricePreview. Update the view to dynamically display the correct billing period based on the preview response instead of using static PHP logic.
This commit is contained in:
@@ -4,6 +4,7 @@ namespace App\Actions\Stripe;
|
||||
|
||||
use App\Jobs\ServerLimitCheckJob;
|
||||
use App\Models\Team;
|
||||
use Stripe\Exception\InvalidRequestException;
|
||||
use Stripe\StripeClient;
|
||||
|
||||
class UpdateSubscriptionQuantity
|
||||
@@ -42,6 +43,7 @@ class UpdateSubscriptionQuantity
|
||||
}
|
||||
|
||||
$currency = strtoupper($item->price->currency ?? 'usd');
|
||||
$billingInterval = $item->price->recurring->interval ?? 'month';
|
||||
|
||||
// Upcoming invoice gives us the prorated amount due now
|
||||
$upcomingInvoice = $this->stripe->invoices->upcoming([
|
||||
@@ -99,6 +101,7 @@ class UpdateSubscriptionQuantity
|
||||
'tax_description' => $taxDescription,
|
||||
'quantity' => $quantity,
|
||||
'currency' => $currency,
|
||||
'billing_interval' => $billingInterval,
|
||||
],
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
@@ -184,7 +187,7 @@ class UpdateSubscriptionQuantity
|
||||
\Log::info("Subscription {$subscription->stripe_subscription_id} quantity updated to {$quantity} for team {$team->name}");
|
||||
|
||||
return ['success' => true, 'error' => null];
|
||||
} catch (\Stripe\Exception\InvalidRequestException $e) {
|
||||
} catch (InvalidRequestException $e) {
|
||||
\Log::error("Stripe update quantity error for team {$team->id}: ".$e->getMessage());
|
||||
|
||||
return ['success' => false, 'error' => 'Stripe error: '.$e->getMessage()];
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
<span class="dark:text-white" x-text="fmt(preview?.recurring_tax)"></span>
|
||||
</div>
|
||||
<div class="flex justify-between gap-6 text-sm font-bold pt-1.5 border-t dark:border-coolgray-400 border-neutral-200">
|
||||
<span class="dark:text-white">Total / {{ $billingInterval === 'yearly' ? 'year' : 'month' }}</span>
|
||||
<span class="dark:text-white">Total / <span x-text="preview?.billing_interval === 'year' ? 'year' : 'month'">month</span></span>
|
||||
<span class="dark:text-white" x-text="fmt(preview?.recurring_total)"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
use Stripe\Exception\InvalidRequestException;
|
||||
use Stripe\Service\InvoiceService;
|
||||
use Stripe\Service\SubscriptionService;
|
||||
use Stripe\Service\TaxRateService;
|
||||
@@ -46,7 +47,7 @@ beforeEach(function () {
|
||||
'data' => [(object) [
|
||||
'id' => 'si_item_123',
|
||||
'quantity' => 2,
|
||||
'price' => (object) ['unit_amount' => 500, 'currency' => 'usd'],
|
||||
'price' => (object) ['unit_amount' => 500, 'currency' => 'usd', 'recurring' => (object) ['interval' => 'month']],
|
||||
]],
|
||||
],
|
||||
];
|
||||
@@ -187,7 +188,7 @@ describe('UpdateSubscriptionQuantity::execute', function () {
|
||||
test('handles stripe API error gracefully', function () {
|
||||
$this->mockSubscriptions
|
||||
->shouldReceive('retrieve')
|
||||
->andThrow(new \Stripe\Exception\InvalidRequestException('Subscription not found'));
|
||||
->andThrow(new InvalidRequestException('Subscription not found'));
|
||||
|
||||
$action = new UpdateSubscriptionQuantity($this->mockStripe);
|
||||
$result = $action->execute($this->team, 5);
|
||||
@@ -199,7 +200,7 @@ describe('UpdateSubscriptionQuantity::execute', function () {
|
||||
test('handles generic exception gracefully', function () {
|
||||
$this->mockSubscriptions
|
||||
->shouldReceive('retrieve')
|
||||
->andThrow(new \RuntimeException('Network error'));
|
||||
->andThrow(new RuntimeException('Network error'));
|
||||
|
||||
$action = new UpdateSubscriptionQuantity($this->mockStripe);
|
||||
$result = $action->execute($this->team, 5);
|
||||
@@ -270,6 +271,46 @@ describe('UpdateSubscriptionQuantity::fetchPricePreview', function () {
|
||||
expect($result['preview']['tax_description'])->toContain('27%');
|
||||
expect($result['preview']['quantity'])->toBe(3);
|
||||
expect($result['preview']['currency'])->toBe('USD');
|
||||
expect($result['preview']['billing_interval'])->toBe('month');
|
||||
});
|
||||
|
||||
test('returns yearly billing interval for annual subscriptions', function () {
|
||||
$yearlySubscriptionResponse = (object) [
|
||||
'items' => (object) [
|
||||
'data' => [(object) [
|
||||
'id' => 'si_item_123',
|
||||
'quantity' => 2,
|
||||
'price' => (object) ['unit_amount' => 500, 'currency' => 'usd', 'recurring' => (object) ['interval' => 'year']],
|
||||
]],
|
||||
],
|
||||
];
|
||||
|
||||
$this->mockSubscriptions
|
||||
->shouldReceive('retrieve')
|
||||
->with('sub_test_qty')
|
||||
->andReturn($yearlySubscriptionResponse);
|
||||
|
||||
$this->mockInvoices
|
||||
->shouldReceive('upcoming')
|
||||
->andReturn((object) [
|
||||
'amount_due' => 1000,
|
||||
'total' => 1000,
|
||||
'subtotal' => 1000,
|
||||
'tax' => 0,
|
||||
'currency' => 'usd',
|
||||
'lines' => (object) [
|
||||
'data' => [
|
||||
(object) ['amount' => 1000, 'proration' => false],
|
||||
],
|
||||
],
|
||||
'total_tax_amounts' => [],
|
||||
]);
|
||||
|
||||
$action = new UpdateSubscriptionQuantity($this->mockStripe);
|
||||
$result = $action->fetchPricePreview($this->team, 2);
|
||||
|
||||
expect($result['success'])->toBeTrue();
|
||||
expect($result['preview']['billing_interval'])->toBe('year');
|
||||
});
|
||||
|
||||
test('returns preview without tax when no tax applies', function () {
|
||||
@@ -336,7 +377,7 @@ describe('UpdateSubscriptionQuantity::fetchPricePreview', function () {
|
||||
test('handles Stripe API error gracefully', function () {
|
||||
$this->mockSubscriptions
|
||||
->shouldReceive('retrieve')
|
||||
->andThrow(new \RuntimeException('API error'));
|
||||
->andThrow(new RuntimeException('API error'));
|
||||
|
||||
$action = new UpdateSubscriptionQuantity($this->mockStripe);
|
||||
$result = $action->fetchPricePreview($this->team, 5);
|
||||
|
||||
Reference in New Issue
Block a user