Executive summary
AI shopping agents are graduating from demos to production scenarios, where they assemble carts, apply coupons, and attempt payment on behalf of users. The challenge: cardholder data (CHD) and strong customer authentication (SCA) rules make “let the bot type a card number into a checkout form” both unsafe and noncompliant. What you actually want is an architecture where the agent never sees CHD, 3‑D Secure 2 (3DS2) challenges are handled correctly, and money only moves after deterministic, idempotent commits. This article proposes a practical, production-ready design:
- Keep the agent out of PCI scope by using hosted payment fields and network tokens. The agent cannot enter or read PANs.
- Drive 3DS2 with the right data elements and a clean handoff to the cardholder when a challenge is required.
- Sandbox carts as draft orders with deterministic digests so that agent retries don’t duplicate purchases.
- Enforce human-in-the-loop (HITL) spend guards based on policy: merchant allowlists, budget thresholds, and risk signals.
- Commit orders through an idempotent protocol that can recover from partial failures and provides reversal hooks (voids/refunds).
The audience here is technical: we’ll cover concrete components, error budgets, data contracts, and code sketches you can adapt to your stack.
- The problem: PCI scope, SCA, and automated browsers
AI agents operate in a gray zone. They need to:
- Navigate merchant sites—often with fragile HTML, anti-bot measures, and dynamic carts.
- Provide payment credentials—subject to PCI DSS rules and issuers’ SCA policies.
- Handle unexpected detours: out-of-stock, re-pricing, split shipments, and different risk outcomes.
Threat model highlights:
- If the agent ever reads or transmits PANs, CVV, or full magnetic stripe data, you have drastically expanded your PCI scope. That adds cost and risk.
- If you bypass authentication or mishandle 3DS2, you can cause declines or increase liability.
- If the agent double-submits or times out mid-transaction, you can create orphaned captures or dangling orders, leading to reconciliation pain.
Non-goals:
- We will not rely on browser hacks to bypass issuer challenges.
- We will not design the agent to be a surrogate cardholder; the user remains the cardholder.
Design goals:
- Agent never touches or learns CHD.
- Successful checkout requires legitimate cardholder authentication where relevant (3DS2), with deterministic handoffs.
- Every order commit is provably idempotent and reversible if any downstream step fails.
- Human approval policies gate spending without introducing unnecessary friction for low-risk, low-value buys.
- Architecture overview
We’ll define a layered system that sits between the AI agent and merchant checkouts. High level:
- AI Agent: Browser automation and scripting; manipulates the cart only with non-PCI operations.
- Checkout Orchestrator (yours): Mediates a standardized, safe checkout sequence.
- Hosted Payment Fields (PSP): iFrames or payment sheets owned by a PCI-compliant provider.
- Token Vault (Network Tokens preferred): Stores account credentials beyond PAN; the merchant/you hold a surrogate.
- 3DS Server + ACS challenge: Orchestrates authentication with the issuer.
- Human-in-the-Loop Console: Approvals based on policy and risk.
- Draft Cart Store: Ephemeral order state, immutable pricing digests, and risk context.
- Commit Service: Idempotent capture + order creation, with reversal hooks if anything fails post-capture.
Text diagram:
AI Agent --> Checkout Orchestrator --> Draft Cart Store |--> Risk/Rules Engine (HITL trigger) |--> 3DS2 Server <--> Issuer ACS |--> Hosted Fields (PSP) -> Token Vault |--> Commit Service -> PSP Capture/Auth |--> Reversal Hooks (Void/Refund) |--> Audit/Observability
Key separations:
- PCI boundary: hosted fields and PSP tokenization live outside your agent and your general web app code. Your servers handle tokens, not PANs.
- Authentication: 3DS2 flows must either be frictionless (no challenge) or routed to the user to complete a challenge.
- Mutability: draft carts are mutable until commit; once committed, all writes go through an idempotent contract.
- Keeping the agent out of PCI scope
The central rule: the AI agent must never process primary account number (PAN) or CVV.
- Use hosted payment fields/iFrames: Create a payment form where sensitive fields are iFrames served directly from your PCI DSS Level 1 payment service provider (PSP). Your DOM gets back only a short-lived token representing the entered card.
- Prefer network tokens over gateway tokens: Network tokens (e.g., Visa Token Service, Mastercard MDES, American Express tokenization) are provisioned by schemes and can be more resilient to card lifecycle events (reissues, expirations). Gateway tokens are still useful but may not offer the same lifecycle guarantees.
- Enforce strict Content Security Policy (CSP) and trusted types: Disallow inline scripts near payment surfaces and whitelist only PSP domains. This reduces injection risk around the iFrames.
- Never scrape or autofill card data: The AI agent may fill shipping address, contact info, promo codes, and selections, but payment input is off-limits.
- Cookie and storage hygiene: Mark all session cookies Secure and HttpOnly; use SameSite=Lax/Strict as appropriate. Avoid storing any payment-related tokens in localStorage; treat them as secrets with short TTLs and bind them to server sessions.
- Alternative rails: Payment Request API and digital wallets (e.g., browser wallets) can further reduce exposure. The agent can trigger wallet selection but should not read wallet payloads; your server receives the cryptogram/token from the PSP.
Data flow when the user must add a new card:
-
Your UI displays hosted fields (PSP iFrames) to the human user, not the agent.
-
The user enters PAN/CVV/expiry; the PSP returns a single-use token to your backend.
-
You exchange that token for a vaulted network token (customer-bound) and store only the token reference and last4/brand/expiry metadata (non-sensitive). The PAN was never in your app.
-
Future payments use the network token and cryptograms; the agent only references the stored payment method by an internal ID.
-
Driving 3‑D Secure 2 correctly
3DS2 splits into two paths:
- Frictionless: The issuer approves based on risk data; no challenge UI. You still must provide rich RBA (risk-based authentication) data elements: billing/shipping, device signals, account age, prior transactions, etc.
- Challenge: The issuer requires cardholder interaction. For agents, this means handoff to the human cardholder. The agent must not attempt to “solve” the challenge.
Implementation tips:
- Integrate a 3DS2 server via your PSP or a dedicated 3DS server provider. Ensure support for both browser and app-based flows.
- Send the maximum data set: email, phone, address match indicators, account age, transaction shipping/billing mismatch, device ID, user authentication method (e.g., WebAuthn passkey at your site), prior transaction count. Higher data quality correlates with more frictionless approvals.
- Timeouts and retries: 3DS flows have strict time windows (commonly 5–10 minutes). Build state machines that gracefully expire a draft cart if the user abandons the challenge, and clearly report the state back to the agent.
- Challenge UX: If your checkout runs in a browser controlled by an agent, don’t embed the challenge in the agent’s viewport. Instead, generate a user-specific approval link or app push notification to the cardholder’s device. Alternatively, embed the challenge iframe only in a human session flagged as “trusted human” (WebAuthn/OTP gate before showing it).
- Delegated authentication: If your app supports delegated authentication with the issuer’s app, let the user complete SCA on their phone. The agent just waits for webhook/callback from the 3DS server that the CAVV/ECI is ready.
- Store authentication results: Persist ECI, CAVV, DS transaction ID, and liability shift indicators along with the order record. You’ll need these for chargeback disputes and audit.
A typical 3DS2 challenge handoff for an AI agent:
-
Agent prepares the cart and requests “pre-auth.”
-
3DS server evaluates and returns either frictionless approved (OK to proceed) or challenge required.
-
If challenge required, orchestrator notifies the HITL console and the user via push/link. The agent is paused.
-
User completes the challenge; orchestrator receives success callback.
-
Proceed to commit. If the user fails or times out, abort and return a deterministic failure to the agent.
-
Human-in-the-loop (HITL) spend guards
Agent autonomy should be policy-bound. Typical guardrails:
- Budget thresholds: Require human approval above a per-transaction or daily cap.
- Merchant allowlists/denylists: Auto-approve known safe merchants; escalate unknown or risky ones.
- Category rules: Office supplies OK; electronics above $300 require approval.
- Risk score gates: Blend signals—new merchant, high-value, mismatch in billing/shipping, velocity spikes—and escalate to HITL.
- Challenge gating: Any 3DS2 challenge automatically routes to the human.
HITL console features:
- Snapshot diff: Show cart items, unit prices, shipping, tax, coupon code, total, and the computed digest. Highlight any price drift since the agent proposed the cart.
- Card selection: Let the user choose among stored tokens; show brand, last4, and SCA requirement hints (never reveal PAN).
- One-click approve/deny: Approval records a signed decision with actor ID and reason code.
- SLA feedback: If approval takes too long, the agent may cancel or attempt a different merchant; show countdowns.
- Audit log: Immutable events for each approval/denial; include IP/device of approver and policy version.
Policies should be code, not tribal knowledge. Externalize rules to a policy engine (e.g., OPA/Rego-style or a simple DSL) and version them. Every decision binds the policy version to the order record.
- Sandbox carts and deterministic order models
Let the agent manipulate a “draft cart” that is not yet an order. To make retries safe:
- Immutable digests: Derive a canonical digest of the intended purchase, e.g., a SHA-256 hash over a sorted JSON payload containing SKU, quantity, unit price, currency, shipping method, discounts, taxes, and expected total, plus merchant ID and a timestamp window.
- Offer expiration: Attach a not-after timestamp and allowable drift rules (e.g., price can change by ≤1% due to tax recalculation, otherwise require re-approval).
- Identities: Bind the draft to a user ID, merchant account ID, and policy snapshot.
- Concurrency locks: Use short TTL locks keyed by the digest to prevent parallel attempts that could double-auth.
Example draft cart schema (abridged):
json{ "draft_id": "dc_01H...", "user_id": "usr_123", "merchant": { "domain": "shop.example", "merchant_id": "mrc_987" }, "line_items": [ {"sku": "ABC-123", "qty": 2, "unit": {"amount": 1299, "currency": "USD"}}, {"sku": "CABLE-1M", "qty": 1, "unit": {"amount": 899, "currency": "USD"}} ], "shipping": {"method": "ground", "amount": 599}, "tax": {"amount": 312}, "discounts": [{"code": "WELCOME10", "amount": 200}], "expected_total": 3309, "address": {"country": "US", "postal_code": "94107"}, "digest": "sha256:...", "expires_at": "2026-06-01T12:00:00Z", "policy_version": "spendpol_v5", "status": "awaiting_approval" }
Build deterministic pricing: Ensure all calculations are server-side with consistent rounding and tax logic. Store both pre- and post-tax subtotals to diagnose drifts caused by the merchant recalculating shipping at commit.
- Idempotent order commits
Committing an order is two-phase in spirit: reserve funds, then create a durable order, then finalize capture. However, gateways differ. A robust pattern:
- Payment Intent model: Create/confirm a payment intent with the PSP using a customer-bound network token. Attach 3DS2 data (ECI/CAVV) if available. Attempt auth-only (not capture) first when you expect shipping delays, or auth+capture for digital goods.
- Idempotency keys: Every commit attempt carries a stable idempotency key derived from the draft digest plus a monotonic sequence if you allow re-pricing. If you retry after a network blip, reuse the same key.
- Exactly-once semantics: Your Commit Service must store the idempotency key with the terminal result (success with receipt; failure with reason). Any duplicates return the recorded result without re-hitting the PSP.
- Atomicity with external effects: Because PSP calls are outside your transaction boundary, you need compensating actions. If capture succeeds but order write fails, enqueue a reversal (void same-day, refund next-day) and alarm.
Idempotency key strategy:
- Key = HMAC-SHA256(policy_version || digest || user_id || merchant_id || total || currency), with a server-side secret.
- Include a replay window (e.g., 48 hours) for which the key remains sticky to the same result.
Reversal hooks and recovery:
- Void if settlement has not happened (same-day or depending on acquirer cut-off).
- Refund partial or full if capture already settled.
- Always reconcile with PSP events and bank cut-off windows.
- Emit structured events (order_committed, capture_succeeded, commit_rolled_back) to your observability pipeline.
- Orchestration flow end-to-end
Sequence diagram (text):
-
Agent -> Orchestrator: CreateDraft(draft_payload)
-
Orchestrator -> RiskEngine: Score(draft)
-
RiskEngine -> Orchestrator: decision=approve_or_HITL
-
If HITL or 3DS challenge expected: Orchestrator -> HITL Console: ReviewLink(draft)
-
Human -> HITL Console: Approve/Reject
-
Orchestrator -> 3DS Server: Authenticate(tokenized_card, amount, RBA data)
-
3DS Server -> Issuer ACS: frictionless or challenge
-
If challenge: Human completes on device; 3DS Server -> Orchestrator: AuthResult(ECI,CAVV)
-
Orchestrator -> Commit Service: Commit(draft_id, idempotency_key)
-
Commit Service -> PSP: ConfirmIntent(Auth or Capt)
-
PSP -> Commit Service: success
-
Commit Service -> Orchestrator: Receipt(order_id, payment_id)
-
Orchestrator -> Agent: Result(success or detailed failure)
-
On any partial failure after capture: Commit Service -> PSP: Void/Refund
-
Example code sketches
TypeScript: generating idempotency keys and commit contract
tsimport crypto from 'node:crypto' type Draft = { digest: string policyVersion: string userId: string merchantId: string amount: number currency: string } export function makeIdempotencyKey(d: Draft, secret: string): string { const msg = `${d.policyVersion}|${d.digest}|${d.userId}|${d.merchantId}|${d.amount}|${d.currency}` return 'idem_' + crypto.createHmac('sha256', secret).update(msg).digest('hex') } export interface CommitRequest { draftId: string idempotencyKey: string } export interface CommitResponse { status: 'succeeded' | 'failed' orderId?: string paymentId?: string failureReason?: string }
Python: idempotent commit endpoint with reversal hooks (pseudo)
pythonfrom fastapi import FastAPI, Header, HTTPException from pydantic import BaseModel from typing import Optional app = FastAPI() class CommitRequest(BaseModel): draft_id: str class CommitRecord(BaseModel): status: str order_id: Optional[str] payment_id: Optional[str] failure_reason: Optional[str] idem_store = {} # replace with durable KV store @app.post('/commit', response_model=CommitRecord) async def commit(req: CommitRequest, x_idempotency_key: str = Header(...)): if x_idempotency_key in idem_store: return idem_store[x_idempotency_key] # Lock by idempotency key acquire_lock(x_idempotency_key) try: if x_idempotency_key in idem_store: return idem_store[x_idempotency_key] draft = load_draft(req.draft_id) if draft is None or draft.status != 'approved': record = CommitRecord(status='failed', failure_reason='draft_not_approved') idem_store[x_idempotency_key] = record return record # Confirm payment intent with PSP try: payment_id = psp_confirm_intent(draft) except PSPAuthError as e: record = CommitRecord(status='failed', failure_reason=f'auth_failed:{e.code}') idem_store[x_idempotency_key] = record return record except TimeoutError: # Retry or return failure; do not create order yet record = CommitRecord(status='failed', failure_reason='psp_timeout') idem_store[x_idempotency_key] = record return record # Write order atomically (best-effort) try: order_id = create_order_record(draft, payment_id) record = CommitRecord(status='succeeded', order_id=order_id, payment_id=payment_id) idem_store[x_idempotency_key] = record return record except Exception as e: # Compensate: void or refund try: psp_reverse(payment_id) except Exception: alarm('reversal_failed', {'payment_id': payment_id}) record = CommitRecord(status='failed', failure_reason='order_persistence_failed') idem_store[x_idempotency_key] = record return record finally: release_lock(x_idempotency_key)
Frontend (human session) initiating 3DS challenge via hosted fields (outline)
html<!-- iFrame containers from PSP; no PAN ever in your DOM --> <div id="card-number"></div> <div id="expiry"></div> <div id="cvc"></div> <button id="add-card">Add Card</button> <script> // PSP SDK mounts secure iFrames into these divs const psp = initPSP({ publishableKey: '...' }) const fields = psp.hostedFields({ number: '#card-number', expiry: '#expiry', cvc: '#cvc' }) document.querySelector('#add-card').addEventListener('click', async () => { const token = await fields.createToken() // Send token to backend; backend exchanges for network token and stores it await fetch('/vault/attach', { method: 'POST', body: JSON.stringify({ token }) }) }) </script>
- Risk and bot reality for agents
Automation is scrutinized by merchants: they deploy bot defenses, device fingerprinting, and velocity checks. Your orchestrator should:
- Respect robots from a compliance standpoint but recognize that many merchant checkouts are designed for humans. Maintain a pool of real browser instances with human-like timings and proper headers.
- Use authenticated accounts: Many merchants require logged-in checkout for coupons and saved addresses. Bind agents to user accounts with session management strictly separated from payment activities.
- Device fingerprint and session continuity: A consistent device/session improves 3DS frictionless rates; abrupt device changes can trigger challenges.
- Rate limit per merchant and per user; automatically back off on WAF challenges.
- Don’t circumvent 3DS or CAPTCHAs unlawfully. Any SCA that arises must be passed to the cardholder via HITL.
- Observability, audit, and compliance
Eventing model:
- Emit structured events for each stage: draft_created, risk_scored, hitl_requested, hitl_approved, threeDS_auth_started, threeDS_auth_succeeded/failed, commit_succeeded/failed, reversal_succeeded/failed.
- Correlate by draft_id, idempotency_key, user_id, and merchant_id.
- Metrics: approval rates, frictionless vs challenge split, auth and capture success rates, average time-to-approval, reversal rate, and refund leakage.
Logging and privacy:
- Never log PAN, CVV, or full expiry. Mask last4 and BIN only when permitted by your PSP agreement.
- Reduce PII wherever possible; store pointers to PII or irreversible hashes for correlation.
- Implement retention policies; purge drafts after expiration; archive orders according to legal requirements.
PCI DSS alignment (conceptual):
- Hosted fields keep you near SAQ A footprint by ensuring CHD never touches your environment.
- Strong CSP, code reviews, and third-party domain audits reduce script injection risk.
- Validate that your PSP handles tokenization and 3DS server responsibilities; maintain current AOCs (Attestations of Compliance).
- Edge cases and advanced scenarios
- Split shipments and partial captures: Prefer auth-only at commit and capture per shipment with the same payment intent where supported. Track remaining authorized amount and authorization extensions.
- Backorders and preorders: Use delayed capture; re-auth before capture if shipping is far out. Inform users that 3DS may be re-invoked depending on issuer rules.
- Subscriptions and MIT/CIT: Initial transaction is CIT with SCA; subsequent merchant-initiated transactions (MIT) can rely on stored mandate references. Preserve 3DS data and mandate IDs.
- Tips and variable final amounts: For services where final total changes, use incremental auth or addendum capture flows where permitted by your PSP.
- Gift cards, store credit, and split tenders: Apply stored-value first; any remainder follows the same 3DS/token path.
- Multi-merchant marketplaces: Each seller is a separate merchant of record. Create one draft per seller, commit atomically per seller; present a roll-up result to the agent. If one seller fails, present partial success with compensations.
- Cross-border SCA: Issuer behavior differs by region; in EEA/UK SCA is stricter. Provide strong RBA data and handle fallback to 3DS1 only where allowed/necessary.
- Digital goods vs physical: Digital often captures immediately; physical should consider auth-only then capture on shipment to align with card network rules.
- Putting it together: a reference flow
Let’s walk a realistic flow for an AI agent buying a monitor and a cable from merchant X.
- Agent builds the cart by visiting product pages, adding SKUs, and applying coupon WELCOME10. The agent provides shipping address and chooses ground.
- Agent calls Orchestrator: CreateDraft with line items, shipping, expected totals.
- RiskEngine scores: Known merchant, total $330.90, new shipping address. Policy says: HITL above $250 and for new addresses.
- Orchestrator pings HITL: “Approve $330.90 at shop.example.” The user opens the console, inspects line items, sees price and shipping, and approves using stored card Token T1.
- Orchestrator runs 3DS2: RBA data shows new address; issuer requests a challenge. The user completes challenge on their phone.
- Orchestrator posts Commit with idempotency key K. PSP returns successful capture.
- Order record is persisted; receipt is generated with ECI, CAVV, auth code, and reference IDs.
- Agent receives success with receipt. If the database write had failed after capture, Commit would have voided before responding or refunded after settlement if too late, and signaled failure to the agent with instructions to retry later (same idempotency key would retrieve the final canonical outcome).
- Testing, staging, and chaos
- PSP simulators: Use your PSP’s test modes to simulate frictionless and challenged outcomes; automate tests for both.
- Deterministic drafts: Fuzz test rounding, taxes, and currency conversions; verify digest stability across serialization paths.
- Idempotency chaos: Inject network timeouts between auth and order write; ensure reversal hooks trigger and leave the system in a consistent state.
- HITL latency drills: Randomly delay approval responses and confirm the agent respects SLAs and cancel windows.
- 3DS fallbacks: Test expired challenges, user cancels, wrong OTP/biometric, and repeated attempts.
- Bot defenses: Run canary agents to measure which merchants deploy new WAF rules and adapt pacing.
- Practical provider notes
- PSP feature map: Confirm support for network tokenization, 3DS server integration, Payment Intents (or equivalent), partial captures, idempotency keys, and webhooks with strong authenticity checks.
- Vault strategy: Prefer network tokens for lifecycle advantages; keep a migration path from gateway tokens.
- Webhooks: Verify signatures and implement deduplication; webhooks should reconcile your state machine, not drive it blindly.
- Secrets: Rotate PSP API keys; isolate environments; encrypt stored references and policy snapshots.
- Security hardening checklist
- Agent never sees or types PAN/CVV; only uses token references.
- Hosted fields/iFrames for all payment data entry.
- Strong CSP allowing PSP domains only; SRI for third-party scripts where feasible.
- Server-side calculation of totals; deterministic pricing digests.
- 3DS2 integration with full RBA data; proper challenge handoff to the human.
- HITL policies codified, versioned, and logged with each decision.
- Idempotent commit API with HMAC’ed keys and durable dedupe storage.
- Reversal hooks (void/refund) wired and tested with chaos drills.
- Observability: full event trail, correlation IDs, and dashboards.
- Data minimization: never log CHD; restrict PII; implement retention.
Conclusion
AI browser agents can safely and reliably execute purchases if you design the pipeline around three pillars: strict PCI isolation, correct SCA orchestration, and transactional integrity via idempotency and compensations. Keep the agent far from PANs using hosted fields and network tokens. Treat 3DS2 as a first-class authentication step with clean human handoffs. Model the purchase itself as a deterministic draft that graduates to an order only through an idempotent commit that can be reversed if external failures occur. Add HITL spend guards to prevent surprises and to align accountability with the cardholder.
This isn’t academic: with these components, you can achieve high auth rates, low chargeback exposure, and predictable reconciliation—even when the front end is a non-deterministic AI crawler. The result is a production-ready checkout architecture built for autonomy without surrendering compliance, security, or financial controls.
