11/**
22 * Stripe Checkout — Top-level enable_plan_immediately
33 *
4- * Feature under test (currently unimplemented — these tests are RED on purpose) :
4+ * Feature under test:
55 * - Top-level `enable_plan_immediately` on attach params (no longer nested under `invoice_mode`).
66 * - When set on a stripe_checkout flow, the customer_product is inserted as Active
77 * BEFORE the customer completes the Stripe-hosted checkout, with a new
@@ -26,6 +26,7 @@ import { TestFeature } from "@tests/setup/v2Features";
2626import { completeStripeCheckoutFormV2 as completeStripeCheckoutForm } from "@tests/utils/browserPool/completeStripeCheckoutFormV2" ;
2727import { items } from "@tests/utils/fixtures/items" ;
2828import { products } from "@tests/utils/fixtures/products" ;
29+ import { waitForStripeWebhook } from "@tests/utils/stripeUtils/waitForStripeWebhook" ;
2930import { initScenario , s } from "@tests/utils/testInitUtils/initScenario" ;
3031import chalk from "chalk" ;
3132import { eq } from "drizzle-orm" ;
@@ -156,11 +157,12 @@ test.concurrent(`${chalk.yellowBright("stripe-checkout enable_plan_immediately:
156157// TEST 2: Abandoned checkout — cusProduct cleaned up on session.expired
157158// ═══════════════════════════════════════════════════════════════════════════════
158159
159- // NOTE: Skipped until the implementation lands. Stripe checkout sessions auto-expire
160- // 24h after creation; we'll drive expiry via `s.advanceTestClock` once the handler
161- // for `checkout.session.expired` exists. The assertions are written out so flipping
162- // `test.skip` → `test.concurrent` is the only change needed.
163- test . skip ( `${ chalk . yellowBright ( "stripe-checkout enable_plan_immediately: expired session cleans up cusProduct" ) } ` , async ( ) => {
160+ // Expiry is driven by expiring the session on Stripe, which emits
161+ // `checkout.session.expired`. The API assertion at the end is the regression
162+ // check for the subject cache: the handler expires the row in Postgres, and the
163+ // webhook refresh middleware must drop the cached subject so `customers.get`
164+ // stops reporting the plan as active.
165+ test . concurrent ( `${ chalk . yellowBright ( "stripe-checkout enable_plan_immediately: expired session cleans up cusProduct" ) } ` , async ( ) => {
164166 const customerId = "stripe-checkout-eppi-expired" ;
165167
166168 const prepaidMessagesItem = items . prepaidMessages ( {
@@ -197,6 +199,9 @@ test.skip(`${chalk.yellowBright("stripe-checkout enable_plan_immediately: expire
197199 const result = await autumnV1 . billing . attach ( attachParams ) ;
198200 expect ( result . payment_url ) . toBeDefined ( ) ;
199201
202+ const checkoutSessionId = parseCheckoutSessionId ( result . payment_url ! ) ;
203+ expect ( checkoutSessionId ) . toBeTruthy ( ) ;
204+
200205 // Sanity: cusProduct exists Active before expiry.
201206 const before = await CusProductService . list ( {
202207 db : ctx . db ,
@@ -205,11 +210,25 @@ test.skip(`${chalk.yellowBright("stripe-checkout enable_plan_immediately: expire
205210 } ) ;
206211 expect ( before . some ( ( cp ) => cp . product . id === pro . id ) ) . toBe ( true ) ;
207212
208- // 2. Drive past the Stripe session expiry (sessions auto-expire after 24h).
209- // TODO: replace with the proper test-clock advance helper once we wire up
210- // `checkout.session.expired` simulation alongside the implementation.
211- // For now this test is `.skip`'d so the typed shape doesn't have to be exact.
212- void s ;
213+ // 2. Abandon the checkout: expire the session on Stripe, which emits
214+ // `checkout.session.expired`. Wait on the cache-backed API view, not the
215+ // DB row: the handler expires the row first and the refresh middleware
216+ // drops the cached subject after it returns, so a DB-only predicate can
217+ // resolve before `customers.get` stops reporting the plan.
218+ await ctx . stripeCli . checkout . sessions . expire ( checkoutSessionId ! ) ;
219+
220+ const isProActiveInApi = async ( ) => {
221+ const customer = await autumnV1 . customers . get < ApiCustomerV3 > ( customerId ) ;
222+ return ( customer . products ?? [ ] ) . some ( ( product ) => product . id === pro . id ) ;
223+ } ;
224+
225+ await waitForStripeWebhook ( {
226+ stripeCli : ctx . stripeCli ,
227+ env : ctx . env ,
228+ types : [ "checkout.session.expired" ] ,
229+ objectId : checkoutSessionId ! ,
230+ until : async ( ) => ! ( await isProActiveInApi ( ) ) ,
231+ } ) ;
213232
214233 // 3. After expiry: cusProduct should no longer be Active.
215234 const after = await CusProductService . list ( {
0 commit comments