Skip to main content

Checkout and Payments Feature Context

Source: .agents/references/features/checkout-and-payments.md

Content

Checkout and Payments Feature Context

Purpose

Checkout and payments cover the flows where visitors or members buy products, subscriptions, memberships, event tickets, sessions, files, and physical goods. This domain is high-risk because it touches pricing, discounts, taxes/fees, payment methods, redirects, analytics, and third-party providers such as Stripe, PayPal, Ebanx, and embedded checkout surfaces.

User-Facing Workflows

  • Buyers enter checkout from community/product pages, checkout widget pages, physical product pages, plan/subscription pages, or magic checkout flows.
  • Users select payment methods, apply discount codes, review pricing/fees, submit payment, and land on success or authentication/3DS redirects.
  • Product-specific flows can include shipping, location availability, variants, inventory, membership tier selection, affiliate codes, and abandoned checkout recovery.
  • Failed service calls should show existing error toast behavior and reset loading or payment state.
  • Payment-method fetch results use the presence of the error field as the failure discriminant; an empty error message is still a handled service failure and must not be reported as a malformed successful response.
  • Checkout UI must be robust on mobile because footers, summaries, and payment forms are often sticky or narrow.

Key Entrypoints

  • Routes:
    • src/pages/checkout-global/
    • src/pages/checkout-widget.page.js
    • src/pages/purchase-domain/
    • src/pages/payment-authentication/
    • src/pages/payment-method/
    • src/pages/ebanx-method-update/
    • src/pages/ebanx3ds/
    • src/pages/[communityLink]/physical-products/[productSlug]/
    • src/pages/[communityLink]/join-membership/[tierId].page.tsx
  • Components/features:
    • src/features/CommonCheckoutComponents/
    • src/features/MagicAdsCheckout/
    • src/features/MagicContentCheckout/
    • src/features/AbandonedCheckout/
    • src/features/Affiliates/
    • src/pages/[communityLink]/physical-products/components/checkout/
    • src/components/common/CheckoutPartials/
  • Hooks:
    • src/hooks/usePayment/
    • src/hooks/useFeeCalculation.ts
    • src/hooks/useEntityPrice.ts
    • src/hooks/useEventBulkPurchase.js
    • src/hooks/useInitCommunitySignup.js
    • src/hooks/useGetReferralDetails.js
  • Services:
    • src/services/paymentService.js
    • src/services/changeMethodService.js
    • src/services/customDomain/customDomainService.ts
    • src/services/unifiedProduct/unifiedProductServices.ts
    • src/services/referral/referralService.ts
    • src/services/nasPlan/nasPlanService.js
  • Utilities:
    • src/utility/checkoutConstants.js
    • src/utility/checkoutUtils.js
    • src/utility/productUtils.ts
    • src/utility/priceValidationUtil.ts
    • src/utility/getSignatureForPresigned.ts

Data Flow and Service Boundaries

  • Service modules under src/services/ generally return { data, error }; callers should destructure that shape and check if (error) instead of wrapping service calls in try/catch.
  • Pricing is often composed from product state, selected variants/items, discounts, fees, currency, buyer country, and payment provider. Inspect the existing hook/service chain before changing a displayed amount.
  • Multiple-ticket event checkout resolves pricing from the selected ticket's public pricingConfig, not the legacy event-level config. Fixed tickets use their ticket-level amount; flexible tickets use their buyer-entered amount with ticket-level minimum and suggested guidance. The selected ticket ID and buyer-entered flexible amount continue through pricing, discount, and signup requests for backend validation and calculation. Until the pricing-read response reflects that selected ticket, the checkout summary derives the unit price, subtotal, and total delta from the selected ticket so it never displays the legacy event price.
  • When a selected flexible ticket first supplies its suggested amount, refresh event pricing with that selected amount before presenting backend-calculated fees and totals.
  • Treat a selected event ticket's localized amount and localized currency as one pair. Use the localized pair only when both fields are usable; if a paid ticket has a zero/missing localized amount or the localized currency is absent, fall back to both the selected ticket's base amount and base currency.
  • Multiple-ticket checkout URLs carry the selected ticket ID, not a duplicated ticket name. Resolve the checkout row label from the matching ticket returned by the public event API.
  • Resolve event ticket metadata from the canonical community and event slugs returned by the sourceInfoOrigin ID-based checkout response. linkClicked is navigation/tracking input and must not be used to resolve event identity. If the canonical response does not provide a slug, keep ticket lookup unavailable rather than querying a potentially unrelated event.
  • /subscribe-plan must source plan pricing, payment methods, and Stripe metadata from the same fetchPlatformPlans result. Mixing page-local pricing with global plan-context payment configuration can pair a regional Stripe price with the wrong Stripe account.
  • Payment redirects and authentication routes may depend on query params and provider callback shape. Guard missing params explicitly and avoid leaking sensitive values in logs.
  • Event custom thank-you redirects are eligible only after terminal checkout completion. Treat a submitted no-payment enrollment with no pending verification, an initial signup that actually registers the event, and a verified provider callback as terminal success. Pending verification, pending approval, malformed responses, and already-owned/no-op states must keep the normal completion flow.
  • Checkout signupToken and signupId identify one transaction. Preserve them across intermediate payment steps, then invalidate both on terminal completion or explicit checkout reset so a later purchase starts with a fresh /signup transaction and newly generated request signature/nonce.
  • A verified one-time entity payment completes from the existing checkout state and verify response; it must not call /signup again after /confirm. Post-verification /signup refresh is reserved for a genuinely non-terminal multi-step checkout, such as moving from a paid membership step to another payable entity.
  • Custom-domain checkout enters /purchase-domain from /portal/domain with domain, registrationYear, and backTo query params. src/pages/purchase-domain/utils.ts normalizes the domain, clamps registration years to the supported range, and rejects absolute/external backTo values before routing.
  • Domain checkout uses src/services/customDomain/customDomainService.ts rather than generic payment services. The provided backend flow is: GET /communities/:communityId/custom-domains/pricing, POST /communities/:communityId/custom-domains/signup, POST /communities/:communityId/custom-domains/signup/confirm, and POST /communities/:communityId/custom-domains/verify-payment.
  • Domain signup payloads send the selected domain, periodYears, and nested registrant contact fields such as name, phoneNumber, address, unitNo, postalCode, country, subdivisionName, city, and saveAddress. Avoid inventing payment-provider fields in the signup draft.
  • The custom-domain success screen is entered from verified payment status or explicit success query state and shows the domain as Connecting; the actual domain activation continues through the custom domain status flow.
  • Checkout executes on NAS, not on white-label custom domains. Client-generated /checkout-global... links must pass through resolveNavigationHref before a hard navigation so the browser receives the final NAS checkout URL and cross-host auth handoff directly. Centralized middleware remains the server-navigation fallback.
  • Post-checkout public/community/product destinations should prefer the community custom domain when available.
  • Affiliate/referral and abandoned checkout flows may write/read browser storage through existing utility wrappers. Do not add direct localStorage or sessionStorage access.
  • Physical product checkout adds shipping, variants, and location constraints. Start with src/pages/[communityLink]/physical-products/ before touching generic checkout helpers.

