Skip to content

[Slice 2] Authenticated account checkout #322

@field123

Description

@field123

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:

  1. Customer signs in via the existing better-auth flow.
  2. They have an item in their cart (no subscription items).
  3. They navigate to /checkout. The session auto-creates as in Slice 1.
  4. They fill the form, select shipping, enter card details (non-3DS), click Place Order.
  5. 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.
  6. Single-shot flow proceeds as in Slice 1 — createCartPaymentIntentcheckoutApi (account-scoped) → confirmOrder → cart cleanup.
  7. Cart cleanup runs deleteAccountCartAssociation before deleteACart because the cart was account-associated.
  8. 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

  • handlePay builds checkoutApi body with account: { id, member_id } when accountToken is on the request, with customer: { name, email } otherwise.
  • Checkout Body Builder deep module unit-tests cover both guest and account shapes including all EP-required empty-string defaults.
  • Cart Cleanup Operation deep module invokes deleteAccountCartAssociation before deleteACart when the cart was account-associated, skips it for guest cart cleanup.
  • Cart Cleanup Operation continues with cart deletion even if dissociation fails (failure is logged).
  • Integration test: account user with non-subscription cart completes checkout; assertions verify account body shape was sent to EP and association was dissociated before cart delete.
  • Integration test: guest user (no accountToken) completes checkout (regression check that Slice 1 path still works).
  • Manual end-to-end: signed-in user completes a checkout with 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:

  • User story 22

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