The agent confirmed the order and the charge failed afterwards
In short
A charge that fails after an agent has confirmed an order is usually a delegated payment token being refused, not a card being declined: the total moved above the allowance, the token expired mid-confirmation, it was bound to a different session or merchant, or a retry lost its idempotency key. Check those 4 in order, and unwind the order side before the money side.
Key takeaways
- An order with no authorisation is a state your schema must have, not an exception to catch.
- Read allowance, expiry, merchant binding and session binding in order — the first refusal ends the search.
- Token expiries run in minutes, so a slow completion handler is a payment defect, not a latency one.
- A retry without the original idempotency key is how one failure becomes 2 charges, or none.
- Unwind the order before the money: fulfilment moves faster than any refund you can issue.
You have an order record, a shopper who was told the purchase went through, and no successful authorisation against it. This is almost never a card decline. It is a delegated payment token being refused, and that token carries only 4 constraints: a maximum amount, a currency, an expiry, and a binding to one merchant and one session. Read them in that order and stop at the first mismatch.
Nobody designs for this state. On your own storefront, payment happens before the order exists. In an agent flow the confirmation came from the agent's surface, your completion handler creates the order, and authorisation is a separate call that can fail after both. Your schema needs a state for "ordered, unpaid" or operations will invent one in a spreadsheet.
Pull one failed completion apart before you touch the code
Take one occurrence and reconstruct it. The published Agentic Commerce Protocol specification requires every request to carry an Idempotency-Key, a Request-Id, a Signature and a Timestamp, and the response to echo the first 2 back. Those identifiers join your logs to the agent's; if you are not persisting them against the order, you are debugging blind.
- Find the completion call. Locate the POST to your session-complete endpoint by Request-Id, and record when it arrived and when your handler answered. That gap decides cause 2.
- Read the session status at the moment of the call. The specification names 4: not_ready_for_payment, ready_for_payment, completed and canceled. Completion attempted against anything but ready_for_payment is a sequencing bug on your side.
- Compare 2 totals: the one your session last returned to the agent, and the amount you presented for authorisation. If they differ by even the smallest unit of currency, you have cause 1.
- Read the refusal as your processor recorded it. An allowance breach, an expired credential and a binding mismatch are 3 different refusals, and the processor distinguishes them even where your logs collapse them into "declined".
- Count authorisation attempts on that session. More than one, under more than one idempotency key, is cause 4 — the only one that can leave a duplicate charge rather than none.
| Check | What you compare | Refusal signature | Where the fix lives |
|---|---|---|---|
| Allowance | Amount presented against the cap the token was issued for | Exceeds the authorised amount, cap unchanged | Your session: the total moved after approval |
| Expiry | Authorisation timestamp against the validity window | Expired, regardless of amount | Completion-handler latency, and when you request the token |
| Merchant binding | Acquiring account presenting it against the one it names | Refused before any amount check runs | Processor account mapping, usually multi-entity |
| Session binding | Session id on the token against the session completing | Not matching, or reversed on reconciliation | Session lifecycle: an id regenerated, retried or reused |
Cause 1: the total moved after the allowance was cut
The most common cause, and entirely a merchant-side defect. The shopper approved an amount, their provider issued a token capped at it, and something on your side then recalculated the total between the response the agent rendered and the authorisation you presented. The cap held, which is the system working.
- Tax resolved late. The address completed, a destination-based rate applied, and the total rose after the session had reported itself ready for payment.
- A fulfilment option defaulted. The agent chose no shipping method, your handler picked one at completion, and the line appeared after the cap was set.
- A promotion expired between confirmation and completion. Discount validity is evaluated at charge time on most platforms, and an agent session can straddle midnight.
- Rounding on a multi-line basket. Per-line tax rounding that differs from your quote's aggregate rounding produces a one-minor-unit difference, which is still a breach.
- A currency mismatch dressed as an amount problem. The token names one currency; a total in another is a different constraint failing.
The fix is a rule, not a patch: nothing may change the total once the session reports itself ready for payment. If something must, the session leaves that state and the agent obtains a new token. The figure the allowance was cut from came out of your own tool responses, which is why designing the tool surface a shopping agent is allowed to call treats response shaping as the whole job.
Cause 2: the credential expired while your handler was still working
Delegated tokens expire in minutes, not days, and that short window is most of what makes them safe to hand to software you did not write. It also makes your completion handler's tail latency a payment characteristic. If p99 on that handler is 40 seconds because it reserves stock in an ERP synchronously and calls a tax service, you lose authorisations on the slow tail and nowhere else.
Cause 3: the token belongs to a different merchant or a different session
Binding failures cluster in 2 places. The first is any retailer with more than one legal entity or acquiring account — brands under one group, a marketplace entity beside a first-party one, an account per market. The token names the merchant the shopper approved; present it from the sibling account and it is refused before any amount is examined, which reads in your logs as an inexplicable decline on a valid basket.
The second is a session id that did not survive: recreated by a retry path, regenerated on a cart merge, reused on a second attempt. A token bound to the original id and presented against the replacement is indistinguishable from an attack, and should be — a system that accepts a token against the wrong session has given up the property the binding exists for.
Cause 4: the retry that leaves you with 2 charges, or none
Idempotency keys exist so a network failure does not become a second charge. The first request under a key has its status code and body stored; later requests presenting the same key get that stored result back instead of executing again. Providers retain keys for a bounded window — Stripe documents at least 24 hours — after which a key is pruned and becomes a new request. The agentic checkout specification carries the idea into commerce with an Idempotency-Key on every call and an error code for a request that is not idempotent.
- A retry with a fresh key is a new charge attempt. That is how one confirmation becomes 2 authorisations, and it happens whenever an outer layer generates the retry without the original key.
- A retry with the same key but changed parameters is rejected, not executed. Providers compare incoming parameters against the stored ones, so a retry that recalculates the total fails as a mismatch, not a decline.
- A retry outside the retention window silently becomes a first request. Anything replayed from a dead-letter queue a day later is no longer protected by the key it carries.
- A retry that never reaches you looks identical to a refusal. If your edge challenged the second attempt, the order is unpaid because of your own bot rules — traced in your bot protection is blocking the AI surfaces you are trying to sell through.
Unwind the order before you touch the money
The asymmetry that decides the recovery order: fulfilment is faster and less reversible than settlement. A pick list printed in a warehouse cannot be recalled by a payments team. Stop the goods first, then work out what happened to the money.
- Block fulfilment by a hard state, not a flag some downstream job may ignore. An order without a recorded authorisation must be structurally unpickable.
- Establish whether anything was authorised. Query the processor by order reference, not your internal payment record — the failure you are investigating is one where those 2 disagree.
- Void rather than refund any stray authorisation. A void releases a hold; a refund creates a settled outbound movement, a line on the shopper's statement, and a support conversation you did not need.
- Decide whether the session is recoverable. Cancelled or completed is terminal; still open with a total problem is correctable in place.
- Reconcile the order last. Move it to a named state — awaiting payment, expired, or cancelled by merchant — and stop reporting counting it as revenue in the same run.
The dangerous minute is not the one where the charge fails. It is the one where the order exists, the warehouse job already fired, and nothing has told either side to stop.
Telling a shopper you may have no way to reach
The shopper may exist to you only as a buyer record on a session — no login, and no consent usable for anything but this transaction. The message has to travel back through the surface it came from, which means knowing which surface that was: tag the order at session creation, because afterwards there is no referrer to recover it from, as telling which orders came from an AI surface sets out.
Say what state the order is in — "we could not take payment, so nothing has been charged and nothing is being shipped" answers the question they actually have. Never ask for card details in a recovery message: no card data was ever meant to travel, and an email asking for a card is indistinguishable from a phishing attempt. Give support the refusal in words, because "payment_declined" is neither answerable nor, in these 4 cases, accurate.
What we build here, and what we deliberately do not
BuildspaceLabs integrates payment providers. We are not a payment institution, we hold no financial licence, and no funds pass through anything we run. We build the merchant side: the completion endpoint, the check that a token matches the session it claims, the state machine that refuses to create a shippable order before authorisation returns, and the job that finds the orders where those went wrong. That is MVP and product build work with payments inside it, in our retail and e-commerce practice.
What a clean authorisation still will not guarantee
All of this gets the money and the order to agree. It says nothing about whether the order was right: an authorisation that succeeds against the wrong variant never appears in a payments log, and is traced in the size chart and the SKU disagree. Ordinary card declines and fraud rules are a different investigation too — those fail before the constraints are evaluated.
The rest of the handshake sits in agentic commerce. Take the constraint behaviour from there and the field names from whichever specification you implement: the checkout revision we read pins an API-Version dated 2025-09-12, and property names have moved between revisions before.
Frequently asked questions
Short answers to the follow-ups this page tends to raise.
Why did the payment fail after the agent already confirmed the order?
Because confirmation and authorisation are 2 separate events and only the second moves money. The agent confirmed from your session response; the charge then went against a delegated token with a capped amount, a currency, a short expiry and a binding to one merchant and one session. If the total moved, the token aged out or the binding failed, the charge is refused while the order already exists.
Should we retry the charge or cancel the order?
Retry only if the session is still open and you know which constraint refused. A total above the allowance can be corrected and re-authorised against a new token; an expired token cannot be retried, because the credential is dead. Retrying blindly under a new idempotency key is how one failure becomes 2 charges.
Can we charge the difference separately when the total exceeded the allowance?
No. The allowance is the amount the shopper approved, so a second charge for the balance is money they never authorised — and in a dispute, indistinguishable from overcharging. Correct the total, or have the agent obtain a new token for the correct figure.
How do we stop an unpaid order from being picked and shipped?
Make authorisation a precondition of the order state itself, not a flag on it. If no status can become shippable without a recorded authorisation reference, no downstream system has to remember the rule. Flags get ignored by exactly one integration, and it is always the one printing the pick list.
- agentic commerce
- payments
- checkout
- incident response
The work behind this page
Builds from our portfolio that this page draws on.
Read next
- The delegated payment token: an allowance with an expiry, not a card numberThe credential an AI agent hands your checkout is not a card number. It is a reference to a vaulted card wrapped in 4 constraints, and those constraints — not the encryption around them — are what limit an agent's spending.definition
- The agentic checkout session: a cart your server ownsA server-side basket the merchant owns and an AI agent only renders. Every create, update or complete call returns the merchant's full current state — the property that makes agent buying safe to allow.definition
- The agent shows one total and your checkout session returns anotherThe shopper saw one number and your server charged another. Replay the call sequence: the agent is nearly always displaying a response that a later call replaced.diagnostic
- Agents fetch your manifest and still skip your storeA file that loads in your browser can be unreachable, unparseable or disqualifying to an agent. Three verdicts, and the ordered checks that tell them apart.diagnostic
- The assistant keeps recommending products you stopped sellingA shopping assistant naming products you withdrew is rarely hallucination. It is a stale index, a missing lifecycle field, an unenforced tool call or a tool returning unpublished rows.diagnostic
- The catalogue tool server: what it may expose, what it must never returnA read-only surface an agent may call over your catalogue. The definition is the boundary: published data and coarse availability in, cost price and customer-keyed data out.definition
Related across the site
Working on something in this space?
Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.
Start the conversation