import { describe, it, expect } from 'vitest'; // Mirrors the grant decision in src/routes/webhooks-stripe.ts. `checkout.session.completed` fires // as soon as Checkout finishes, which for delayed-notification payment methods (ACH debit, bank // transfer, some wallets) happens BEFORE any money moves — payment_status 'unpaid'. Granting on // the event alone hands out a paid plan for an unsettled payment. type PaymentStatus = 'paid' | 'unpaid' | 'no_payment_required'; function shouldGrantPlan(paymentStatus: PaymentStatus): boolean { return paymentStatus === 'paid' || paymentStatus === 'no_payment_required'; } // Mirrors the subscription-status branch in the same file. type SubStatus = | 'active' | 'trialing' | 'past_due' | 'unpaid' | 'canceled' | 'incomplete' | 'incomplete_expired'; function planForSubscription(status: SubStatus): 'pro' | 'starter' | null { if (status === 'active' || status === 'trialing') return 'pro'; if (status === 'unpaid' || status === 'incomplete_expired') return 'starter'; return null; // leave the current plan untouched } describe('checkout grant decision', () => { it('grants on a settled payment', () => { expect(shouldGrantPlan('paid')).toBe(true); }); it('grants when no payment was required (e.g. a 100% coupon)', () => { expect(shouldGrantPlan('no_payment_required')).toBe(true); }); it('withholds the plan while the payment is unsettled', () => { // The regression this guards: a delayed-payment method completing Checkout unpaid used to // grant 'lifetime' outright. expect(shouldGrantPlan('unpaid')).toBe(false); }); }); describe('subscription status mapping', () => { it('treats only active and trialing as paying', () => { expect(planForSubscription('active')).toBe('pro'); expect(planForSubscription('trialing')).toBe('pro'); }); it('does not upgrade on past_due, and does not downgrade mid-retry either', () => { // Stripe is still retrying the charge — flipping the plan in either direction here would // either hand out Pro for a failed renewal or cut off a customer whose retry succeeds. expect(planForSubscription('past_due')).toBeNull(); }); it('drops to starter on terminal non-payment states', () => { expect(planForSubscription('unpaid')).toBe('starter'); expect(planForSubscription('incomplete_expired')).toBe('starter'); }); it('leaves the plan alone for states that carry no payment signal', () => { expect(planForSubscription('incomplete')).toBeNull(); expect(planForSubscription('canceled')).toBeNull(); // handled by subscription.deleted instead }); });