diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json index e008d98..da66d3f 100644 --- a/.agents/plugins/marketplace.json +++ b/.agents/plugins/marketplace.json @@ -1,5 +1,8 @@ { "name": "shop", + "interface": { + "displayName": "Shop" + }, "plugins": [ { "name": "shop", diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json new file mode 100644 index 0000000..e605478 --- /dev/null +++ b/.cursor-plugin/marketplace.json @@ -0,0 +1,16 @@ +{ + "name": "shop", + "owner": { + "name": "Shopify" + }, + "metadata": { + "description": "Shop for agents: product discovery, checkout, and Shop order tracking across millions of stores" + }, + "plugins": [ + { + "name": "shop", + "source": "./plugins/shop", + "description": "Shop across millions of stores: find and compare over a billion products, build carts, check out with Shop Pay, and track orders and deliveries." + } + ] +} diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 5e97c99..5b9b902 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1 @@ -# Replace the individual with a team handle before the repository is made public. /.github/ @igrigorik diff --git a/.github/scripts/verify.mjs b/.github/scripts/verify.mjs index 672efc2..c96e778 100644 --- a/.github/scripts/verify.mjs +++ b/.github/scripts/verify.mjs @@ -15,9 +15,17 @@ const GOVERNANCE = ['.github/', '.gitattributes', '.gitignore', 'SECURITY.md', ' const PLUGIN_MANIFESTS = [ 'plugins/shop/plugin.json', 'plugins/shop/.claude-plugin/plugin.json', - 'plugins/shop/.codex-plugin/plugin.json', + 'plugins/shop/.cursor-plugin/plugin.json', ]; const PLUGIN_SOURCE = './plugins/shop'; +// One logo per format; each host overlay names it under its own field. Paths are plugin-relative. +const PLUGIN_LOGOS = ['assets/logo.svg', 'assets/logo.png']; +const LOGO_FIELDS = [ + ['plugins/shop/.cursor-plugin/plugin.json', ['logo']], + ['plugins/shop/.claude-plugin/plugin.json', ['icon']], + ['plugins/shop/plugin.json', ['extensions', 'com.openai', 'interface', 'logo']], + ['plugins/shop/plugin.json', ['extensions', 'com.openai', 'interface', 'composerIcon']], +]; const native = (posixPath) => join(root, ...posixPath.split('/')); const parseJson = (text, label) => { @@ -96,6 +104,11 @@ async function main() { const claudeEntry = (claude.plugins || []).find((entry) => entry?.name === 'shop'); assert.ok(claudeEntry, '.claude-plugin/marketplace.json: missing plugin entry named "shop"'); assert.ok(claudeEntry.source === PLUGIN_SOURCE, `.claude-plugin/marketplace.json: plugin "shop" source must be ${PLUGIN_SOURCE}`); + const cursor = readJson('.cursor-plugin/marketplace.json'); + assert.ok(cursor.name === 'shop', '.cursor-plugin/marketplace.json: name must be "shop"'); + const cursorEntry = (cursor.plugins || []).find((entry) => entry?.name === 'shop'); + assert.ok(cursorEntry, '.cursor-plugin/marketplace.json: missing plugin entry named "shop"'); + assert.ok(cursorEntry.source === PLUGIN_SOURCE, `.cursor-plugin/marketplace.json: plugin "shop" source must be ${PLUGIN_SOURCE}`); const agents = readJson('.agents/plugins/marketplace.json'); assert.ok(agents.name === 'shop', '.agents/plugins/marketplace.json: name must be "shop"'); const agentsEntry = (agents.plugins || []).find((entry) => entry?.name === 'shop'); @@ -103,7 +116,18 @@ async function main() { assert.ok(agentsEntry.source?.path === PLUGIN_SOURCE, `.agents/plugins/marketplace.json: plugin "shop" source.path must be ${PLUGIN_SOURCE}`); const pluginDir = native(PLUGIN_SOURCE.replace(/^\.\//, '')); assert.ok(existsSync(pluginDir) && statSync(pluginDir).isDirectory(), `${PLUGIN_SOURCE} is not a directory in this checkout`); - pass('both marketplace catalogs name "shop" and resolve to plugins/shop'); + pass('all three marketplace catalogs name "shop" and resolve to plugins/shop'); + + // (d2) every overlay's logo reference resolves to a shipped file + for (const logo of PLUGIN_LOGOS) { + const file = native(`plugins/shop/${logo}`); + assert.ok(existsSync(file) && statSync(file).isFile(), `plugins/shop/${logo} is missing; the plugin manifests reference it`); + } + for (const [manifest, keys] of LOGO_FIELDS) { + const value = keys.reduce((node, key) => (node && typeof node === 'object' ? node[key] : undefined), readJson(manifest)); + assert.ok(typeof value === 'string' && PLUGIN_LOGOS.includes(value.replace(/^\.\//, '')), `${manifest}: ${keys.join('.')} must name one of ${PLUGIN_LOGOS.join(', ')}; found ${JSON.stringify(value)}`); + } + pass(`logo references resolve to ${PLUGIN_LOGOS.length} shipped files`); // (e) the module runs under this Node and reports the package version const banner = shop('--version').toString('utf8'); diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e849e5e..4f0edfa 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -94,7 +94,7 @@ jobs: VERSION: ${{ steps.plan.outputs.version }} run: echo "::notice::@shopify/shop@$VERSION is already on the registry; nothing to publish." - # --ignore-scripts: the postinstall hook is for installers and does not run here. + # --ignore-scripts: the package declares no lifecycle scripts, and none may run here. # --provenance: explicit, because npm's automatic OIDC provenance is best-effort; a publish that cannot attest fails instead of shipping without provenance. - name: Publish if: steps.plan.outputs.private != 'true' && steps.plan.outputs.published != 'true' diff --git a/README.md b/README.md index 2d80fcf..d4e233c 100644 --- a/README.md +++ b/README.md @@ -277,7 +277,7 @@ shop order search "trail runner" -The results cover orders placed through this agent from any store, plus the shopper's other recent and in-progress orders that Shop tracks. Leave out the query to get the most recent orders. +The results cover every order Shop tracks for the shopper, from any store. By default only in-progress orders are shown; pass `--status all` to search the full order history. ## Approve a browser purchase @@ -426,7 +426,7 @@ Every command accepts `--arguments ` with the complete argument ob | 1 | no response from the network | | 2 | usage error | | 3 | sign-in required | -| 4 | the merchant or Shop answered with an error | +| 4 | the merchant or Shop answered with an error, or a precondition failed | Errors use the same JSON envelope, and their `next` steps say what to do: diff --git a/lib/shop.wasm b/lib/shop.wasm index 80b8026..e23aa42 100644 Binary files a/lib/shop.wasm and b/lib/shop.wasm differ diff --git a/package.json b/package.json index 84238b1..6fa20f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@shopify/shop", - "version": "1.1.1", + "version": "1.2.0", "description": "Shop for agents: product discovery, checkout, and Shop order tracking across millions of stores", "license": "MIT", "repository": { @@ -24,6 +24,7 @@ "skills", "plugins", ".claude-plugin", + ".cursor-plugin", ".agents", "README.md", "LICENSE" diff --git a/plugins/shop/.claude-plugin/plugin.json b/plugins/shop/.claude-plugin/plugin.json index abd6c1c..6900969 100644 --- a/plugins/shop/.claude-plugin/plugin.json +++ b/plugins/shop/.claude-plugin/plugin.json @@ -1,8 +1,35 @@ { "name": "shop", - "version": "1.1.1", - "description": "Shop across merchants through the Universal Commerce Protocol", + "displayName": "Shop", + "version": "1.2.0", + "description": "Shop across millions of stores: find and compare over a billion products, build carts, check out with Shop Pay, and track orders and deliveries.", "author": { - "name": "Shopify" - } + "name": "Shopify", + "url": "https://shop.com" + }, + "homepage": "https://github.com/Shopify/shop", + "repository": "https://github.com/Shopify/shop", + "license": "MIT", + "keywords": [ + "shop", + "shop app", + "shop pay", + "shopify", + "shopping", + "buy", + "products", + "price comparison", + "cart", + "checkout", + "orders", + "order tracking", + "delivery", + "ecommerce", + "ucp" + ], + "icon": "./assets/logo.png", + "documentationUrl": "https://github.com/Shopify/shop#readme", + "supportUrl": "https://help.shop.app/", + "privacyPolicyUrl": "https://www.shopify.com/legal/privacy/consumers", + "termsOfServiceUrl": "https://shop.com/terms-of-service" } diff --git a/plugins/shop/.codex-plugin/plugin.json b/plugins/shop/.codex-plugin/plugin.json deleted file mode 100644 index 58461c2..0000000 --- a/plugins/shop/.codex-plugin/plugin.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "shop", - "version": "1.1.1", - "description": "Shop across merchants through the Universal Commerce Protocol", - "author": { - "name": "Shopify" - }, - "skills": "./skills/", - "mcpServers": "./.mcp.json" -} diff --git a/plugins/shop/.cursor-plugin/plugin.json b/plugins/shop/.cursor-plugin/plugin.json new file mode 100644 index 0000000..257ee32 --- /dev/null +++ b/plugins/shop/.cursor-plugin/plugin.json @@ -0,0 +1,39 @@ +{ + "name": "shop", + "displayName": "Shop", + "version": "1.2.0", + "description": "Shop across millions of stores: find and compare over a billion products, build carts, and check out. Sign in with Shop to pay with Shop Pay, use your saved addresses, get results personalized to you, and track your orders and deliveries. At stores that don't support Shop Pay directly, approve the purchase and your agent completes it with a single-use card.", + "author": { + "name": "Shopify" + }, + "homepage": "https://shop.com", + "repository": "https://github.com/Shopify/shop", + "license": "MIT", + "logo": "assets/logo.svg", + "keywords": [ + "shop", + "shop app", + "shop pay", + "shopify", + "shopping", + "buy", + "products", + "price comparison", + "cart", + "checkout", + "orders", + "order tracking", + "delivery", + "ecommerce", + "ucp" + ], + "category": "integrations", + "tags": [ + "shop", + "shopping", + "ecommerce", + "shop-pay", + "mcp" + ], + "skills": "./skills/" +} diff --git a/plugins/shop/assets/logo.png b/plugins/shop/assets/logo.png new file mode 100644 index 0000000..1e3b4d5 Binary files /dev/null and b/plugins/shop/assets/logo.png differ diff --git a/plugins/shop/assets/logo.svg b/plugins/shop/assets/logo.svg new file mode 100644 index 0000000..74ef5b4 --- /dev/null +++ b/plugins/shop/assets/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/plugins/shop/plugin.json b/plugins/shop/plugin.json index 1a22ef6..d4031de 100644 --- a/plugins/shop/plugin.json +++ b/plugins/shop/plugin.json @@ -1,10 +1,61 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "shop", - "version": "1.1.1", - "description": "Shop across merchants through the Universal Commerce Protocol", + "version": "1.2.0", + "description": "Shop across millions of stores: find and compare over a billion products, build carts, check out with Shop Pay, and track orders and deliveries.", "author": { - "name": "Shopify" + "name": "Shopify", + "url": "https://shop.com" }, - "license": "MIT" + "homepage": "https://github.com/Shopify/shop", + "repository": "https://github.com/Shopify/shop", + "license": "MIT", + "keywords": [ + "shop", + "shop app", + "shop pay", + "shopify", + "shopping", + "buy", + "products", + "price comparison", + "cart", + "checkout", + "orders", + "order tracking", + "delivery", + "ecommerce", + "ucp" + ], + "extensions": { + "com.openai": { + "interface": { + "displayName": "Shop", + "shortDescription": "Shop millions of stores", + "longDescription": "Shop across millions of stores: find and compare over a billion products, build carts, and check out. Sign in with Shop to pay with Shop Pay, use your saved addresses, get results personalized to you, and track your orders and deliveries. At stores that don't support Shop Pay directly, approve the purchase and your agent completes it with a single-use card.", + "developerName": "Shopify", + "category": "Shopping", + "capabilities": [ + "Search over 1 billion products from millions of stores", + "Compare products, prices and options", + "Build carts and check out", + "Pay with Shop Pay and saved addresses", + "Track orders and deliveries", + "Approve single-use card purchases at stores without Shop Pay" + ], + "websiteURL": "https://shop.com", + "supportURL": "https://help.shop.app/", + "privacyPolicyURL": "https://www.shopify.com/legal/privacy/consumers", + "termsOfServiceURL": "https://shop.com/terms-of-service", + "defaultPrompt": [ + "Find a waterproof running jacket under $150 that ships to Canada", + "Compare ceramic coffee drippers under $40", + "What's on its way?" + ], + "brandColor": "#5433EB", + "composerIcon": "./assets/logo.png", + "logo": "./assets/logo.png" + } + } + } } diff --git a/plugins/shop/skills/shop/SKILL.md b/plugins/shop/skills/shop/SKILL.md index 19f33de..23e8a31 100644 --- a/plugins/shop/skills/shop/SKILL.md +++ b/plugins/shop/skills/shop/SKILL.md @@ -45,7 +45,7 @@ This is notation, not JSON. `?` means optional. Replace `RESOURCE` with `cart` o | `get_spend_request`, `cancel_spend_request` | `{id}`; cancel may add `meta`. Get may issue/replay a credential and is not read-only. | | `update_spend_request` | `{id,spend_request:{shipping_address?,line_items?,totals?,approval_limit?},meta?}`. | | `complete_spend_request` | `{id,result:{status:"success",order?:{id?,permalink_url?,total?,currency?}}}` or `{id,result:{status:"error",failure_code,message?}}`; present optional fields are non-null. | -| `search_orders` | `{query?,activity?,date_from?,date_to?,pagination?}`; omit query for recent orders. | +| `search_orders` | `{orders:{query?,filters?:{status?,date?:{from?,to?}},pagination?}}`; in-progress orders by default; `status:"all"` searches the full order history. | Use `seller.domain` for `shop`. Keep merchants separate. Copy returned IDs and URLs exactly, with every query parameter; do not rebuild or shorten them. ### Search diff --git a/plugins/shop/skills/shop/references/transactions.md b/plugins/shop/skills/shop/references/transactions.md index 50546d8..db7d621 100644 --- a/plugins/shop/skills/shop/references/transactions.md +++ b/plugins/shop/skills/shop/references/transactions.md @@ -18,7 +18,7 @@ Mutations accept `meta:{"idempotency-key":uuid}`. Reuse the UUID only for the sa 1. Inspect the latest checkout returned by `create_checkout`, `update_checkout`, or `get_checkout`, including status, items, totals, messages, and `payment.instruments`. If it may be stale, call `get_checkout` first. 2. Treat checkout status as authoritative. Resolve `incomplete` messages. For `requires_escalation`, give the user its `continue_url`. Proceed only from `ready_for_complete`; do not recreate Checkout's eligibility decision from `get_account` budget data. 3. Present the offered instruments and have the user confirm one exact instrument with the current merchant, items, quantities, and total. Copy that full object exactly as returned, preserving every field and extension; never choose a default, resolve by id/handler/display, or invent a credential or token. -4. Send `complete_checkout` exactly once; never repeat it, even with the same idempotency key. Pass the exact object to the CLI, which adds only root `selected:true`; other bindings set that one root member before sending: +4. Send `complete_checkout` exactly once; never repeat it, even with the same idempotency key. Add `selected: true` at the top level of the copied instrument, change nothing else, and send it as the single entry in `payment.instruments`: ```text {shop,id,checkout:{payment:{instruments:[exact_confirmed_instrument_with_selected_true]}}} @@ -39,9 +39,9 @@ The buyer approves the payee, purchase terms, saved card and spending limit in S Use `update_spend_request` with `{id,spend_request:{…},meta?}` to change the shipping address, items, totals, or spending limit. Omitted fields stay unchanged. Supplied arrays replace their previous values, and Wallet deep-merges address fields, unlike full-document cart/checkout updates. Wallet decides whether the request's current state allows the update; do not assume that buyer approval makes it editable. 1. Observe the actual business and purchase terms. -2. Call `create_spend_request` once with `spend_request` containing the required `business{name,url}`, `currency`, total, and `approval_limit` in currency minor units. Use the currency the site's checkout charges in; never convert or re-price. If Wallet refuses it (`unsupported_currency`), tell the user; do not retry in another currency. Add `business.country` only when the site states it; omit it rather than infer it from the domain, currency, or shipping address. Omit `instrument_id` to use the account's preferred card; name one only when the user chose a different saved card (ids from `get_account.selectable_instruments[]`, shown by `display.funding_instrument.description` when present, otherwise brand and last digits). Include actual line items and shipping address when available. Always include `context.intent` (required): what is being bought and why, in the user's language — it is the text the user reads on Shop's approval page (100–500 characters; no URLs, card details, emails or addresses). CLI shorthand takes it as `--intent`. If Wallet refuses it as too short, rewrite it as a full sentence the user would recognize; never pad it. Never invent missing terms. The preferred card is not checkout `selected`, an eligibility guarantee, a credential, or purchase authorization; the user still approves the payee, terms, card, and ceiling in Shop. +2. Call `create_spend_request` once with `spend_request` containing the required `business{name,url}`, `currency`, total, and `approval_limit` in currency minor units. Use the currency the site's checkout charges in; never convert or re-price. If Wallet refuses it (`unsupported_currency`), tell the user; do not retry in another currency. Add `business.country` only when the site states it; omit it rather than infer it from the domain, currency, or shipping address. Omit `instrument_id` to use the account's preferred card; name one only when the user chose a different saved card (ids from `get_account.selectable_instruments[]`, shown by `display.funding_instrument.description` when present, otherwise brand and last digits). Include actual line items and shipping address when available. Always include `context.intent` (required): what is being bought and why, in the user's language — it is the text the user reads on Shop's approval page (100–500 characters; no URLs, card details, emails or addresses). If Wallet refuses it as too short, rewrite it as a full sentence the user would recognize; never pad it. Never invent missing terms. The preferred card is not checkout `selected`, an eligibility guarantee, a credential, or purchase authorization; the user still approves the payee, terms, card, and ceiling in Shop. 3. Share the returned `continue_url` unchanged. If absent, say the spend request is waiting in Shop; never construct a review link. -4. With the CLI, run `shop spend-request get '' --wait 90`; with another binding, call `get_spend_request` again only after a successful `pending` result. After buyer approval, get returns the payment credential. This is not a read-only operation. Do not run checks concurrently. When a credential is delivered, the response includes `consumed` terms and an `instrument`. +4. Call `get_spend_request` for the returned id, and call it again only after a successful `pending` result. After buyer approval, get returns the payment credential. This is not a read-only operation. Do not run checks concurrently. When a credential is delivered, the response includes `consumed` terms and an `instrument`. 5. Enter the issued `instrument`'s credential only into the reviewed website's checkout form. Use it exactly as returned and only for the approved purchase. Its `display` describes that issued card. Never repeat its number, expiry, CVV, or name in chat, logs, diagnostics, plans, or error text. `display.funding_instrument` is the user's saved card that pays: name that card to the user, and if the business needs to find the payment, offer the issued card's `display.last_digits`. Present either card's `description`, when present, as is; otherwise use its brand and last digits. 6. After the purchase attempt, call `complete_spend_request` with the observed `success` or `error`. Report success only from transaction evidence and include order details only if known. For an error, send the observed `failure_code` and optional `message`, preserving any unknown charge outcome. Never include a payment credential. This records your report, not payment confirmation. Follow the recovery section if report acceptance is uncertain. @@ -65,13 +65,13 @@ On `auth_required`, follow the binding's sign-in path, then retry. A missing fie ## Search orders -Use `search_orders` for personal orders. Sign-in is required. Do not ask for a merchant order ID first. Use query text for merchant, item, or order number. Results are the orders placed through this app plus the shopper's other recent or in-progress orders; an older order placed elsewhere is not returned, so treat its absence as out of view, not as nonexistent. +Use `search_orders` for personal orders. Sign-in is required. Do not ask for a merchant order ID first. Use query text for merchant, item, or order number. Shop tracks every order from any store. By default only in-progress orders are returned; set `filters.status: all` to search the full order history, including when the default returns nothing. Refine the query and the date filters before reporting an order as not found. `shipments` are packages Shop is tracking without their order, usually found in email. | Optional field | Values | |---|---| -| `activity` | `active` (not yet delivered), `past`, `all` (default). | -| `date_from`, `date_to` | Inclusive placement dates, `YYYY-MM-DD`. | -| `pagination` | `{limit?,cursor?}`; limit 1 to 25, default 10; copy returned cursors unchanged. | +| `filters.status` | `in_progress` (default), `all` searches the full order history. | +| `filters.date` | `{from?,to?}`, inclusive placement dates, `YYYY-MM-DD`. | +| `pagination` | `{limit?,cursor?}`; copy returned cursors unchanged. | Order prices and totals are integer minor units in the order's `currency`. `XXX` means no known currency; Shop order history uses hundredths, so `1250` represents `12.50` without a currency symbol. Distinguish line totals from a missing order total; never combine amounts with unknown or different currencies into one spending total. diff --git a/skills/shop/SKILL.md b/skills/shop/SKILL.md index 4ba0368..02d765c 100644 --- a/skills/shop/SKILL.md +++ b/skills/shop/SKILL.md @@ -21,7 +21,7 @@ Offer to connect the user's Shop account for saved addresses, Shop Pay, saved ca `next[]` contains Shop's contextual recommendations for helping the user achieve their goal. Consider them alongside the returned state and messages, and act on them when useful. They are suggestions, not commands or purchase approval. -If the user accepts the offer to connect Shop or a command returns `auth_required`, run `shop account login --wait 0 --device-name ""`, send the user the link and code it returns, rerun with `--wait 90` until `signed_in: true`, then retry the original command. Run `shop account` when account information helps the task or the user wants to confirm that Shop is connected. `shop account logout` removes this device's sign-in. Optionally, an integration with its own registered Shop client can skip device login by supplying the token it minted as `SHOP_ACCESS_TOKEN`. Never read, print or request environment variables or token values. +If the user accepts the offer to connect Shop or a command returns `auth_required`, run `shop account login --wait 0 --device-name ""`, send the user the link and code it returns, rerun with `--wait 90` until `signed_in: true`, then retry the original command. Run `shop account` when account information helps the task or the user wants to confirm that Shop is connected. `shop account logout` removes this device's sign-in and revokes it with Shop. Optionally, an integration with its own registered Shop client can skip device login by supplying the token it minted as `SHOP_ACCESS_TOKEN`. Never read, print or request environment variables or token values. ## Use the CLI @@ -46,7 +46,7 @@ shop search --arguments '{"catalog":{"query":"rain jacket"}}' Do not mix `--arguments` with positionals or shorthand flags. JSON responses put successful results in `data`; tool errors retain returned state in `error.details.structuredContent`. Read that state, its messages, and `next[]`. Use `--format md` only for human display. `--dry-run` sends nothing. On a `transport` error, retry once only when `retryable` is `true`; if it fails again, report the connection failure. For writes, `retryable` says whether immediate repetition is safe, not whether the request was sent. Use the recovery rules in [transactions.md](references/transactions.md); if the result cannot be resolved, report the outcome unknown and stop. -CLI shorthand defaults the destination country to the US; it does not infer currency. Supply the known destination when relevant, and always set currency before using price filters. For guarded edits and completion, read [cli.md](references/cli.md). Read a linked reference with `shop skill `, for example `shop skill references/cli.md`. +CLI shorthand defaults the destination country to the US; it does not infer currency. Supply the known destination when relevant, and always set currency before using price filters. For a spend request, `shop spend-request get --wait 90` waits up to 90 seconds for the buyer's approval in one call. For image search, guarded edits, completion, and the credential file handoff, read [cli.md](references/cli.md). Read a linked reference with `shop skill `, for example `shop skill references/cli.md`. Use `seller.domain` for `shop`. Keep merchants separate. Copy returned IDs and URLs exactly, with every query parameter; do not rebuild or shorten them. diff --git a/skills/shop/references/transactions.md b/skills/shop/references/transactions.md index 50546d8..db7d621 100644 --- a/skills/shop/references/transactions.md +++ b/skills/shop/references/transactions.md @@ -18,7 +18,7 @@ Mutations accept `meta:{"idempotency-key":uuid}`. Reuse the UUID only for the sa 1. Inspect the latest checkout returned by `create_checkout`, `update_checkout`, or `get_checkout`, including status, items, totals, messages, and `payment.instruments`. If it may be stale, call `get_checkout` first. 2. Treat checkout status as authoritative. Resolve `incomplete` messages. For `requires_escalation`, give the user its `continue_url`. Proceed only from `ready_for_complete`; do not recreate Checkout's eligibility decision from `get_account` budget data. 3. Present the offered instruments and have the user confirm one exact instrument with the current merchant, items, quantities, and total. Copy that full object exactly as returned, preserving every field and extension; never choose a default, resolve by id/handler/display, or invent a credential or token. -4. Send `complete_checkout` exactly once; never repeat it, even with the same idempotency key. Pass the exact object to the CLI, which adds only root `selected:true`; other bindings set that one root member before sending: +4. Send `complete_checkout` exactly once; never repeat it, even with the same idempotency key. Add `selected: true` at the top level of the copied instrument, change nothing else, and send it as the single entry in `payment.instruments`: ```text {shop,id,checkout:{payment:{instruments:[exact_confirmed_instrument_with_selected_true]}}} @@ -39,9 +39,9 @@ The buyer approves the payee, purchase terms, saved card and spending limit in S Use `update_spend_request` with `{id,spend_request:{…},meta?}` to change the shipping address, items, totals, or spending limit. Omitted fields stay unchanged. Supplied arrays replace their previous values, and Wallet deep-merges address fields, unlike full-document cart/checkout updates. Wallet decides whether the request's current state allows the update; do not assume that buyer approval makes it editable. 1. Observe the actual business and purchase terms. -2. Call `create_spend_request` once with `spend_request` containing the required `business{name,url}`, `currency`, total, and `approval_limit` in currency minor units. Use the currency the site's checkout charges in; never convert or re-price. If Wallet refuses it (`unsupported_currency`), tell the user; do not retry in another currency. Add `business.country` only when the site states it; omit it rather than infer it from the domain, currency, or shipping address. Omit `instrument_id` to use the account's preferred card; name one only when the user chose a different saved card (ids from `get_account.selectable_instruments[]`, shown by `display.funding_instrument.description` when present, otherwise brand and last digits). Include actual line items and shipping address when available. Always include `context.intent` (required): what is being bought and why, in the user's language — it is the text the user reads on Shop's approval page (100–500 characters; no URLs, card details, emails or addresses). CLI shorthand takes it as `--intent`. If Wallet refuses it as too short, rewrite it as a full sentence the user would recognize; never pad it. Never invent missing terms. The preferred card is not checkout `selected`, an eligibility guarantee, a credential, or purchase authorization; the user still approves the payee, terms, card, and ceiling in Shop. +2. Call `create_spend_request` once with `spend_request` containing the required `business{name,url}`, `currency`, total, and `approval_limit` in currency minor units. Use the currency the site's checkout charges in; never convert or re-price. If Wallet refuses it (`unsupported_currency`), tell the user; do not retry in another currency. Add `business.country` only when the site states it; omit it rather than infer it from the domain, currency, or shipping address. Omit `instrument_id` to use the account's preferred card; name one only when the user chose a different saved card (ids from `get_account.selectable_instruments[]`, shown by `display.funding_instrument.description` when present, otherwise brand and last digits). Include actual line items and shipping address when available. Always include `context.intent` (required): what is being bought and why, in the user's language — it is the text the user reads on Shop's approval page (100–500 characters; no URLs, card details, emails or addresses). If Wallet refuses it as too short, rewrite it as a full sentence the user would recognize; never pad it. Never invent missing terms. The preferred card is not checkout `selected`, an eligibility guarantee, a credential, or purchase authorization; the user still approves the payee, terms, card, and ceiling in Shop. 3. Share the returned `continue_url` unchanged. If absent, say the spend request is waiting in Shop; never construct a review link. -4. With the CLI, run `shop spend-request get '' --wait 90`; with another binding, call `get_spend_request` again only after a successful `pending` result. After buyer approval, get returns the payment credential. This is not a read-only operation. Do not run checks concurrently. When a credential is delivered, the response includes `consumed` terms and an `instrument`. +4. Call `get_spend_request` for the returned id, and call it again only after a successful `pending` result. After buyer approval, get returns the payment credential. This is not a read-only operation. Do not run checks concurrently. When a credential is delivered, the response includes `consumed` terms and an `instrument`. 5. Enter the issued `instrument`'s credential only into the reviewed website's checkout form. Use it exactly as returned and only for the approved purchase. Its `display` describes that issued card. Never repeat its number, expiry, CVV, or name in chat, logs, diagnostics, plans, or error text. `display.funding_instrument` is the user's saved card that pays: name that card to the user, and if the business needs to find the payment, offer the issued card's `display.last_digits`. Present either card's `description`, when present, as is; otherwise use its brand and last digits. 6. After the purchase attempt, call `complete_spend_request` with the observed `success` or `error`. Report success only from transaction evidence and include order details only if known. For an error, send the observed `failure_code` and optional `message`, preserving any unknown charge outcome. Never include a payment credential. This records your report, not payment confirmation. Follow the recovery section if report acceptance is uncertain. @@ -65,13 +65,13 @@ On `auth_required`, follow the binding's sign-in path, then retry. A missing fie ## Search orders -Use `search_orders` for personal orders. Sign-in is required. Do not ask for a merchant order ID first. Use query text for merchant, item, or order number. Results are the orders placed through this app plus the shopper's other recent or in-progress orders; an older order placed elsewhere is not returned, so treat its absence as out of view, not as nonexistent. +Use `search_orders` for personal orders. Sign-in is required. Do not ask for a merchant order ID first. Use query text for merchant, item, or order number. Shop tracks every order from any store. By default only in-progress orders are returned; set `filters.status: all` to search the full order history, including when the default returns nothing. Refine the query and the date filters before reporting an order as not found. `shipments` are packages Shop is tracking without their order, usually found in email. | Optional field | Values | |---|---| -| `activity` | `active` (not yet delivered), `past`, `all` (default). | -| `date_from`, `date_to` | Inclusive placement dates, `YYYY-MM-DD`. | -| `pagination` | `{limit?,cursor?}`; limit 1 to 25, default 10; copy returned cursors unchanged. | +| `filters.status` | `in_progress` (default), `all` searches the full order history. | +| `filters.date` | `{from?,to?}`, inclusive placement dates, `YYYY-MM-DD`. | +| `pagination` | `{limit?,cursor?}`; copy returned cursors unchanged. | Order prices and totals are integer minor units in the order's `currency`. `XXX` means no known currency; Shop order history uses hundredths, so `1250` represents `12.50` without a currency symbol. Distinguish line totals from a missing order total; never combine amounts with unknown or different currencies into one spending total.