feat(ep-commerce): server-cart shopper context + composable checkout#180
Open
feat(ep-commerce): server-cart shopper context + composable checkout#180
Conversation
Replace the stale shopper auth specs with server-cart architecture: - ShopperContext GlobalContext for Studio preview and checkout URL params - Server-route-based cart hooks (useCart, useAddItem, useRemoveItem, useUpdateItem) - httpOnly cookie management and X-Shopper-Context header utilities - 4-phase migration plan (context → reads → mutations → credential removal) All new code targets src/shopper-context/ in the EP commerce provider package. Consumer app (storefront) implements API routes using exported server utilities.
…ties ShopperContext GlobalContext provides override channel for cart identity (Studio preview, checkout URLs). Server utilities (resolveCartId, buildCartCookieHeader) enable httpOnly cookie-based cart management in consumer API routes without exposing EP credentials to the browser. - ShopperContext component with Symbol.for singleton pattern - useShopperContext hook, useShopperFetch with X-Shopper-Context header - Server-side resolve-cart-id (header > cookie > null priority) - Server-side cart-cookie builder (httpOnly, SameSite=Lax) - Plasmic GlobalContext registration - 4 test suites (22 assertions), all passing
…alization Server-route-based cart hooks replacing direct EP SDK calls: - useCart: SWR hook fetching GET /api/cart via useShopperFetch, cache key includes cartId for Studio preview refetch - useCheckoutCart: normalizes nested EP cart response into flat CheckoutCartData with formatted prices for Plasmic data binding - EPCheckoutCartSummary: accepts optional cartData prop for server-route mode (two-component pattern avoids hooks violation) - Design-time mock data (MOCK_SERVER_CART_DATA) for Studio preview - swr added as peerDependency (>=1.0.0)
Server-route-based mutation hooks that replace direct EP SDK calls from
the browser. All cart operations go through /api/cart/* server routes so
client_secret never reaches the browser.
- useAddItem: POST /api/cart/items with auto cart refetch
- useRemoveItem: DELETE /api/cart/items/{id} with URL-encoded IDs
- useUpdateItem: PUT /api/cart/items/{id} debounced at 500ms
- Defensive isEmpty check in useCart for missing items field
- Barrel exports updated with new hooks and AddItemInput type
…e, server promo routes P3-1: Add @deprecated JSDoc to old client-side cart hooks (use-cart, use-add-item, use-remove-item, use-update-item) and cart-cookie utilities, directing developers to the new server-route alternatives in shopper-context/. P3-2: Add serverCartMode boolean prop to CommerceProvider. When enabled with no clientId, renders children without EP SDK initialization — cart operations use server routes via ShopperContext instead. P3-3: Add useServerRoutes prop to EPPromoCodeInput. When enabled, promo apply/remove go through POST/DELETE /api/cart/promo server routes via useShopperFetch() instead of the client-side EP SDK. Refactored to two-component pattern (client/server inner components) to avoid conditional hook calls.
…ActionsProvider P3-4: Audited all getEPClient/useCommerce usage. All cart paths have server-route alternatives. Product/search/inventory/bundle hooks remain client-side (public data only, no client_secret exposed). P3-5: Created ServerCartActionsProvider that bridges shopper-context hooks (useAddItem, useRemoveItem, useUpdateItem) to Plasmic's global actions system. CommerceProvider now uses it when serverCartMode=true, giving designers access to addItem/updateItem/removeItem actions even without EP SDK initialization.
Root orchestrator for the composable checkout flow. Wraps useCheckout() and exposes complete checkout state via DataProvider + 9 refActions for Plasmic interaction wiring. Design-time preview with mock data for all 4 checkout steps (customer info, shipping, payment, confirmation). New files: - CheckoutContext.tsx — shared payment context (EPCheckoutProvider ↔ EPPaymentElements) - EPCheckoutProvider.tsx — component + registration metadata + 9 refActions - EPCheckoutProvider.test.tsx — 9 tests covering render, preview states, refActions Changes: - use-checkout.tsx — cartId optional (server resolves from cookie) - design-time-data.ts — composable checkout mock data for all steps - registerCheckout.tsx — register EPCheckoutProvider - composable/index.ts — barrel exports
…Totals (CC-P0-2..4) EPCheckoutStepIndicator: repeater over 4 steps with per-step DataProvider EPCheckoutButton: step-aware button with label/disabled/processing state EPOrderTotalsBreakdown: financial totals from checkout/cart context
…-1..3) EPCustomerInfoFields: headless firstName/lastName/email with validation EPShippingAddressFields: shipping address with postcode validation by country EPBillingAddressFields: billing address with shipping mirror mode
…mentElements (CC-P2-1..2) EPShippingMethodSelector: repeater fetching shipping rates with selectMethod action EPPaymentElements: Stripe Elements wrapper with lazy loading and design-time mock All 9 composable checkout components complete (CC-P0 through CC-P2).
Replace `import { type Foo }` / `export { type Foo }` with separate
`import type` / `export type` statements across shopper-context and
checkout composable files. tsdx bundles older TS/Babel that does not
support the inline type-modifier syntax (TS 4.5+).
Fix three-arg log.debug() calls in form field components to match the
two-arg `(message, data?)` logger signature.
Add ux/ep-commerce-components.md usage guidance covering all 25+
components across shopper context, cart hooks, server utilities, and
composable checkout.
…ssion model Implements the foundation layer for the checkout session architecture: Server-side: - CheckoutSession types, PaymentAdapter interface, SessionStore interface - CookieSessionStore with AES-256-GCM encryption (httpOnly cookie) - AdapterRegistry for payment gateway dispatch - 6 framework-agnostic route handlers (create/get/update/calculateShipping/pay/confirm) - Address translation utils (camelCase session ↔ snake_case EP API) - Cart hash utility for mutation detection (deterministic SHA-256) Client-side: - useCheckoutSession SWR hook with mutation helpers - EPCheckoutSessionProvider Plasmic component (DataProvider + 6 refActions) - PaymentRegistrationContext for gateway self-registration - Design-time mock data (collecting/paying/complete previewStates) Tests: 8 new test files, 157 tests covering cookie store crypto, adapter registry, address utils, cart hash determinism, and all handler paths (double-submit, cart mismatch 409, 3DS escalation, retry).
…+ components Server-side: - clover-types.ts: Clover API types (charge, 3DS, SDK interfaces) - clover-api.ts: chargeClover() + finalizeCloverPayment() + deriveIdempotencyKey() - clover-adapter.ts: PaymentAdapter with 3DS method/challenge/escalation handling, card declined detection, network retry with idempotency Client-side: - EPCloverPayment: SDK init, gateway registration, tokenization, 3DS state machine - EPCloverCardField: shared internal component for iframe field mounting - EPCloverCardNumber/Expiry/CVV/PostalCode: individual PCI-compliant card fields - clover-singleton.ts: SDK lazy-loader (sandbox/production environments) - clover-3ds-sdk.ts: 3DS SDK loader with 30s timeout on executePatch - clover-context.ts: React context for sharing Clover elements instance Integration: - adapters/index.ts: adapter factory exports - registerCheckout.tsx: session + Clover component registrations - session/index.ts: expanded with all Clover exports Tests: 3 files, 34 tests (adapter, component, field) Build fix: inline type imports separated for tsdx compatibility
…+ component
Stripe PaymentAdapter (server-side):
- createStripeAdapter({ secretKey }) factory — creates PaymentIntents with
automatic_payment_methods, metadata validation on confirm, cross-session
attack prevention via order_id check
- Lazy require('stripe') inside factory to avoid client-side bundling
EPStripePayment (client-side Plasmic component):
- Lazy-loads @stripe/stripe-js + @stripe/react-stripe-js
- Registers gateway "stripe" with PaymentRegistrationContext
- Reads session.payment.clientToken via useCheckoutSession (SWR deduplication)
- Renders Stripe Elements + PaymentElement when clientSecret available
- submitPayment refAction: stripe.confirmPayment() → /confirm
- DataProvider "stripePaymentData" with isReady, isProcessing, error
- Design-time preview states (auto, ready, processing, error)
Integration:
- stripe@^14.0.0 added to package.json dependencies
- Stripe adapter exported from adapters/index.ts and session/index.ts
- EPStripePayment registered in registerCheckout.tsx
Tests: 22 new tests (14 adapter + 8 component), 311 total passing
Remove legacy composable orchestration components (EPCheckoutProvider, EPCheckoutButton, EPCheckoutStepIndicator, EPPaymentElements, CheckoutContext) superseded by the session model. Adapt surviving composable components to read from the checkoutSession DataProvider: EPOrderTotalsBreakdown reads session.totals, EPShippingMethodSelector reads availableShippingRates and calls updateSession on selection, form field components read session address and customer info. Expand EPCheckoutSessionProvider DataProvider with updateSession and calculateShipping callbacks for child component access. All 291 tests pass across 19 suites.
…oute files Add ./server subpath export (src/server.ts + build-server.mjs) so consumer storefronts can import handler functions, CookieSessionStore, adapter registry, and payment adapters without pulling Node.js-only deps (crypto, stripe) into client bundles. Consumer storefront gets: - lib/checkout-config.ts: adapter registry with Clover + Stripe - lib/checkout-handler.ts: NextApiRequest → SessionRequest adapter - 5 route files: sessions/index, sessions/current, current/shipping, current/pay, current/confirm All 78/78 implementation plan items complete.
Created 4 test files that were planned but never implemented: - get-session.test.ts (A-10.4): 12 tests for handleGetSession - calculate-shipping.test.ts (A-10.6): 22 tests for handleCalculateShipping - EPCheckoutSessionProvider.test.tsx (A-10.9): 25 tests for the provider component - use-checkout-session.test.ts (A-10.10): 24 tests for the SWR hook Fixed jest.config.checkout.js testMatch — the pattern checkout/** did not match checkout-session/, so all 6 API endpoint checkout-session tests were silently excluded. Added explicit checkout-session/** pattern. Total: 27 suites, 471 tests pass.
…orepo compat Tests were passing under jest.config.checkout.js (which sets testEnvironment: jsdom) but failing under the monorepo root jest config (which defaults to node). Added @jest-environment jsdom docblock to each file that renders React components. Also set up global.fetch as a jest mock in use-checkout-session.test.ts. 72 suites, 1365 tests pass.
…on plan Add the 5 checkout session specification files that were used to guide the implementation. Condense IMPLEMENTATION_PLAN.md from 611 lines to 156 lines now that all 78 items are complete — preserves architectural decisions, key learnings, and deferred work items.
When a gateway charge fails and the session status resets to "open", the existing EP order from the first attempt is preserved in session.order. On retry, the handler now detects this and skips checkoutApi (avoiding a duplicate EP order), going straight to re-authorization on the existing order via paymentSetup. Adds 5 tests covering the retry path.
…EPCheckoutStepIndicator, EPCheckoutButton Add 3 new composable checkout components that wrap useCheckout() hook and expose state via DataProvider for designer binding: - EPCheckoutProvider: root orchestrator with 9 refActions (nextStep, previousStep, goToStep, submitCustomerInfo, submitShippingAddress, submitBillingAddress, selectShippingRate, submitPayment, reset), checkoutData DataProvider, and CheckoutInternalContext for EPPaymentElements clientSecret sharing - EPCheckoutStepIndicator: 4-step repeater with currentStep DataProvider per iteration (isActive, isCompleted, isFuture) - EPCheckoutButton: step-aware button with checkoutButtonData DataProvider (label, isDisabled, isProcessing) Also updates EPOrderTotalsBreakdown to read from checkoutData.summary (EPCheckoutProvider) as priority-1 source before falling back to checkoutSession.totals and checkoutCartData. All 3 components registered in registerCheckout.tsx in leaf-first order. 75 test suites, 1,406 tests passing. Build verified.
…s + EPPaymentElements Phase 2 (P1): - EPCustomerInfoFields: dual-source pre-population from checkoutData (composable) and checkoutSession (session) - EPShippingAddressFields: dual-source pre-population, useAccountAddress refAction (no-op until account addresses available) Phase 3 (P2): - EPPaymentElements: new Stripe Elements wrapper reading clientSecret from CheckoutInternalContext, paymentData DataProvider, lazy SDK loading, design-time mock form - EPShippingMethodSelector: added checkoutData awareness for composable flow, parentComponentName in registration meta All 76 test suites pass (1,413 tests).
…t form fields EPCustomerInfoFields now reads account profile (name + email) from any ancestor shopperContextData DataProvider as a third pre-population source after checkoutData and checkoutSession. EPShippingAddressFields useAccountAddress refAction now copies saved address fields from shopperContextData.addresses by ID, mapping EP snake_case fields (name, line_1, region, phone_number) to component camelCase state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
typesyntax with separateimport type/export typefor tsdx compatibility; fix logger call arity in form field componentsux/ep-commerce-components.mddocumenting all ~25 components with type signatures, DataProvider names, refActions, and API route examplesTest plan
yarn buildpasses inplasmicpkgs/commerce-providers/elastic-path/