💡 Human users: This page is intended for AI agents. It contains the Standard Operating Procedure for Z-ZERO payment tasks. For setup instructions, see /docs/agent.
# Z-ZERO Agent Skill — Payment SOP
Version: 2.0 | Updated: 2026-07-26
(v2.0 is the only published SOP. The old /docs/sop URL redirects here.)

⚠️  AGENT: Read this ENTIRE page before using any payment tool.
    This is your SOP. Missing a step causes checkout failures.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## PART 1: PAYMENT WORKFLOW (SOP)

You are connected to the Z-ZERO MCP Server. You will NEVER see or handle a real credit card number.
Follow this checklist precisely — in order.

### Step 0 — Proactive Info Collection (DO THIS FIRST)

When the user sends a product link and asks to buy:
1. IMMEDIATELY ask for shipping info in ONE message:
   - Full name
   - Shipping address (street, city, state/province, country, postal code)
   - Email
   - Phone (if required by merchant)
   - Product options (size, color, quantity) if applicable

2. Do NOT wait until you reach the shipping form. Ask NOW.

3. Start browsing the product page IN PARALLEL while user responds.

Special cases:
- "use my old info" → recall from conversation history
- Digital goods (software, API key, download) → only email needed, skip address
- Use judgment — this is where LLM intelligence matters

### Step 1 — Verify Intent & Price

- Confirm exactly what to buy and the expected price in USD
- If unknown, browse the merchant's pricing page first

### Step 2 — Check Balance

- Call check_balance with the user's card_alias (default: Card_01)
- If insufficient → STOP and ask user to top up at /dashboard/agent-wallet

### Step 3 — Platform Detection (Speed Boost)

Before browsing checkout, detect platform:

| URL signal | DOM signal | Platform key | What you get |
|---|---|---|---|
| etsy.com in URL | — | _platform_etsy | Full flow: variations → cart → guest → shipping → card → review |
| /checkouts/ in URL or cdn.shopify.com scripts | window.Shopify JS object | _platform_shopify | Card selectors + iframe handling |
| No URL match | woocommerce in body class | _platform_woocommerce | WooCommerce Stripe Elements |
| No match | — | use domain from URL | Domain-specific hints if any |

Call: get_merchant_hints(platform_key_or_domain)
→ Returns pre_steps and selectors from DB
→ This is 4× faster than generic mode

### Step 4 — Card Issuance Gate

Only call request_payment_token when BOTH are true:
1. Shipping info has been submitted on the form
2. Card input fields (or payment iframe) are visible on current page

If EITHER is false → DO NOT request token. Keep navigating first.

Find the FINAL total (after shipping + tax) before requesting token.

### Step 5 — Request JIT Token

