From 74cbdd42efea0e8884639b049a5de7138489fad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Fri, 24 Apr 2026 13:07:58 +0100 Subject: [PATCH] feat(billing): apply Stripe tax rates to subscriptions (#325) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Every subscription now gets Stripe tax rates attached automatically via Cashier. ## Changes - `config/subscriptions.php`: new `tax_rates` array, env `STRIPE_TAX_RATES` (comma-separated), default `txr_1TPfzrLRCmKA3oWMNWmkQeq2` - `app/Models/User.php`: `taxRates()` reads from config — Cashier picks it up automatically on `newSubscription()` checkout + subscription creation - `tests/Feature/SubscriptionTest.php`: 2 tests ## Applies to - New checkout sessions (`SubscriptionController::checkout`) - New subscriptions created via Cashier ## Existing subscriptions Not updated automatically. To sync: ```php $user->subscription('default')->syncTaxRates(); ``` ## Notes Using hard-coded tax rate IDs (not Stripe Tax auto-calc). Switch to `Cashier::calculateTaxes()` later if desired. --- app/Models/User.php | 10 ++++++++++ config/subscriptions.php | 12 ++++++++++++ tests/Feature/SubscriptionTest.php | 12 ++++++++++++ 3 files changed, 34 insertions(+) diff --git a/app/Models/User.php b/app/Models/User.php index 0f935505..f59d74c9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -165,6 +165,16 @@ class User extends Authenticatable implements HasLocalePreference, MustVerifyEma return $this->subscribed('default'); } + /** + * The tax rates that should apply to the customer's subscriptions. + * + * @return array + */ + public function taxRates(): array + { + return config('subscriptions.tax_rates', []); + } + public function isDemoAccount(): bool { return $this->email === config('app.demo.email'); diff --git a/config/subscriptions.php b/config/subscriptions.php index da46dcba..09715959 100644 --- a/config/subscriptions.php +++ b/config/subscriptions.php @@ -131,6 +131,18 @@ return [ | */ + /* + |-------------------------------------------------------------------------- + | Tax Rates + |-------------------------------------------------------------------------- + | + | Stripe tax rate IDs applied to every subscription created via Cashier. + | Configure tax rates in your Stripe dashboard and reference them here. + | + */ + + 'tax_rates' => array_values(array_filter(explode(',', (string) env('STRIPE_TAX_RATES', 'txr_1TPfzrLRCmKA3oWMNWmkQeq2')))), + 'promo' => [ 'enabled' => env('PROMO_ENABLED', true), 'code' => 'FOUNDER', diff --git a/tests/Feature/SubscriptionTest.php b/tests/Feature/SubscriptionTest.php index 4a402553..f2b0b57d 100644 --- a/tests/Feature/SubscriptionTest.php +++ b/tests/Feature/SubscriptionTest.php @@ -272,6 +272,18 @@ test('paywall shows canUseFreePlan false when user has a bank connection', funct ); }); +test('taxRates returns configured stripe tax rate ids', function () { + config(['subscriptions.tax_rates' => ['txr_test_1', 'txr_test_2']]); + + $user = User::factory()->create(); + + expect($user->taxRates())->toBe(['txr_test_1', 'txr_test_2']); +}); + +test('taxRates config defaults to the production tax rate id', function () { + expect(config('subscriptions.tax_rates'))->toContain('txr_1TPfzrLRCmKA3oWMNWmkQeq2'); +}); + test('billing portal creates stripe customer when user has no stripe id', function () { $user = Mockery::mock(User::class)->shouldIgnoreMissing(); $user->shouldReceive('isDemoAccount')->andReturn(false);