Add Express Checkout with Apple Pay / Google Pay - #34
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds a client-side Stripe Express Checkout UI and integration into cart UI, centralizes Stripe initialization, introduces server-side quick/express checkout actions and utilities, and adds HTTPS development docs/config and environment example updates. Changes
Sequence DiagramsequenceDiagram
participant Browser as Browser
participant UI as ExpressCheckoutButton
participant Stripe as Stripe SDK
participant Server as Server Actions
participant Spree as Spree Backend
Browser->>UI: Click express checkout
UI->>Stripe: Initialize Elements / present wallet
Stripe-->>Browser: Wallet UI (Apple/Google/Link)
Browser->>Stripe: Select payment method & shipping address
Stripe->>UI: Address & paymentMethod token
UI->>Server: expressCheckoutResolveShipping(orderId, address)
Server->>Spree: updateAddresses + advance
Spree-->>Server: order with shipments/rates
Server-->>UI: shipping options
UI->>Server: expressCheckoutSelectRates(orderId, selections)
Server->>Spree: selectShippingRate(s)
Spree-->>Server: updated totals
Server-->>UI: updated order totals
UI->>Server: expressCheckoutPreparePayment(orderId, fullAddress)
Server->>Spree: update full address & email
Spree-->>Server: ready order
Server-->>UI: ready for payment
UI->>Server: expressCheckoutCreateSession(orderId, paymentMethodId, stripePaymentMethodId)
Server->>Spree: createCheckoutPaymentSession
Spree-->>Server: sessionId
Server-->>UI: sessionId
UI->>Stripe: confirm payment with sessionId
Stripe-->>UI: payment confirmed
UI->>Server: expressCheckoutFinalize(orderId, sessionId)
Server->>Spree: complete payment & order
Spree-->>Server: success
Server-->>UI: complete
UI->>Browser: Navigate to order confirmation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (7)
src/lib/utils/express-checkout.ts (1)
17-34: Strengthen typing for checkout helpers (input + return types).Please replace
Record<string, unknown>/casts with concrete SDK types and add explicit return types on exported helpers to prevent silent shape drift.💡 Example direction
+import type { StoreOrder } from "@spree/sdk"; -export function buildLineItems(order: Record<string, unknown>) { +export function buildLineItems(order: StoreOrder): Array<{ name: string; amount: number }> { const items: Array<{ name: string; amount: number }> = []; - const itemTotal = toCents(order.item_total as string); + const itemTotal = toCents(order.item_total); items.push({ name: "Subtotal", amount: itemTotal }); - const promoTotal = toCents(order.promo_total as string); + const promoTotal = toCents(order.promo_total); if (promoTotal < 0) { items.push({ name: "Discount", amount: promoTotal }); } - const additionalTaxTotal = toCents(order.additional_tax_total as string); + const additionalTaxTotal = toCents(order.additional_tax_total); if (additionalTaxTotal > 0) { items.push({ name: "Tax", amount: additionalTaxTotal }); } return items; }As per coding guidelines, "Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type."
Based on learnings, "Import and use Spree SDK types (StoreProduct, StoreVariant, StoreOrder, StoreLineItem, PaginatedResponse) for type safety."Also applies to: 52-75
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/express-checkout.ts` around lines 17 - 34, The exported helper buildLineItems uses loose typing (Record<string, unknown>) and runtime casts which can hide shape drift; replace the input type with the appropriate Spree SDK type (e.g., StoreOrder) and give the function an explicit return type (e.g., StoreLineItem[] or Array<{ name: string; amount: number }>), remove all "as" casts for order.item_total / promo_total / additional_tax_total and instead access those fields with proper types and safe checks (optional chaining / default values) so the compiler enforces the shape; apply the same pattern to the other helpers mentioned (lines ~52-75) by importing and using StoreProduct, StoreVariant, PaginatedResponse, etc., and returning the concrete SDK types.src/lib/data/express-checkout-flow.ts (1)
16-16: Use absolute import foractionResultThis file uses
./utils; prefer@/lib/...to match the project import convention.As per coding guidelines:
**/*.{ts,tsx}: Use absolute imports with @ alias (e.g.,@/components/...,@/lib/...) instead of relative imports🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/data/express-checkout-flow.ts` at line 16, The import for actionResult in express-checkout-flow.ts uses a relative path ("./utils"); update it to the project's absolute alias (use "@/lib/data/utils" or the correct `@/lib` path where utils.ts lives) so the import reads with the @ alias and matches project convention; ensure the imported symbol name actionResult remains unchanged and that the file still builds after switching the import.src/components/cart/CartDrawer.tsx (1)
6-8: Consider dynamically importingExpressCheckoutButtonto keep Stripe out of the always-loaded drawer bundleCartDrawer is typically present site-wide; a static import can pull Stripe Elements code into a very hot client bundle. A
next/dynamicsplit (withssr: false+ small loading fallback) would likely help.As per coding guidelines:
src/components/**/*.{ts,tsx}: Use dynamic imports with loading fallback for non-critical components to improve code splittingAlso applies to: 274-282
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/cart/CartDrawer.tsx` around lines 6 - 8, Replace the static import of ExpressCheckoutButton in CartDrawer with a dynamic import using next/dynamic (ssr: false) and a small loading fallback so Stripe Elements aren't bundled into the always-loaded drawer; locate the ExpressCheckoutButton reference in the CartDrawer component and swap the import for something like dynamic(() => import('@/components/checkout').then(m => m.ExpressCheckoutButton), { ssr: false, loading: () => <SmallLoadingFallback/> }), and apply the same change to the other ExpressCheckoutButton usage in this file (the block referenced around the second occurrence).src/app/[country]/[locale]/(storefront)/cart/page.tsx (1)
8-9: Optional: code-splitExpressCheckoutButtonon the cart page tooLess critical than the drawer (route-level bundle vs persistent bundle), but still a reasonable win if Stripe adds noticeable weight.
As per coding guidelines:
src/components/**/*.{ts,tsx}: Use dynamic imports with loading fallback for non-critical components to improve code splittingAlso applies to: 180-189
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[country]/[locale]/(storefront)/cart/page.tsx around lines 8 - 9, Replace the static import of ExpressCheckoutButton with a dynamic import to code-split the cart page: remove the top-level import of ExpressCheckoutButton in page.tsx and use Next.js dynamic import (dynamic(() => import(".../ExpressCheckoutButton"), { ssr: false, loading: () => <YourLoadingFallback/> })) where the component is rendered; keep ShoppingBagIcon as a normal import. Apply the same change for the other occurrences noted (around the existing ExpressCheckoutButton usage at lines ~180-189) so the non-critical Stripe UI is loaded asynchronously with a simple loading fallback component.src/components/checkout/ExpressCheckoutButton.tsx (2)
313-321:expressCheckoutFinalizeis treated as best-effort, but you’ll never see business failures viacatch
expressCheckoutFinalizeis anactionResult-style API (it returns{ success: false, error }instead of throwing). So the currenttry/catchonly catches transport/runtime failures, not a “failed finalize” response. Consider checking the returned{ success }and logging/telemetry at least (even if you still proceed).As per coding guidelines:
**/*.ts{,x}: Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/ExpressCheckoutButton.tsx` around lines 313 - 321, The try/catch around expressCheckoutFinalize only catches transport/runtime errors but not business failures because expressCheckoutFinalize returns an actionResult object; update the code that calls expressCheckoutFinalize(orderId, sessionId) to await the result, explicitly check the returned { success, error } (or typed ActionResult) and log or send telemetry when success is false (include orderId/sessionId/context), then proceed with router.push(`${basePath}/order-placed/${orderId}`) and onComplete() as before; also ensure expressCheckoutFinalize has an explicit TypeScript return type (e.g., ActionResult) instead of any so the caller can rely on typing.
36-41: Tighten types to reduceRecord<string, unknown>/ assertion chains (and consider exporting the props type)There are several cascading assertions (e.g.,
cart as unknown as Record<string, unknown>,result.order as Record<string, unknown>, inlineshipmentscasting). This makes it easy to miss a shape mismatch (especially aroundshipments.shipping_rates).Suggestions:
- Define a small “order-like” type with only the fields you read (
item_total,promo_total,additional_tax_total,shipments) and use it across client + server action return types.- Export
ExpressCheckoutButtonPropsif you want to enable typed dynamic imports from the cart drawer/page without falling back toany.As per coding guidelines:
**/*.ts{,x}: Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' typeAlso applies to: 87-91, 110-120, 239-266, 288-295, 401-439
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/ExpressCheckoutButton.tsx` around lines 36 - 41, The props and many inline casts are too loose—define a narrow exported order-like type (e.g., export type OrderSummary = { item_total: number; promo_total?: number; additional_tax_total?: number; shipments?: { shipping_rates?: Array<{ id: string; price: number; }>; }[] } ) that captures only the fields read, then replace the ad-hoc casts (e.g., the uses of cart as unknown as Record<string, unknown>, result.order as Record<string, unknown>, and inline shipments casting) with that type (use OrderSummary | undefined where appropriate), update any client/server action return types to return OrderSummary for the order shape, and export ExpressCheckoutButtonProps so dynamic imports keep strong typing; adjust the functions/methods that reference shipments.shipping_rates to use the typed shipments shape instead of assertion chains.src/lib/data/quick-checkout.ts (1)
5-8: Avoidascasts on address literals; use typed variables /satisfiesRight now you’re using
as QuickCheckoutAddressParamsto bypass excess-property checks. Prefer a typed variable (orsatisfies) so TypeScript still validates the shape (and to follow the repo guideline).Proposed change (typed variables, no `as`)
-import { actionResult } from "./utils"; +import { actionResult } from "@/lib/data/utils"; @@ export async function quickCheckoutUpdateAddress( orderId: string, address: QuickCheckoutPartialAddress, ) { return actionResult(async () => { + const ship_address: QuickCheckoutAddressParams = { + firstname: address.firstname || "Express", + lastname: address.lastname || "Checkout", + address1: "TBD", + ...address, + quick_checkout: true, + }; const order = await updateAddresses(orderId, { - ship_address: { - firstname: address.firstname || "Express", - lastname: address.lastname || "Checkout", - address1: "TBD", - ...address, - quick_checkout: true, - } as QuickCheckoutAddressParams, + ship_address, }); return { order }; }, "Failed to update address for quick checkout"); } @@ export async function quickCheckoutUpdateFullAddress( @@ ) { return actionResult(async () => { + const ship_address: QuickCheckoutAddressParams = { + ...params.shipAddress, + quick_checkout: true, + }; + const bill_address: QuickCheckoutAddressParams = { + ...params.billAddress, + quick_checkout: true, + }; const order = await updateAddresses(orderId, { email: params.email, - ship_address: { - ...params.shipAddress, - quick_checkout: true, - } as QuickCheckoutAddressParams, - bill_address: { - ...params.billAddress, - quick_checkout: true, - } as QuickCheckoutAddressParams, + ship_address, + bill_address, }); return { order }; }, "Failed to update full address"); }As per coding guidelines:
**/*.ts{,x}: Use 'satisfies' operator for type checking object literals instead of casting with 'as'Also applies to: 19-35, 44-66
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/data/quick-checkout.ts` around lines 5 - 8, You’re bypassing TS excess-property checks by casting address object literals to QuickCheckoutAddressParams with `as`; instead declare the object with the type or use the `satisfies` operator so the compiler validates the shape. Find all places that use `as QuickCheckoutAddressParams` (the address literal creations referenced in the review) and replace them with either `const addr: QuickCheckoutAddressParams = { ... }` or `const addr = { ... } satisfies QuickCheckoutAddressParams`, ensuring required keys match the QuickCheckoutAddressParams type; update any callers that expect the previous cast only if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/cart/CartDrawer.tsx`:
- Around line 274-282: Replace the current ExpressCheckoutButton callback that
only closes the drawer with an onComplete handler that calls refreshCart()
before closeCart() so the cart badge state is updated immediately; update the
onComplete prop on the ExpressCheckoutButton in CartDrawer to invoke refreshCart
(awaiting it if needed) and then call closeCart, referring to the existing
CartDrawer, ExpressCheckoutButton, refreshCart, and closeCart symbols to locate
the change.
In `@src/components/checkout/ExpressCheckoutButton.tsx`:
- Around line 188-336: handleConfirm can run twice causing non-idempotent side
effects; add a re-entrancy guard using a React ref (e.g., const isConfirmingRef
= useRef(false)) and check it at the top of handleConfirm: if
isConfirmingRef.current is true, call event.paymentFailed({ reason: "fail" })
and return; otherwise set isConfirmingRef.current = true before starting async
work. Ensure you only clear the guard when it's safe: if stripePaymentConfirmed
is false (failure/cancel paths) reset isConfirmingRef.current = false in the
fail() paths and in the catch/finally; if payment succeeds (after
stripePaymentConfirmed = true and router.push/onComplete) you may leave it true
(or reset after full finalization) to prevent re-run. Reference handleConfirm,
stripePaymentConfirmed, fail, event.paymentFailed, expressCheckoutFinalize,
router.push and onComplete when making the changes.
- Around line 93-186: In handleShippingAddressChange, after building
shippingRates and lineItems but before event.resolve, compute the total amount
for the default selected rate (use shippingRates[0] as the default), sum
buildLineItems(order).amount plus defaultRate.amount (fallback 0), and call
elements?.update({ amount: defaultAmount }) to ensure Elements is synced; keep
shippingRateMapRef handling and then call event.resolve({ shippingRates,
lineItems }) as before.
- Around line 83-91: Update the payment failure reason type and the express
payment handling: change the type annotation used by fail() to include
"invalid_billing_address" and "address_unserviceable" in addition to the
existing "fail" | "invalid_shipping_address" | "invalid_payment_data" so it
matches Stripe v8.7.0; then review handleClick and the isGooglePayRef logic in
function handleClick (which reads event.expressPaymentType) — either explicitly
handle other values ('apple_pay'|'link'|'paypal') if you need distinct behavior
or add a comment clarifying that only google_pay is intentionally special-cased
and all other methods are treated the same. Use the symbols fail(), handleClick,
isGooglePayRef and event.expressPaymentType to locate the code to update.
In `@src/lib/data/express-checkout-flow.ts`:
- Around line 83-92: expressCheckoutFinalize currently ignores the return values
of completeCheckoutPaymentSession and completeCheckoutOrder (they return {
success: false, error } on failure) so it can report success incorrectly; modify
expressCheckoutFinalize to capture each call's result, check result.success, and
if false throw an Error (or return a rejected result) with the returned error
message (e.g., throw new Error(result.error || 'completeCheckoutPaymentSession
failed')) before proceeding to the next step so actionResult will report
failure; ensure you reference the functions completeCheckoutPaymentSession and
completeCheckoutOrder when adding these checks and throw meaningful errors that
include the underlying error text.
In `@src/lib/utils/express-checkout.ts`:
- Around line 115-123: The current logic in the block that constructs rateMap
(using rate.shipping_method_id) only sets the amount once and ignores subsequent
rates with the same shipping_method_id, causing shippingRates.amount to
undercount; modify the handling in the code that references rateMap,
rate.shipping_method_id, rate.cost and toCents so that if
rateMap.has(rate.shipping_method_id) you update the existing entry by adding to
its amount (e.g., existing.amount += toCents(rate.cost)) instead of skipping,
while preserving the existing id/displayName (and keep the current id generation
using randomSuffix() and isGooglePay only when creating a new entry).
- Around line 2-4: The toCents function currently converts its input with
Number(...) and can return NaN/Infinity; update toCents to first coerce the
input into a numeric value (e.g., const n = Number(amount)), then validate
Number.isFinite(n) and if not finite throw a clear TypeError or RangeError
describing the invalid amount (include the original value/type in the message);
finally return Math.round(n * 100). This change should be applied inside the
toCents function to ensure only finite numeric inputs are converted for Stripe
amounts.
In `@src/lib/utils/stripe.ts`:
- Around line 5-7: Extract process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY into a
const (e.g. publishableKey), remove the non-null assertion from the loadStripe
call, and guard against a missing key by logging an explicit error
(console.error or a logger) and using a safe fallback (empty string or
undefined) when calling loadStripe in stripePromise; update the loadStripe
invocation that currently references stripeAccountId so it uses the new
publishableKey variable and preserves the existing stripeAccount option logic.
---
Nitpick comments:
In `@src/app/`[country]/[locale]/(storefront)/cart/page.tsx:
- Around line 8-9: Replace the static import of ExpressCheckoutButton with a
dynamic import to code-split the cart page: remove the top-level import of
ExpressCheckoutButton in page.tsx and use Next.js dynamic import (dynamic(() =>
import(".../ExpressCheckoutButton"), { ssr: false, loading: () =>
<YourLoadingFallback/> })) where the component is rendered; keep ShoppingBagIcon
as a normal import. Apply the same change for the other occurrences noted
(around the existing ExpressCheckoutButton usage at lines ~180-189) so the
non-critical Stripe UI is loaded asynchronously with a simple loading fallback
component.
In `@src/components/cart/CartDrawer.tsx`:
- Around line 6-8: Replace the static import of ExpressCheckoutButton in
CartDrawer with a dynamic import using next/dynamic (ssr: false) and a small
loading fallback so Stripe Elements aren't bundled into the always-loaded
drawer; locate the ExpressCheckoutButton reference in the CartDrawer component
and swap the import for something like dynamic(() =>
import('@/components/checkout').then(m => m.ExpressCheckoutButton), { ssr:
false, loading: () => <SmallLoadingFallback/> }), and apply the same change to
the other ExpressCheckoutButton usage in this file (the block referenced around
the second occurrence).
In `@src/components/checkout/ExpressCheckoutButton.tsx`:
- Around line 313-321: The try/catch around expressCheckoutFinalize only catches
transport/runtime errors but not business failures because
expressCheckoutFinalize returns an actionResult object; update the code that
calls expressCheckoutFinalize(orderId, sessionId) to await the result,
explicitly check the returned { success, error } (or typed ActionResult) and log
or send telemetry when success is false (include orderId/sessionId/context),
then proceed with router.push(`${basePath}/order-placed/${orderId}`) and
onComplete() as before; also ensure expressCheckoutFinalize has an explicit
TypeScript return type (e.g., ActionResult) instead of any so the caller can
rely on typing.
- Around line 36-41: The props and many inline casts are too loose—define a
narrow exported order-like type (e.g., export type OrderSummary = { item_total:
number; promo_total?: number; additional_tax_total?: number; shipments?: {
shipping_rates?: Array<{ id: string; price: number; }>; }[] } ) that captures
only the fields read, then replace the ad-hoc casts (e.g., the uses of cart as
unknown as Record<string, unknown>, result.order as Record<string, unknown>, and
inline shipments casting) with that type (use OrderSummary | undefined where
appropriate), update any client/server action return types to return
OrderSummary for the order shape, and export ExpressCheckoutButtonProps so
dynamic imports keep strong typing; adjust the functions/methods that reference
shipments.shipping_rates to use the typed shipments shape instead of assertion
chains.
In `@src/lib/data/express-checkout-flow.ts`:
- Line 16: The import for actionResult in express-checkout-flow.ts uses a
relative path ("./utils"); update it to the project's absolute alias (use
"@/lib/data/utils" or the correct `@/lib` path where utils.ts lives) so the import
reads with the @ alias and matches project convention; ensure the imported
symbol name actionResult remains unchanged and that the file still builds after
switching the import.
In `@src/lib/data/quick-checkout.ts`:
- Around line 5-8: You’re bypassing TS excess-property checks by casting address
object literals to QuickCheckoutAddressParams with `as`; instead declare the
object with the type or use the `satisfies` operator so the compiler validates
the shape. Find all places that use `as QuickCheckoutAddressParams` (the address
literal creations referenced in the review) and replace them with either `const
addr: QuickCheckoutAddressParams = { ... }` or `const addr = { ... } satisfies
QuickCheckoutAddressParams`, ensuring required keys match the
QuickCheckoutAddressParams type; update any callers that expect the previous
cast only if needed.
In `@src/lib/utils/express-checkout.ts`:
- Around line 17-34: The exported helper buildLineItems uses loose typing
(Record<string, unknown>) and runtime casts which can hide shape drift; replace
the input type with the appropriate Spree SDK type (e.g., StoreOrder) and give
the function an explicit return type (e.g., StoreLineItem[] or Array<{ name:
string; amount: number }>), remove all "as" casts for order.item_total /
promo_total / additional_tax_total and instead access those fields with proper
types and safe checks (optional chaining / default values) so the compiler
enforces the shape; apply the same pattern to the other helpers mentioned (lines
~52-75) by importing and using StoreProduct, StoreVariant, PaginatedResponse,
etc., and returning the concrete SDK types.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
.env.local.exampleREADME.mdnext.config.tspackage.jsonsrc/app/[country]/[locale]/(storefront)/cart/page.tsxsrc/components/cart/CartDrawer.tsxsrc/components/checkout/ExpressCheckoutButton.tsxsrc/components/checkout/StripePaymentForm.tsxsrc/components/checkout/index.tssrc/lib/data/express-checkout-flow.tssrc/lib/data/index.tssrc/lib/data/quick-checkout.tssrc/lib/utils/express-checkout.tssrc/lib/utils/stripe.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (5)
.gitignore (1)
48-48: Anchor the certificates ignore rule to the repo root.
certificatescan match nested directories too. If this is specifically for./certificatesused bydev:https, prefer a root-anchored directory pattern.Suggested diff
-#certs for https -certificates +# certs for https +/certificates/🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.gitignore at line 48, The .gitignore entry "certificates" is unanchored and will match nested paths; replace it with a root-anchored directory pattern (use "/certificates/" or "/certificates") so only the repo-root ./certificates directory used by dev:https is ignored; update the .gitignore line for the "certificates" entry accordingly.src/lib/data/express-checkout-flow.ts (2)
16-16: Use alias import here for consistency with project path rules.Prefer
@/lib/data/utilsinstead of./utilsin this module.As per coding guidelines: "Use absolute imports with @ alias (e.g.,
@/components/...,@/lib/...) instead of relative imports".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/data/express-checkout-flow.ts` at line 16, Replace the relative import of actionResult in src/lib/data/express-checkout-flow.ts with the project alias path: change the import that currently references "./utils" to "@/lib/data/utils" so the symbol actionResult is imported via the alias and matches the project's absolute import convention.
33-33: Avoid broadRecord<string, unknown>casts fororderpayloads.These casts hide contract mismatches. Define/propagate a typed order shape from the action layer instead.
As per coding guidelines: "Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type".
Also applies to: 49-49, 79-79
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/data/express-checkout-flow.ts` at line 33, Replace the broad cast to Record<string, unknown> for the order payload by introducing and using an explicit Order interface/type and propagating it through the action layer and return types; locate the returns that cast advanceResult.order (the occurrences at/around the lines returning "advanceResult.order as Record<string, unknown>") and change the function signatures to return Promise<{ order: Order }> (or appropriate sync type), update the type of advanceResult (or the action that produces it) so advanceResult.order is typed as Order, and adjust any callers to accept the new typed shape so the contract is enforced rather than hidden by a broad cast.src/components/checkout/ExpressCheckoutButton.tsx (1)
446-451: Consider replacing fixed$200buffer with configurable/store-aware logic.A hardcoded buffer can still be too low for high-shipping orders, and the wallet amount cannot be increased after opening. Making this configurable (or derived from known shipping ceilings) reduces edge-case payment failures.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/ExpressCheckoutButton.tsx` around lines 446 - 451, Replace the hardcoded $200 buffer used in the useMemo that computes amount by making the buffer configurable or calculated from store/shipping data: update the amount calculation in the useMemo (referencing amount, toCents, cart.total) to use a configurable prop or context value (e.g., checkoutConfig.walletBuffer) or derive a ceiling from known shipping options/selectedShippingRate (e.g., maxShippingRate or shippingCeiling) and add that derived buffer; also ensure the logic that opens the wallet (where amount is consumed) can accept an updated amount rather than a fixed precomputed value so the wallet total can increase if shipping changes.src/lib/utils/express-checkout.ts (1)
23-39: Use a typed order shape instead ofRecord<string, unknown>in line-item building.This function relies on known order totals; keeping it untyped forces unsafe casts and weakens compile-time checks.
As per coding guidelines: "Import and use Spree SDK types (StoreProduct, StoreVariant, StoreOrder, StoreLineItem, PaginatedResponse) for type safety".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/express-checkout.ts` around lines 23 - 39, Change the loose Record type to the Spree SDK order type and remove unsafe casts: import and use StoreOrder (from the Spree types) in the buildLineItems signature so buildLineItems(order: StoreOrder) instead of Record<string, unknown>, then call toCents with the properly typed order properties (order.item_total, order.promo_total, order.additional_tax_total) without "as string" casts; update the returned item shape if desired to a typed alias (or StoreLineItem-like shape) to keep compile-time safety. Ensure you add the Spree import and update references to buildLineItems and toCents accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/checkout/ExpressCheckoutButton.tsx`:
- Around line 351-353: The call to onComplete() is not awaited which can leak
rejected promises and break the post-success flow; change the sequence to await
onComplete() (e.g., await onComplete()) and wrap it in try/catch/finally so you
handle errors and always reset isConfirmingRef.current = false in the finally
block, calling router.push(`${basePath}/order-placed/${orderId}`) either before
awaiting onComplete() if navigation must happen first or after if onComplete
must complete prior to navigation—adjust ordering accordingly in
ExpressCheckoutButton where router.push, onComplete, and isConfirmingRef.current
are used.
In `@src/lib/utils/express-checkout.ts`:
- Around line 2-9: toCents currently multiplies by 100 unconditionally, which
inflates amounts for zero-decimal currencies; change toCents to accept a
currency parameter (e.g. toCents(amount: string|number, currency: string)) and
compute the multiplier based on whether the currency is zero-decimal (use a
set/list of Stripe zero-decimal currencies or Stripe's minor unit map) so it
returns Math.round(n * multiplier). Thread the new currency argument through
buildLineItems and buildShippingRateMap so they call toCents(price, currency)
(and update their signatures/call sites accordingly) to ensure amounts for JPY,
KRW, VND, etc. are not multiplied by 100.
---
Nitpick comments:
In @.gitignore:
- Line 48: The .gitignore entry "certificates" is unanchored and will match
nested paths; replace it with a root-anchored directory pattern (use
"/certificates/" or "/certificates") so only the repo-root ./certificates
directory used by dev:https is ignored; update the .gitignore line for the
"certificates" entry accordingly.
In `@src/components/checkout/ExpressCheckoutButton.tsx`:
- Around line 446-451: Replace the hardcoded $200 buffer used in the useMemo
that computes amount by making the buffer configurable or calculated from
store/shipping data: update the amount calculation in the useMemo (referencing
amount, toCents, cart.total) to use a configurable prop or context value (e.g.,
checkoutConfig.walletBuffer) or derive a ceiling from known shipping
options/selectedShippingRate (e.g., maxShippingRate or shippingCeiling) and add
that derived buffer; also ensure the logic that opens the wallet (where amount
is consumed) can accept an updated amount rather than a fixed precomputed value
so the wallet total can increase if shipping changes.
In `@src/lib/data/express-checkout-flow.ts`:
- Line 16: Replace the relative import of actionResult in
src/lib/data/express-checkout-flow.ts with the project alias path: change the
import that currently references "./utils" to "@/lib/data/utils" so the symbol
actionResult is imported via the alias and matches the project's absolute import
convention.
- Line 33: Replace the broad cast to Record<string, unknown> for the order
payload by introducing and using an explicit Order interface/type and
propagating it through the action layer and return types; locate the returns
that cast advanceResult.order (the occurrences at/around the lines returning
"advanceResult.order as Record<string, unknown>") and change the function
signatures to return Promise<{ order: Order }> (or appropriate sync type),
update the type of advanceResult (or the action that produces it) so
advanceResult.order is typed as Order, and adjust any callers to accept the new
typed shape so the contract is enforced rather than hidden by a broad cast.
In `@src/lib/utils/express-checkout.ts`:
- Around line 23-39: Change the loose Record type to the Spree SDK order type
and remove unsafe casts: import and use StoreOrder (from the Spree types) in the
buildLineItems signature so buildLineItems(order: StoreOrder) instead of
Record<string, unknown>, then call toCents with the properly typed order
properties (order.item_total, order.promo_total, order.additional_tax_total)
without "as string" casts; update the returned item shape if desired to a typed
alias (or StoreLineItem-like shape) to keep compile-time safety. Ensure you add
the Spree import and update references to buildLineItems and toCents
accordingly.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
.gitignoresrc/app/[country]/[locale]/(storefront)/cart/page.tsxsrc/components/cart/CartDrawer.tsxsrc/components/checkout/ExpressCheckoutButton.tsxsrc/lib/data/express-checkout-flow.tssrc/lib/utils/express-checkout.tssrc/lib/utils/stripe.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/lib/utils/stripe.ts
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/components/checkout/ExpressCheckoutButton.tsx (1)
127-141:⚠️ Potential issue | 🟠 MajorResolve amount synchronization before
event.resolve()and fail fast on update errors.Right now the flow resolves the shipping event first, then treats
elements.updatefailures as non-fatal. That can continue the wallet with a stale amount and defer failure to confirm-time.🔧 Suggested fix
- const lineItems = buildLineItems(order); - event.resolve({ shippingRates, lineItems }); - - // Sync Elements amount to match the default (first) shipping rate - // so subsequent rate changes can only decrease the authorized amount. - try { - const lineItemsSum = lineItems.reduce( - (sum, item) => sum + item.amount, - 0, - ); - const defaultShippingAmount = shippingRates[0]?.amount ?? 0; - elements?.update({ amount: lineItemsSum + defaultShippingAmount }); - } catch (_) { - /* elements.update failed — non-fatal */ - } + const lineItems = buildLineItems(order); + const lineItemsSum = lineItems.reduce((sum, item) => sum + item.amount, 0); + const defaultShippingAmount = shippingRates[0]?.amount ?? 0; + try { + elements?.update({ amount: lineItemsSum + defaultShippingAmount }); + } catch (_updateErr) { + setError("Could not update payment amount for the selected shipping address."); + event.reject(); + return; + } + event.resolve({ shippingRates, lineItems });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/ExpressCheckoutButton.tsx` around lines 127 - 141, Compute the line items and default shipping amount (using buildLineItems and shippingRates[0]?.amount) and call elements.update({ amount: lineItemsSum + defaultShippingAmount }) before calling event.resolve; do not swallow errors from elements.update — instead fail fast by propagating the error or calling event.reject with the error so the wallet flow stops rather than continuing with a stale amount; only call event.resolve({ shippingRates, lineItems }) after elements.update succeeds.
🧹 Nitpick comments (3)
src/components/checkout/ExpressCheckoutButton.tsx (2)
43-48: Add explicit return types for component functions.
ExpressCheckoutInnerandExpressCheckoutButtoncurrently rely on inference. Explicit return types make the component contracts clearer and stricter.🧩 Suggested refactor
-import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import type { JSX } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ function ExpressCheckoutInner({ cart, basePath, onComplete, onProcessingChange, -}: ExpressCheckoutButtonProps) { +}: ExpressCheckoutButtonProps): JSX.Element | null { @@ export function ExpressCheckoutButton({ cart, basePath, onComplete, onProcessingChange, -}: ExpressCheckoutButtonProps) { +}: ExpressCheckoutButtonProps): JSX.Element {As per coding guidelines: "
**/*.ts{,x}: Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type".Also applies to: 430-435
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/ExpressCheckoutButton.tsx` around lines 43 - 48, Add explicit return types to the component functions: annotate ExpressCheckoutInner and ExpressCheckoutButton with a React/JSX return type (e.g., : JSX.Element or : React.ReactElement) instead of relying on inference; update the function signatures for ExpressCheckoutInner and the exported ExpressCheckoutButton to include the chosen return type and ensure any corresponding props types remain unchanged. Also apply the same explicit return-type change to the other component occurrence referenced (the function around lines 430–435) so all component functions in this file have explicit JSX/React return types.
449-455: Prefersatisfiesfor Stripe options object typing instead of literal casts.Using
as consthere weakens object-literal shape validation.satisfieskeeps literal precision and validates against Stripe’s options contract.♻️ Suggested refactor
import type { StripeExpressCheckoutElementClickEvent, StripeExpressCheckoutElementConfirmEvent, + StripeElementsOptions, StripeExpressCheckoutElementReadyEvent, StripeExpressCheckoutElementShippingAddressChangeEvent, StripeExpressCheckoutElementShippingRateChangeEvent, } from "@stripe/stripe-js"; @@ const options = useMemo( - () => ({ - mode: "payment" as const, - amount, - currency, - paymentMethodCreation: "manual" as const, - }), + () => + ({ + mode: "payment", + amount, + currency, + paymentMethodCreation: "manual", + }) satisfies StripeElementsOptions, [amount, currency], );As per coding guidelines: "
**/*.ts{,x}: Use 'satisfies' operator for type checking object literals instead of casting with 'as'".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/checkout/ExpressCheckoutButton.tsx` around lines 449 - 455, Replace the two "as const" casts in the useMemo options object with a TypeScript "satisfies" assertion to preserve literal precision and validate the object shape against the Stripe options type; update the options creation in the ExpressCheckoutButton component (the options variable returned by useMemo, including the mode and paymentMethodCreation properties) to remove "as const" on the properties and instead append a single "satisfies <StripeOptionsType>" (use the appropriate Stripe options type from `@stripe/stripe-js` or your Stripe typings) so the object is both literal-precise and type-checked.src/lib/utils/express-checkout.ts (1)
55-73: Add explicit return types for exported utility functions.
buildLineItemsandbuildSpreeAddressshould expose explicit return types to keep their public contracts stable and strict.🧱 Suggested refactor
-export function buildLineItems(order: StoreOrder) { +export function buildLineItems( + order: StoreOrder, +): Array<{ name: string; amount: number }> { @@ export function buildSpreeAddress( name: { firstname: string; lastname: string }, address: { @@ }, phone?: string, -) { +): { + firstname: string; + lastname: string; + address1: string; + address2?: string; + city: string; + zipcode: string; + country_iso: string; + state_name?: string; + phone?: string; +} {As per coding guidelines: "
**/*.ts{,x}: Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type".Also applies to: 91-114
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/utils/express-checkout.ts` around lines 55 - 73, The exported functions buildLineItems and buildSpreeAddress currently rely on inferred return types; add explicit return type annotations to their function signatures to make the public contract strict and stable — for buildLineItems annotate the return type to match the array shape being returned (an array of objects with name:string and amount:number) and for buildSpreeAddress add the explicit return type that matches the Spree address shape used elsewhere (e.g., the SpreeAddress/interface used in your codebase); update the exported signatures for these functions so TypeScript enforces the exact structure instead of inferring it.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/components/checkout/ExpressCheckoutButton.tsx`:
- Around line 127-141: Compute the line items and default shipping amount (using
buildLineItems and shippingRates[0]?.amount) and call elements.update({ amount:
lineItemsSum + defaultShippingAmount }) before calling event.resolve; do not
swallow errors from elements.update — instead fail fast by propagating the error
or calling event.reject with the error so the wallet flow stops rather than
continuing with a stale amount; only call event.resolve({ shippingRates,
lineItems }) after elements.update succeeds.
---
Nitpick comments:
In `@src/components/checkout/ExpressCheckoutButton.tsx`:
- Around line 43-48: Add explicit return types to the component functions:
annotate ExpressCheckoutInner and ExpressCheckoutButton with a React/JSX return
type (e.g., : JSX.Element or : React.ReactElement) instead of relying on
inference; update the function signatures for ExpressCheckoutInner and the
exported ExpressCheckoutButton to include the chosen return type and ensure any
corresponding props types remain unchanged. Also apply the same explicit
return-type change to the other component occurrence referenced (the function
around lines 430–435) so all component functions in this file have explicit
JSX/React return types.
- Around line 449-455: Replace the two "as const" casts in the useMemo options
object with a TypeScript "satisfies" assertion to preserve literal precision and
validate the object shape against the Stripe options type; update the options
creation in the ExpressCheckoutButton component (the options variable returned
by useMemo, including the mode and paymentMethodCreation properties) to remove
"as const" on the properties and instead append a single "satisfies
<StripeOptionsType>" (use the appropriate Stripe options type from
`@stripe/stripe-js` or your Stripe typings) so the object is both literal-precise
and type-checked.
In `@src/lib/utils/express-checkout.ts`:
- Around line 55-73: The exported functions buildLineItems and buildSpreeAddress
currently rely on inferred return types; add explicit return type annotations to
their function signatures to make the public contract strict and stable — for
buildLineItems annotate the return type to match the array shape being returned
(an array of objects with name:string and amount:number) and for
buildSpreeAddress add the explicit return type that matches the Spree address
shape used elsewhere (e.g., the SpreeAddress/interface used in your codebase);
update the exported signatures for these functions so TypeScript enforces the
exact structure instead of inferring it.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
.gitignoresrc/components/checkout/ExpressCheckoutButton.tsxsrc/lib/data/express-checkout-flow.tssrc/lib/utils/express-checkout.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/lib/data/express-checkout-flow.ts
- .gitignore
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/lib/data/quick-checkout.ts (2)
5-5: Use the repo alias for local imports.
./utilsbreaks the TS import rule used elsewhere in the app. Import this via@/lib/data/utilsinstead.As per coding guidelines, "Use absolute imports with @ alias (e.g.,
@/components/...,@/lib/...) instead of relative imports".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/data/quick-checkout.ts` at line 5, The import in quick-checkout.ts uses a relative path ("./utils") which violates the project's TS import rule; replace it with the repo alias import "@/lib/data/utils" so the symbol actionResult is imported via the absolute alias (e.g., change the import that brings in actionResult in quick-checkout.ts to use "@/lib/data/utils").
16-19: Add explicit return types to the exported server actions.These helpers are part of the public
lib/datasurface and are already consumed bysrc/lib/data/express-checkout-flow.ts. Right now the contract is only whateveractionResultinfers, which makes upstream type drift harder to catch.Example of the shape to pin explicitly
-import type { AddressParams } from "@spree/sdk"; +import type { AddressParams, StoreOrder } from "@spree/sdk"; import { actionResult } from "./utils"; + +type QuickCheckoutActionResult = Promise< + | { success: true; order: StoreOrder } + | { success: false; error: string } +>; export async function quickCheckoutUpdateAddress( orderId: string, address: QuickCheckoutPartialAddress, -) { +): QuickCheckoutActionResult { return actionResult(async () => { const order = await updateOrder(orderId, { ship_address: { firstname: address.firstname || "Express", lastname: address.lastname || "Checkout", address1: "TBD", ...address, quick_checkout: true, }, }); return { order }; }, "Failed to update address for quick checkout"); } -export async function quickCheckoutAdvance(orderId: string) { +export async function quickCheckoutAdvance( + orderId: string, +): QuickCheckoutActionResult {As per coding guidelines, "Use strict TypeScript type checking; define explicit return types for functions and avoid 'any' type" and "Import and use Spree SDK types (StoreProduct, StoreVariant, StoreOrder, StoreLineItem, PaginatedResponse) for type safety".
Also applies to: 34-34, 41-48, 65-65
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/data/quick-checkout.ts` around lines 16 - 19, The exported server action quickCheckoutUpdateAddress (and the other exported helpers noted) currently rely on inferred actionResult types; add explicit TypeScript return types to each exported function (e.g., quickCheckoutUpdateAddress): import and use the appropriate Spree SDK types (such as StoreOrder, StoreLineItem, PaginatedResponse, StoreProduct/StoreVariant as applicable) to declare a concrete Promise<...> return type that matches the actionResult shape consumed by src/lib/data/express-checkout-flow.ts, and update the function signatures to return that explicit type instead of relying on inference.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/lib/data/quick-checkout.ts`:
- Around line 22-26: In the ship_address object, the current order spreads
...address after setting fallback firstname/lastname which allows empty or
undefined values from address to overwrite the fallbacks; move the spread
operator (...address) before the firstname and lastname assignments in the
ship_address construction and change the assignments to use trimmed existence
checks (e.g., use address.firstname?.trim() and address.lastname?.trim() to
decide whether to use address values or the fallbacks "Express" and "Checkout")
so whitespace-only or empty strings don't override the defaults.
- Around line 67-69: The update in quick-checkout.ts is incorrectly clearing
addresses by passing empty strings to updateOrder; change the payload passed to
updateOrder(orderId, ...) so that ship_address_id and bill_address_id are set to
the literal string "CLEAR" (not ""), ensuring the temporary wallet address is
removed; locate the call to updateOrder in the quick-checkout logic (the
function/updateOrder invocation around orderId) and replace the empty-string
values with "CLEAR".
---
Nitpick comments:
In `@src/lib/data/quick-checkout.ts`:
- Line 5: The import in quick-checkout.ts uses a relative path ("./utils") which
violates the project's TS import rule; replace it with the repo alias import
"@/lib/data/utils" so the symbol actionResult is imported via the absolute alias
(e.g., change the import that brings in actionResult in quick-checkout.ts to use
"@/lib/data/utils").
- Around line 16-19: The exported server action quickCheckoutUpdateAddress (and
the other exported helpers noted) currently rely on inferred actionResult types;
add explicit TypeScript return types to each exported function (e.g.,
quickCheckoutUpdateAddress): import and use the appropriate Spree SDK types
(such as StoreOrder, StoreLineItem, PaginatedResponse, StoreProduct/StoreVariant
as applicable) to declare a concrete Promise<...> return type that matches the
actionResult shape consumed by src/lib/data/express-checkout-flow.ts, and update
the function signatures to return that explicit type instead of relying on
inference.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5c3d87d9-0d59-4f7e-9db6-235d8d62ac6a
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (5)
README.mdnext.config.tspackage.jsonsrc/lib/data/index.tssrc/lib/data/quick-checkout.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- src/lib/data/index.ts
- package.json
- README.md
Enable customers to skip the full checkout flow and pay directly from the cart drawer and cart page using Stripe Express Checkout Element with Apple Pay, Google Pay, and Link wallet payments. - Add ExpressCheckoutButton component with full shipping address/rate resolution and Stripe payment confirmation flow - Add server actions for quick checkout (address update, advance, finalize) - Add express-checkout-flow orchestration layer between component and server actions - Extract shared Stripe initialization to lib/utils/stripe.ts with Stripe Connect (stripeAccount) support - Add utility helpers for building line items, shipping rate maps, and address conversion between Stripe and Spree formats - Integrate Express Checkout in CartDrawer and cart page with processing state that hides summary/actions during payment - Add HTTPS dev server setup (mkcert + shop.lvh.me) for Apple Pay testing - Add Stripe env vars to .env.local.example Closes #29 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- toCents: add Number.isFinite validation to prevent NaN propagation - buildShippingRateMap: accumulate shipping cost across multiple shipments - stripe.ts: remove non-null assertion, add graceful degradation for missing key - expressCheckoutFinalize: check return values from payment session/order completion - ExpressCheckoutButton: export props interface, add re-entrancy guard, widen fail() reason type, sync Elements amount after shipping resolve, check finalize result, add Google Pay comment - CartDrawer: await refreshCart() before closeCart in onComplete, dynamic import for ExpressCheckoutButton (code-split Stripe bundle) - Cart page: dynamic import for ExpressCheckoutButton - .gitignore: add certificates directory - Update @spree/sdk to ^0.5.0 (fixes CountrySwitcher build error) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Complete - toCents: add currency parameter with Stripe zero-decimal currency support (JPY, KRW, VND, etc.) to prevent inflated amounts - buildLineItems: use StoreOrder type instead of Record<string, unknown>, thread currency through toCents calls - buildShippingRateMap: use StoreShipment type from SDK, add currency param - express-checkout-flow: remove unnecessary Record<string, unknown> casts, let StoreOrder type flow through from SDK functions - ExpressCheckoutButton: remove all Record<string, unknown> casts, await onComplete() in try/finally to prevent promise leaks and ensure isConfirmingRef cleanup, widen onComplete type to void | Promise<void> - Extract SHIPPING_BUFFER_AMOUNT constant for Apple Pay pre-authorization, make it currency-aware via toCents - .gitignore: anchor /certificates to repo root only Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace removed `updateAddresses` with `updateOrder` in quick-checkout and remove `getStore`/`StoreStore` which no longer exist in the SDK. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…hs, and return types - Move spread operator before firstname/lastname fallbacks with trim checks - Use "CLEAR" instead of empty strings for clearing addresses - Replace relative import paths with @/ aliases - Add explicit TypeScript return types to all exported functions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
49ada51 to
2dc5cef
Compare
Summary
Closes #29
ExpressCheckoutButtoncomponent using Stripe's Express Checkout Element with Apple Pay, Google Pay, and Link wallet supportlib/utils/stripe.tswith Stripe Connect (stripeAccount) supportmkcert+shop.lvh.me) required for Apple Pay testing locallyNew files
src/components/checkout/ExpressCheckoutButton.tsxExpressCheckoutElementwith shipping address/rate resolution and payment confirmationsrc/lib/data/quick-checkout.ts@spree/nextsrc/lib/data/express-checkout-flow.tssrc/lib/utils/express-checkout.tssrc/lib/utils/stripe.tsstripePromisewith optional Stripe Connect accountHow it works
Test plan
npm run dev:https)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores