Parent PRD
#317
What to build
When a cart contains subscription items, an unauthenticated shopper is gated to sign up before checkout can complete. After they sign up or sign in, the same cart proceeds through the same placeOrder interaction, now using Slice 2's account path.
The vertical user journey:
- Anonymous shopper adds a subscription product to their cart (no account yet).
- They navigate to /checkout. Session auto-creates, form renders.
- They fill the form and click "Place Order".
- Server
handlePay detects subscription items in the cart and no accountToken on the request. Returns HTTP 400 with { success: false, error: { code: "ACCOUNT_REQUIRED" } }.
- Plasmic page reads
$ctx.checkoutSession.error.code and renders a "Sign up to continue" CTA instead of the form.
- The CTA links to
/register?redirect=/checkout.
- User registers, lands back on /checkout. Their better-auth session now has an
accountToken.
- They click "Place Order" again. The same cart, now with
accountToken, succeeds via Slice 2's path. Subscription is created against their new account.
Cuts through every layer:
- Package — detector. New
Subscription Cart Detector deep module: hasSubscriptionItems(cartItems): boolean. Pure function; covered by table tests across realistic EP cart shapes including subscription_item and any related types.
- Package — handler.
handlePay invokes the detector early. If subscription items are present and no accountToken, short-circuits with ACCOUNT_REQUIRED before any EP API calls. The error response shape is documented for designer binding.
- Designer experience. No new components, no new props. Designer binds conditional UI to
$ctx.checkoutSession.error.code === "ACCOUNT_REQUIRED". Documentation update in COMPONENTS.md (or successor) documents the error code as the contract.
- Tests. Unit tests for the detector across cart shapes. Handler tests cover all four (guest×subscription, account×subscription, guest×no-sub, account×no-sub) combinations.
See parent PRD §Implementation Decisions → "Account vs guest checkout" for full detail.
Acceptance criteria
Blocked by
User stories addressed
Reference by number from the parent PRD:
- User story 8
- User story 21
Parent PRD
#317
What to build
When a cart contains subscription items, an unauthenticated shopper is gated to sign up before checkout can complete. After they sign up or sign in, the same cart proceeds through the same
placeOrderinteraction, now using Slice 2's account path.The vertical user journey:
handlePaydetects subscription items in the cart and noaccountTokenon the request. Returns HTTP 400 with{ success: false, error: { code: "ACCOUNT_REQUIRED" } }.$ctx.checkoutSession.error.codeand renders a "Sign up to continue" CTA instead of the form./register?redirect=/checkout.accountToken.accountToken, succeeds via Slice 2's path. Subscription is created against their new account.Cuts through every layer:
Subscription Cart Detectordeep module:hasSubscriptionItems(cartItems): boolean. Pure function; covered by table tests across realistic EP cart shapes includingsubscription_itemand any related types.handlePayinvokes the detector early. If subscription items are present and noaccountToken, short-circuits withACCOUNT_REQUIREDbefore any EP API calls. The error response shape is documented for designer binding.$ctx.checkoutSession.error.code === "ACCOUNT_REQUIRED". Documentation update in COMPONENTS.md (or successor) documents the error code as the contract.See parent PRD §Implementation Decisions → "Account vs guest checkout" for full detail.
Acceptance criteria
Subscription Cart Detectordeep module returnstruefor carts containing items of typesubscription_itemandfalseotherwise; covered by a table test.handlePayrejects with HTTP 400{ success: false, error: { code: "ACCOUNT_REQUIRED" } }when subscription items are present and noaccountTokenis on the request, before any EP write call.accountTokenis present, subscription items proceed through Slice 2's account checkout path.ACCOUNT_REQUIRED(string-stable contract for designer binding).ACCOUNT_REQUIREDas a session error code with its meaning and the recommended designer pattern.ACCOUNT_REQUIRED.Blocked by
User stories addressed
Reference by number from the parent PRD: