Parent PRD
#317
What to build
A signed-in account customer completes a checkout end-to-end. Same Plasmic page, same Stripe component, same placeOrder flow as Slice 1 — but the server detects the better-auth session's accountToken and dispatches the checkoutApi body in account: { id, member_id } form instead of the guest customer: { name, email } form. Cart cleanup also dissociates the cart from the account before deleting it.
The vertical user journey:
- Customer signs in via the existing better-auth flow.
- They have an item in their cart (no subscription items).
- They navigate to /checkout. The session auto-creates as in Slice 1.
- They fill the form, select shipping, enter card details (non-3DS), click Place Order.
- Server's
handlePay detects the accountToken, looks up account ID and member ID via existing EP shopper SDK calls, builds the checkoutApi body with account: ... shape.
- Single-shot flow proceeds as in Slice 1 —
createCartPaymentIntent → checkoutApi (account-scoped) → confirmOrder → cart cleanup.
- Cart cleanup runs
deleteAccountCartAssociation before deleteACart because the cart was account-associated.
- Customer sees order confirmation; their cart is empty; the order is visible in their EP account history.
This slice does not introduce any new routes, new Plasmic components, or new env vars — it adds branching inside handlePay and inside the cart cleanup operation, plus the test coverage to prove both shapes work.
Cuts through every layer:
- Package — handler.
handlePay reads accountToken from the request (resolved by the route adapter from the better-auth session). When present, fetches account + member via the EP shopper SDK, switches body builder to account form. When absent, retains Slice 1's guest body.
- Package — body builder.
Checkout Body Builder deep module gains an account branch alongside its existing guest branch. EP's mandatory empty-string fields (company_name, county, instructions) and snake_case translation handled identically to Slice 1.
- Package — cart cleanup.
Cart Cleanup Operation deep module gains a deleteAccountCartAssociation step that runs only when an account was associated with the cart. Failure of dissociation is logged but does not fail the response.
- Host — context. Route adapter resolves
accountToken from the better-auth session and threads it into the SessionHandlerContext for handlers that need it.
- Tests. Unit tests extend the body builder and cart cleanup deep modules to cover the account branch. Integration test for
/pay covers the account happy path.
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:
Parent PRD
#317
What to build
A signed-in account customer completes a checkout end-to-end. Same Plasmic page, same Stripe component, same
placeOrderflow as Slice 1 — but the server detects the better-auth session'saccountTokenand dispatches thecheckoutApibody inaccount: { id, member_id }form instead of the guestcustomer: { name, email }form. Cart cleanup also dissociates the cart from the account before deleting it.The vertical user journey:
handlePaydetects theaccountToken, looks up account ID and member ID via existing EP shopper SDK calls, builds thecheckoutApibody withaccount: ...shape.createCartPaymentIntent→checkoutApi(account-scoped) →confirmOrder→ cart cleanup.deleteAccountCartAssociationbeforedeleteACartbecause the cart was account-associated.This slice does not introduce any new routes, new Plasmic components, or new env vars — it adds branching inside
handlePayand inside the cart cleanup operation, plus the test coverage to prove both shapes work.Cuts through every layer:
handlePayreadsaccountTokenfrom the request (resolved by the route adapter from the better-auth session). When present, fetches account + member via the EP shopper SDK, switches body builder to account form. When absent, retains Slice 1's guest body.Checkout Body Builderdeep module gains anaccountbranch alongside its existing guest branch. EP's mandatory empty-string fields (company_name,county,instructions) and snake_case translation handled identically to Slice 1.Cart Cleanup Operationdeep module gains adeleteAccountCartAssociationstep that runs only when an account was associated with the cart. Failure of dissociation is logged but does not fail the response.accountTokenfrom the better-auth session and threads it into theSessionHandlerContextfor handlers that need it./paycovers the account happy path.See parent PRD §Implementation Decisions → "Account vs guest checkout" for full detail.
Acceptance criteria
handlePaybuildscheckoutApibody withaccount: { id, member_id }whenaccountTokenis on the request, withcustomer: { name, email }otherwise.Checkout Body Builderdeep module unit-tests cover both guest and account shapes including all EP-required empty-string defaults.Cart Cleanup Operationdeep module invokesdeleteAccountCartAssociationbeforedeleteACartwhen the cart was account-associated, skips it for guest cart cleanup.Cart Cleanup Operationcontinues with cart deletion even if dissociation fails (failure is logged).accountbody shape was sent to EP and association was dissociated before cart delete.accountToken) completes checkout (regression check that Slice 1 path still works).4242 4242 4242 4242; the resulting EP order is associated with their account; their cart is empty; the order shows in their EP account order history.Blocked by
User stories addressed
Reference by number from the parent PRD: