@curless/shopify-adapter
v0.9.3
Published
Use a Shopify store as an agentbank catalog source, and record agent-settled sales back as paid Shopify orders. Maps Shopify Admin products → a generic CatalogItem your merchant backend prices from, writes an order back after an agent pays through agentba
Readme
@curless/shopify-adapter
Use a Shopify store as your agentbank catalog, and record agent-settled sales back as paid Shopify orders. Drop it into a merchant backend so an AI agent can buy your Shopify products through any agentbank payment protocol — the money settles through agentbank, and the sale shows up in your Shopify admin.
Zero runtime dependencies — it uses the platform fetch and the Shopify Admin
GraphQL API. Pairs with @curless/agentbank-merchant-sdk,
which prices and opens the checkout.
npm install @curless/shopify-adapterThe three halves
import {
shopifyConfigFromEnv,
fetchShopifyCatalog,
writebackShopifyOrder,
refundShopifyOrder,
} from '@curless/shopify-adapter';
const shopify = shopifyConfigFromEnv(); // null unless SHOPIFY_* env is set1. Catalog — map the store's products into generic CatalogItems you price from:
const items = shopify ? await fetchShopifyCatalog(shopify) : [];
// items: { sku, name, priceMinor, currency, description, imageUrl?, variantId?, productType? }[]
// …price a merchant-quoted checkout from `items` with the merchant SDK…2. Writeback — after the agent pays through agentbank, record the paid order:
await writebackShopifyOrder(shopify, {
variantId, // CatalogItem.variantId
quantity,
protocol: 'acp', // which agentbank protocol settled it
agentbankOrderId: orderId, // idempotency tag
unitPriceMinor: 268000, // the amount actually collected (see below)
currency: 'EUR',
});No money moves in Shopify. The order is booked with a SALE transaction on the
agentbank gateway (not just financialStatus: PAID, which paints the label and
creates no transaction — such an order can never be refunded) and tagged agentbank +
protocol:… + the agentbank order id, so it's traceable and never mistaken for a
Shopify Payments sale. Pass unitPriceMinor + currency or there is no total to book
the transaction with, and the order won't be reversible.
3. Refund — when agentbank refunds the sale, stop the Shopify order reading as paid:
// e.g. from your `order.refunded` webhook handler
await refundShopifyOrder(shopify, {
agentbankOrderId: [`order_${paymentIntentId}`, paymentIntentId], // any tag you wrote back with
reason: 'guest cancelled',
});
// → { refunded: true, id } | { refunded: false, reason }It records the reversal rather than processing one — the sale was booked on our
own gateway, so the matching REFUND transaction moves no real money. It does have to
BE a transaction, though: a refund with none attached is a 0.00 refund and the order
goes on reading PAID. An order written back before this adapter recorded a sale
transaction has no parent to refund against and comes back
{refunded:false, reason:'…no SALE transaction…'} — reverse those in the admin once.
Full-order only — agentbank refunds the whole captured amount, and inventing a partial here would let you record a reversal that doesn't match the money that actually went back.
Notes
Auth — pick by whose store it is.
- A store you do NOT own (a real merchant): they create a custom app in
their own admin and give you its Admin API token. Set
SHOPIFY_ADMIN_TOKENSHOPIFY_SHOP_DOMAIN. This is the only option — the client-credentials grant requires the app and shop to sit in the same Shopify organization, so it cannot reach a third party's store at all.
- Your own store: Shopify's 2026 client-credentials grant. A Dev
Dashboard app no longer shows a token in the UI, so you exchange
SHOPIFY_CLIENT_ID+SHOPIFY_CLIENT_SECRETfor a short-lived Admin token programmatically.
A token, when present, wins — no exchange, one fewer call and one fewer way to fail. The app needs
read_products(catalog) andwrite_orders(writeback) — required scopes go inshopify.app.toml, thendeploy+ reinstall. Secrets come from the environment; never hardcode them.- A store you do NOT own (a real merchant): they create a custom app in
their own admin and give you its Admin API token. Set
Amounts are currency-aware. Prices convert to integer minor units using the currency's own exponent — cents for USD/EUR, none for JPY, mills for KWD — so a non-2-decimal store isn't silently mis-scaled.
Record the real amount. Pass
unitPriceMinor+currencyto the writeback so the Shopify order reflects what the agent actually paid. Omit them to fall back to the variant's current price (which drifts if the price changed after payment).Idempotency. The writeback searches for an order already tagged with the agentbank order id. Shopify's tag search lags creation by the search-index refresh, so two writebacks fired within a second can both miss and duplicate — add your own in-process guard on the order id if you fire them rapidly; the tag search is the cross-restart backstop.
Refunds are idempotent and safe to miss.
refundShopifyOrdertakes one id or a list, tries them in order, and returns{refunded:false, reason}— never throws — when nothing matches or the order is already REFUNDED. Ids that could false-match a Shopify search (spaces, operators) are dropped rather than searched: refunding the wrong order is worse than refunding none.Pagination. Products paginate to completion. A single product with more than 250 variants has its tail dropped with a warning (not silently).
MIT
