trypost/tests/Unit/Actions/Billing/StartSubscriptionCheckoutTest.php
Paulo Castellano 4590fc5fd1
Make Stripe Checkout configurable via billing env knobs (#252)
* Make Stripe Checkout configurable via billing env knobs

Replace the hard-required $1 first-month coupon with env-driven trial days,
optional coupon, and allow_promotion_codes so SaaS can switch checkout modes
without a code change.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Fix no-effect ReflectionClass import in checkout test

CI treats bare global use statements as ErrorException and aborts
loading the suite before any assertions run.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Document checkout env knobs in AGENTS.md instead of .ai/

Remove the Boost record-rule .ai/rules folder and keep durable billing
checkout guidance in AGENTS.md / CLAUDE.md project-specific rules.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Harden checkout env knobs from review findings

Default allow_promotion_codes to false, grant Stripe trial only to
first-time subscribers, clarify the coupon/promo XOR error, and cover
negative XOR cases plus StartSubscriptionCheckout wiring.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 21:25:32 -03:00

57 lines
2 KiB
PHP

<?php
declare(strict_types=1);
use App\Actions\Billing\StartSubscriptionCheckout;
use App\Models\Account;
use App\Models\Workspace;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Cashier\SubscriptionBuilder;
use Mockery\MockInterface;
use Symfony\Component\HttpFoundation\Response;
uses(RefreshDatabase::class);
test('redirect applies checkout configuration before opening the stripe session', function () {
config([
'trypost.billing.require_card_for_trial' => true,
'cashier.trial_days' => 8,
'cashier.first_month_coupon_id' => '',
'cashier.allow_promotion_codes' => false,
]);
$account = Account::factory()->create();
Workspace::factory()->create(['account_id' => $account->id]);
$cancelUrl = route('app.welcome');
$builder = Mockery::mock(SubscriptionBuilder::class);
$builder->shouldReceive('quantity')->once()->with(1)->andReturnSelf();
$builder->shouldReceive('trialDays')->once()->with(8)->andReturnSelf();
$builder->shouldReceive('withCoupon')->never();
$builder->shouldReceive('allowPromotionCodes')->never();
$builder->shouldReceive('checkout')
->once()
->with([
'success_url' => route('app.billing.processing').'?session_id={CHECKOUT_SESSION_ID}',
'cancel_url' => $cancelUrl,
])
->andReturn((object) ['url' => 'https://checkout.stripe.test/session']);
/** @var Account&MockInterface $accountMock */
$accountMock = Mockery::mock($account)->makePartial();
$accountMock->shouldReceive('createOrGetStripeCustomer')->once()->andReturnNull();
$accountMock->shouldReceive('newSubscription')
->once()
->with(Account::SUBSCRIPTION_NAME, 'price_monthly_test')
->andReturn($builder);
$response = app(StartSubscriptionCheckout::class)->redirect(
$accountMock,
'price_monthly_test',
$cancelUrl,
);
expect($response->headers->get('Location'))->toBe('https://checkout.stripe.test/session')
->and($response->getStatusCode())->toBe(Response::HTTP_FOUND);
});