Skip to content

[Slice 3] Subscription items force account sign-up #323

@field123

Description

@field123

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:

  1. Anonymous shopper adds a subscription product to their cart (no account yet).
  2. They navigate to /checkout. Session auto-creates, form renders.
  3. They fill the form and click "Place Order".
  4. Server handlePay detects subscription items in the cart and no accountToken on the request. Returns HTTP 400 with { success: false, error: { code: "ACCOUNT_REQUIRED" } }.
  5. Plasmic page reads $ctx.checkoutSession.error.code and renders a "Sign up to continue" CTA instead of the form.
  6. The CTA links to /register?redirect=/checkout.
  7. User registers, lands back on /checkout. Their better-auth session now has an accountToken.
  8. 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

  • Subscription Cart Detector deep module returns true for carts containing items of type subscription_item and false otherwise; covered by a table test.
  • handlePay rejects with HTTP 400 { success: false, error: { code: "ACCOUNT_REQUIRED" } } when subscription items are present and no accountToken is on the request, before any EP write call.
  • No EP order is created on the rejected path.
  • When accountToken is present, subscription items proceed through Slice 2's account checkout path.
  • The error response includes the typed code ACCOUNT_REQUIRED (string-stable contract for designer binding).
  • Documentation (COMPONENTS.md or equivalent) lists ACCOUNT_REQUIRED as a session error code with its meaning and the recommended designer pattern.
  • Integration test: subscription cart + anonymous shopper → 400 with ACCOUNT_REQUIRED.
  • Integration test: subscription cart + signed-in account → 200 success (regression of Slice 2).
  • Integration test: non-subscription cart + anonymous shopper → 200 success (regression of Slice 1).
  • Manual end-to-end: anonymous shopper with subscription in cart sees the Plasmic "Sign up to continue" CTA, registers, returns to checkout, completes purchase, subscription is visible in their EP account.

Blocked by

User stories addressed

Reference by number from the parent PRD:

  • User story 8
  • User story 21

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions