Cart Updates
Only add cart updates if the payer can change plan, interval, quantity, discount, shipping, or another financial field after checkout opens. Opening never grants payer ownership or a cart lease; authorized viewers continue to share the versioned Proxy cart until a qualifying payment.
Hosted and Embedded Checkout Use Replacement
Section titled “Hosted and Embedded Checkout Use Replacement”Stripe does not support changing line items on hosted or embedded Checkout Sessions through this mutable-cart contract. syncCheckoutCart(...) detects an explicit hosted or embedded acquisition and throws ProxyStripeCartSyncError with the stable code: "hosted_replacement_required" before Stripe or Proxy mutation.
Replace a hosted or embedded Session in this order:
- Expire the bound Session from the merchant backend with the merchant-owned Stripe client.
- Wait for Proxy webhook/API reconciliation to expose the acquisition as provider-confirmed
expired. Do not infer terminal state from a local timeout. - Recompute prices from the server-side catalog and write the next Proxy cart with
expectedCartVersion. - Supersede the expired acquisition with reason
checkout_expiredand the canonical fingerprint of the next hosted parameters. - Call
openCheckout(...)with those exact parameters. Identical callers join the replacement and create one new Session.
import { fingerprintHostedCheckoutProviderOptions, openCheckout,} from "@proxy-checkout/stripe-server-js";
await stripe.checkout.sessions.expire(acquisition.providerRootObjectId);const expired = await waitForProxyAcquisitionStatus(acquisition.id, "expired");
const updated = await proxy.sessions.cart.set(proxySessionId, { amountMinor: nextCart.amountMinor, cartSnapshot: nextCart.cartSnapshot, currency: nextCart.currency, expectedCartVersion: session.cartVersion,});const nextParams = buildCheckoutSessionParams(updated);
await proxy.providerAcquisitions.supersedeProviderAcquisition( proxySessionId, expired.id, { expectedVersion: expired.version, reason: "checkout_expired", replacement: { // Use "embedded" when replacing an embedded Checkout Session. checkoutUiMode: "hosted", integrationPath: "checkout_session", providerOptionsFingerprint: fingerprintHostedCheckoutProviderOptions(nextParams), }, },);
return openCheckout({ ...openOptions, proxySessionId, buildCheckoutSessionParams: () => nextParams,});Keep this route server-side and authenticated to the active payer flow. Browser callers provide only app-level edit input; they never provide Stripe credentials, Proxy metadata, or merchandiseSubtotalMinor.
If a stale or bypassed positive payment succeeds during replacement, Proxy retains the immutable payment and earned fee, raises reconciliation visibility, and does not provision it after another acquisition wins.
Custom Checkout
Section titled “Custom Checkout”Custom one-time Checkout uses that replacement sequence for immutable Session
options. syncCheckoutCart(...) can update only a mutable custom Checkout
acquisition. It validates the acquisition and contract-v2
metadata before provider access, runs on the merchant server, and never accepts
browser-authored amount, currency, line, or fee inputs. It is not a fallback for
hosted or embedded Checkout.