Conventions and Gotchas

  • Treat payment amount, currency, product ID, checkout ID, user ID, and redirect params as hostile input. Validate at boundaries and avoid unsafe interpolation.
  • Keep loading state deterministic. If an error path returns early, reset the same state the success path would leave stable.
  • Treat the latest signup response as authoritative for payment ordering. When paymentStatus.requirePayment is true, provider confirmation must succeed before verification. ZeroLink flexible-pricing state must not enter the no-payment verification shortcut.
  • Post-verification checkout refreshes must carry the latest signup response token explicitly. React render state can remain stale throughout the async payment interaction and must not recreate a paid signup. Keep that explicit token in the shared checkout state/ref while the refresh request is pending.
  • Initial checkout validation waits for route query hydration, attached entity ID and checkout-step initialization, and entity API settlement. An already-enrolled member buying an attached event or other entity must not be redirected as a duplicate community enrollment from transient entity state.
  • /subscribe-plan paid confirmation uses a two-phase signup token flow: the final signup request must include the preliminary token. If the prefetched token is unavailable, payment submit must complete a preliminary signup before the final signup and pass only the final token to confirmation.
  • Do not add broad try/catch around service APIs. Use the repo service-response pattern.
  • Avoid duplicating price formatting. Search existing price utilities and product card display helpers first.
  • Preserve analytics action IDs and provider-specific event timing unless the task explicitly changes them.
  • Do not inline styles for provider widgets unless the third-party API requires dynamic values.
  • For discount or pricing copy, use direct t() keys and aligned placeholders.
  • Event-ticket discount submission calls the existing community discount creation API immediately in edit mode; create-event ticket discounts remain local drafts until event creation.
  • A persisted event-ticket discount request scopes its linked event entity with the selected ticketTypeObjectId; the event-only linked entity must not be used for multi-ticket discounts.
  • Persisted event-ticket discount disable is an immediate discount mutation (isActive: false), not a deferred event-save field; only a successful mutation removes the code from the ticket UI.
  • .agents/rules/no-try-catch-service-api.mdc
  • .agents/rules/web-security.mdc
  • .agents/rules/no-inline-styles.mdc
  • .agents/rules/use-classnames-for-conditional-classes.mdc
  • .agents/rules/direct-translation-and-component-files.mdc
  • .agents/references/coding-standard/useeffect-guidance.md
  • .agents/references/features/custom-domain-management.md

Useful Graph Queries and Fallback Searches

Use the terms below with search_graph/search_code, then run trace_path on the central checkout or payment symbols. Run these shell searches only when the graph is insufficient or the target is excluded.

rtk rg "checkout|payment|discount|stripe|paypal|ebanx|3ds|fee|price" src/pages src/features src/hooks src/services src/utility
rtk rg "showErrorToast|paymentService|selectedPaymentMethod|discountCode" src
rtk rg "purchase-domain|signupCustomDomain|verifyCustomDomainPayment|registrationYear" src/pages src/services
rtk find src/features/CommonCheckoutComponents -maxdepth 4 -type f

Update Triggers

Update this reference when payment service contracts, checkout routes, pricing display, provider redirects, discount handling, or physical product checkout behavior changes.