In short: when an agent may check out through more than one merchant adapter, use Kit’s multi-provider commerce router. Paybond stays the authorize → prove → release → receipt layer — not a buy-anything HTTP proxy.
Shopify-only vs multi-provider
| Helper | Use when |
|---|---|
instrumentShopifyCheckout / paybond_kit.shopify | You only ever check out on Shopify |
instrumentCommerceCheckout / paybond_kit.commerce | One commerce.checkout tool routes across Shopify, Stripe, and/or Zinc |
Both inject tenant_id and paybond_intent_id from the Paybond session binding — never from unauthenticated tool args.
Scaffold
terminal
# TypeScript
paybond init --template commerce-checkout-agent
cp .env.example .env.local
paybond login
npm install
npm run smoke
# Python
paybond init --template commerce-checkout-agent --language python
cp .env.example .env.local
paybond login
uv sync # or: pip install -e .
paybond agent sandbox smoke --policy-file paybond.policy.yaml --operation commerce.checkout --requested-spend-cents 4500 --evidence-preset cost_and_completion --result-body '{"status":"completed","cost_cents":4500,"provider":"zinc","order_id":"zinc_sandbox_ord_amazon_B00SMOKE"}' --format json
# Or demo main: python app.pyWire middleware
Prefer instrumentCommerceCheckout so session binding is injected on every call. Tenant and intent IDs come from the Paybond session — never from tool args.
paybond-session.ts
import { Paybond } from "@paybond/kit";
import {
createShopifyCommerceProvider,
createStripeCommerceProvider,
createZincCommerceProvider,
instrumentCommerceCheckout,
} from "@paybond/kit/commerce";
const shopify = createShopifyCommerceProvider({
executeCheckout: async (input) => {
// UCP / storefront with input.checkoutPayload (note_attributes already bound).
return {
status: "completed",
cost_cents: input.amountCents,
order_id: "gid://shopify/Order/123",
shop: input.shopDomain,
};
},
});
const stripe = createStripeCommerceProvider({
executeCheckout: async (input) => {
// PaymentIntent create/confirm with input.metadata (tenant + intent bound).
return {
status: "completed",
cost_cents: input.amountCents,
payment_intent_id: "pi_test",
};
},
});
const zinc = createZincCommerceProvider({ mode: "sandbox" });
const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! });
const instrumented = await instrumentCommerceCheckout(paybond, {
policy: "./paybond.policy.yaml",
providers: { shopify, stripe, zinc },
defaultProvider: "shopify",
});
instrumented.bindingRef.tenantId = tenantId;
instrumented.bindingRef.intentId = intentId;
await instrumented.bind({ intentId, capabilityToken });Tool args select the adapter with provider (defaults to defaultProvider). Provider payloads live under shopify, stripe, or zinc.
Policy
tools:
commerce.checkout:
side_effecting: true
max_spend_cents: 10000
evidence_preset: cost_and_completion
paybond policy init --preset shopping scaffolds the same shape.
Per-provider binding
| Provider | What gets stamped | Your job |
|---|---|---|
| Shopify | Checkout note_attributes (tenant_id, paybond_intent_id) | Call UCP/storefront with the stamped payload |
| Stripe | PaymentIntent metadata (+ optional settlement rail) | Create/confirm with the stamped metadata |
| Zinc | Session ids on the adapter request | Use sandbox offline, or pass a live httpClient |
Related
- Protect Shopify payments from agents — Shopify-only helper
- Protect Stripe payments from agents — Stripe charge tool pattern
- Docs: Commerce checkout docs