- Call request_payment_token with exact amount and merchant name
- Pass criteria: the few things your owner actually stated (item, price ceiling,
  colour, size, delivery — whatever they said, and nothing they didn't). Someone who
  said "a hat, $10" gave you exactly two; inventing three more marks a good purchase
  as wrong. Locked and signed now, before the outcome is known.
- You receive a token (temp_auth_...). Valid for 1 hour. Single-use.

### Step 6 — Execute Payment — TWO CALLS

- FIRST call execute_payment with token + checkout_url, and NO recheck.
  Nothing is charged, no card is touched. It hands you back the criteria from Step 5.
- Look at the checkout page once more. Write down what is actually there — item,
  variant, quantity, final total — and hold it against what your owner asked for.
- SECOND call: execute_payment with recheck = {page_shows, decision, why?}
  · go    → the bridge injects card data, clicks Pay, watches for a REAL confirmation
  · pause → nothing is charged, the card is never typed in, the hold stays refundable.
            Tell your owner what did not line up.
- Pausing costs nothing. Catching a mismatch here costs nothing; after go it costs a card.
- What you declare is signed into the receipt next to what settled — so the purchase can
  be shown to have been made for a stated reason, not merely to have happened.

Outcomes it reports back:
| status | Meaning | Token | Your move |
|---|---|---|---|
| confirmed | order placed and confirmed | burned (USED) | done — report the receipt |
| declined | merchant rejected the card | released, hold refunded on-chain automatically | tell the user; do NOT retry the same card |
| unconfirmed | submitted, no confirmation seen | left ACTIVE (refunded when the 1h TTL lapses) | verify with the merchant BEFORE retrying — double-charge risk |
| not_submitted / no_fields | never reached Pay | left ACTIVE (auto-refunded) | get a submit_selector hint, or report the failure |
| purpose_check | first call — nothing charged | untouched | read the criteria, look at the page, call again with recheck |
| paused_by_agent | you chose to pause | left ACTIVE (refundable) | tell your owner what did not line up; cancel_payment_token to release the hold |

The $0.10 issuance fee is charged once per token and is never refunded; the held
amount always comes back on anything other than a confirmed charge.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## PART 2: HYBRID EXECUTION MODEL

LLM handles thinking. Scripts handle clicking.

🧠 LLM territory:
- User conversation and context ("use old info", "this is digital")
- Platform recognition and exception handling
- Unexpected popups, CAPTCHAs, errors

🤖 Script territory (NO LLM per click):
- For known platforms → follow pre_steps from get_merchant_hints as a deterministic script
- Each click follows the pre_steps list in order
- Only fall back to LLM when a scripted step fails or something unexpected appears

WHY: Each LLM call adds ~2-3s latency.
- 11-step Etsy checkout with LLM-per-step = ~25s overhead
- Same flow with script-first = near-zero overhead

RULE: If you got pre_steps from get_merchant_hints → follow them mechanically.
      Do NOT reason about each step individually. Just execute in order.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## PART 3: AUTO-ROUTING (Web3 vs Fiat)

When you call auto_pay_checkout, Z-ZERO auto-detects the best payment path:

Detection flow:
  auto_pay_checkout(url)
         │
         ▼
  Does page have window.ethereum or EIP-681 links?
         │
        YES ──────► Path 2: USDC on Base (gasless smart account)
         │
         NO
         │
         ▼
                    Path 1: JIT Visa Card
                    request_payment_token → execute_payment (purpose check) → execute_payment (go)

Payment Paths Summary:
| Path | Mechanism | Playwright? | Fail Risk |
|---|---|---|---|
| 1: JIT Visa | Playwright form fill | Yes | Medium |
| 2: Gasless USDC (Base) | Coinbase Smart Account + Paymaster | No | Low |

For physical goods → auto_pay_checkout may return PRICE_NOT_FOUND (price only appears after shipping).
In that case: use browser tools manually, follow Part 1 SOP above.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## PART 4: FAILURE REPORTING (Self-Healing Loop)

Every failure you label becomes evidence that helps the next agent. Failed runs are
also labeled automatically from the browser outcome — your report adds what only you
saw.

RULE: "Did I successfully complete the purchase the user asked for?"
- NO → report_checkout_fail(url, failure_class, step, error_message)
- YES → done, no report needed

failure_class is a FIXED ENUM. Pick the closest one; use 'unknown' only as a last
resort and describe what you saw in error_message:

  card_declined_issuer     bank rejected the card
  card_declined_bin_block  merchant refuses prepaid/virtual cards
  avs_mismatch             billing address rejected
  3ds_required             extra verification / SCA screen appeared
  bot_detected             CAPTCHA, Cloudflare, "unusual activity"
  form_changed             expected field or button not found
  price_changed            total differs from what was authorized
  out_of_stock             item unavailable at checkout
  shipping_unsupported     cannot ship to this address
  login_required           checkout demands an account
  timeout                  page or flow timed out
  outcome_unconfirmed      submitted but no confirmation seen
  intent_mismatch          page does not match what the user asked for
  unknown                  none of the above

step (optional): navigate · pre_steps · fill_form · submit · confirm

REPORT these (form/website is the cause):
✅ Form too complex, don't know how to fill
✅ Card fill failed (wrong selector, unknown iframe)
✅ Page blocked by CAPTCHA or bot detection
✅ Unexpected redirect or popup
✅ Checkout page does not match the user's request → intent_mismatch

DO NOT REPORT these (user/money is the cause):
❌ User cancelled or said "stop"
❌ Insufficient balance
❌ Token expired before you reached checkout
❌ Price mismatch (handled via cancel_payment_token)

NEVER put card numbers in error_message. They are stripped at capture, but do not
rely on that.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## PART 4b: PROVE THE PURCHASE (signed intent + receipt)

Do not tell the user "I bought it" from memory. Prove it.

BEFORE the token — compare the checkout page with what the user actually asked for:
same items, same quantity, same variant/size/colour, same destination. If anything
differs, do NOT request a token. Fix the cart or ask. A mismatch caught here costs
nothing; after the token it costs a card.

WHEN requesting the token — pass the cart:

  request_payment_token(card_alias, amount, merchant,
                        cart=[{title, qty, unit_price}], ship_to="...")

Z-ZERO signs that statement and binds the card to it. The user now has proof of what
the card was authorized to buy — not just how much it could spend. The shipping
address is stored as a hash, never raw.

AFTER a confirmed payment — execute_payment returns signed_receipt:

  { receipt_id, receipt_hash, match, diff, verify_url }

- diff = what the merchant actually did vs what was authorized. If it is non-empty,
  tell the user plainly (e.g. charged more than authorized).
- Give the user verify_url. The page is public; anyone can check it.
- verify_receipt(receipt_id) re-checks it any time.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## PART 5: CRITICAL RULES (Never Break These)

- NEVER print token in chat — treat as sensitive even though temporary
- NEVER loop: if execute_payment returns success: false → inform user, do NOT retry
- NEVER accept card numbers in chat
- PRICE MUST MATCH: if checkout shows higher price than token → cancel_payment_token first, then request new token
- PHYSICAL GOODS: Always collect shipping info BEFORE navigating to checkout

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

## PART 6: MAINTENANCE (Run at session start)

✅ MCP version warnings arrive automatically via the X-MCP-Version response header
   → If a response includes an update warning, relay it to your human user — no separate tool call needed

✅ Platform hints are in the DB — no need to memorize selectors
   → Always call get_merchant_hints(domain) to get latest selectors
   → DB is updated as new checkouts are discovered
Z-ZERO Agent Skills — z-zero.xyz/docs/agent/skill