Shared Payment Tokens in ACP: How the SPT Charge Flow Works
shared_payment_granted_token on a PaymentIntent with immediate confirmation, maps the three possible outcomes to three clean results (success, requires_3ds, payment_declined), and never lets a raw processor exception cross the API boundary. This page walks the whole token path, from the platform's grant to the order webhook firing.
What a Shared Payment Token Actually Is
In delegated ACP payments the platform, not the merchant, holds the shopper's payment method. The shopper saved a card with the assistant's operator, the platform vaulted it with its processor, and when the shopper approves a purchase the platform asks Stripe to mint a token that represents permission to charge that vaulted method once, for this specific merchant, up to the approved amount. That token is the SPT, and it is all the merchant ever sees. It is not a card number, not a reusable customer id, and not a general-purpose payment method: outside its scope it is worthless, which is exactly the point.
The properties that matter to a merchant integration are the constraints. One use: a charged token cannot be charged again. One merchant: a token granted for your store cannot be spent elsewhere. One ceiling: the charge must not exceed the amount the shopper authorized, and the module always charges exactly the session total it computed and displayed, the same integer-cent figure from the catalog math, never a client-supplied number. Those constraints are why the delegated flow keeps PCI exposure minimal on every side, the card data stays inside the platform's processor relationship, as covered in the security model.
Where the Token Arrives: The Complete Endpoint
The token shows up in exactly one place: the payment_data object of a POST to /checkout_sessions/{id}/complete, usually alongside a billing address and sometimes final buyer details. The full request rules for that route, bearer auth, the API-Version header, and the mandatory Idempotency-Key, are in the endpoints reference. Before any money moves, the module applies the billing address and re-prices the session so tax is computed against the buyer's real location, then validates the session actually has items. Only a session in a payable state proceeds to the charge, a canceled or frozen session fails cleanly before the provider is ever called.
The order of operations matters and is deliberate. Address first, then tax, then charge, means the amount charged always matches the amount the re-priced session shows, and the platform can display a final total to the shopper that will not shift after approval. Client-supplied amounts are never part of this flow anywhere: the catalog is the only price authority, and the token is charged for the session's own total entry.
The Stripe Call: shared_payment_granted_token
The charge itself is one PaymentIntent creation. The module passes the platform's token as a shared_payment_granted_token, sets the session's exact integer-cent total and currency, and confirms immediately, there is no separate authorize-then-capture dance in this flow. Three outcomes come back, and each maps to one result the platform can act on.
Success. The PaymentIntent succeeds, the module marks the session completed, attaches the order object with its id and permalink, stores the payment reference for later refunds, and fires your order webhook. The platform shows the confirmation in the same thread, and from your side the order looks identical to one paid through hosted checkout.
Authentication required. If the issuer demands 3D Secure, Stripe returns requires_action, and the module surfaces it as a 400 with code requires_3ds. The merchant cannot run that challenge, there is no browser on the merchant side of a server-to-server call, so the error hands the problem to the party who has the shopper's screen: the platform completes the authentication with the shopper and retries the complete call. The session is still payable, nothing was charged, and the retry follows the normal idempotency rules below.
Declined. Any decline becomes a 400 with code payment_declined and Stripe's decline reason carried in the human-readable message. The raw Stripe exception, its stack, and its internal identifiers never cross the API boundary, the translation happens inside the provider layer. Declines are normal in card processing, a few percent of legitimate attempts fail for issuer reasons, and in delegated mode your rate is also shaped by the quality of the platform's vaulting. The module's job is clean reporting and consistent state.
State After Each Outcome
The state rules are what make retries safe. A declined or 3DS-blocked session stays ready_for_payment, so the shopper can fix the problem and the platform can try again. A completed session is terminal and idempotent: completing it again returns the same completed session unchanged and charges nothing. A canceled session can never be completed, and a late confirmation can never resurrect it. Combined with the Idempotency-Key semantics, same key and body replays the stored response, same key with a different body is a 422 conflict, an in-flight key returns 409 with a Retry-After hint, a platform can retry aggressively at the network level without any risk of a double charge. The error codes reference lists every code this path can produce.
Availability and the Fallback
Shared Payment Tokens are part of Stripe's agentic commerce capabilities, in preview for United States merchants at the time of writing. That constraint belongs to the processor, not the protocol, and the module treats it accordingly: delegated mode lights up when your Stripe account can use it, while direct mode, the hosted Stripe Checkout flow described in payment modes, works wherever Stripe operates. Non-US merchants run conversational commerce through hosted checkout today and gain the token path as Stripe expands it, with no code changes, both modes already run through the same provider interface and the same session rules.
requires_3ds round trip, a decline leaving the session payable, and an idempotent replay of a successful complete charging exactly once. All three run against the mock provider with no Stripe account, which makes them easy to show in CI.
Testing the Token Path Without Stripe
With no STRIPE_SECRET_KEY configured the module swaps in its deterministic mock provider, and the mock speaks token conventions designed for exactly this page's scenarios: tok_decline produces a declined result, tok_3ds produces the requires-action result, and any other token string succeeds with a predictable payment reference. The mock also records every call it receives, so a test can assert a session was charged once and only once through a retry storm. The quickstart has a curl walkthrough that takes a session from create to a mock-token complete in under a minute, and the configuration guide covers the environment switches involved.
When you move to live testing, the same three scenarios exist with Stripe's test tokens, and the wire behavior is identical because nothing outside the provider layer knows which provider is running. That seam, a four-method PaymentProvider interface, is also where a team would integrate a different processor entirely.
Why This Flow Is Worth Implementing
Delegated tokens are what make true in-assistant checkout possible: no link, no redirect, the shopper approves once and the confirmation appears in the conversation seconds later. Every step that leaves the thread costs conversion, so the platforms building agentic commerce push hard toward this flow, and merchants who already speak it are the easy integrations. The module gives you the whole path, the endpoint contract, the token charge, the 3DS and decline mapping, and the idempotency guarantees, in one embeddable package, free and open source. Where the money actually lands, and the economics of processing it, are the subject of our broader payment processing pillar.
