Skip to content
Approvals

Guides

Approve a purchase outside Shopify

Have the shopper approve the store, the purchase, a saved card and a spending limit in Shop, so an agent can pay at a store that doesn't check out through Shopify.

Some purchases can’t go through a Shop checkout: the store doesn’t run on Shopify, so the agent has to pay on the store’s own checkout page. The approval tools cover that case. The shopper reviews the store, the purchase, one of their saved cards and a spending limit in Shop. Once they approve, Shop gives the agent card details for that purchase, paid for by the saved card.

Tool What it does
create_approval Asks the shopper to approve a purchase. It doesn’t pay or place an order.
get_approval Reads an approval and, once it’s approved, returns the card to pay with
update_approval Changes the limit, items, totals or shipping address
cancel_approval Cancels the approval
complete_approval Records how the purchase went

All five need sign-in. They also depend on permissions Shop grants to the client the shopper signed in with, so they can be refused even when get_account works.

When to use it

Use a Shop checkout whenever the store supports one. Products from Shop search come with a seller.domain, so they always go through the checkout tools.

For a product page found somewhere else, check whether the store runs on Shopify before you ask for an approval. Either of these on the product page means it does:

  • A powered-by HTTP header that names Shopify.
  • A rel="buy" link whose payment tokens include dev.shopify.shop_pay, in an HTTP Link header or an HTML <link> or <a> element.

If the product page has neither, check the store’s cart or checkout page as well. When the store does run on Shopify, create a Shop checkout with it.

An approval isn’t a fallback. Don’t ask for one because a Shop checkout failed or needs the shopper to finish on the merchant’s page.

How it works

  1. Go through the store’s checkout until you can see the real payee and terms: the items, prices, shipping, tax and total.
  2. Call get_account, and use the saved card named by preferred_instrument_id unless the shopper picks another from selectable_instruments. If no cards come back, follow the result’s messages and stop.
  3. Call create_approval once, with the terms you saw.
  4. Give the shopper the returned continue_url, where they review and approve in Shop.
  5. Call get_approval until the approval isn’t pending any more. Once the shopper approves, it returns consumed with an instrument.
  6. Pay on the store’s checkout with the instrument.
  7. Report what happened with complete_approval.

Create an approval

JSON
{
  "approval": {
    "business": { "name": "Example Merchant", "url": "https://merchant.example", "country": "US" },
    "currency": "USD",
    "line_items": [{ "item": { "title": "Ceramic pour-over set", "price": 10500 }, "quantity": 1 }],
    "totals": [
      { "type": "subtotal", "amount": 10500 },
      { "type": "fulfillment", "amount": 800 },
      { "type": "tax", "amount": 700 },
      { "type": "total", "amount": 12000 }
    ],
    "instrument_id": "3f1e2d4c-5b6a-4789-8abc-def012345678",
    "approval_limit": 12000,
    "context": {
      "intent": "A ceramic pour-over coffee set as a birthday present for my sister, from the small roaster she likes, shipped to me so I can wrap it first."
    }
  }
}
Field Value
business The payee’s name, url and country, as the store presents them. Required.
currency The purchase currency (ISO 4217). Required.
totals The totals in minor units, including exactly one total. Discounts are negative. Required.
instrument_id The id of the saved card from get_account. Required.
approval_limit The most the card can be charged, in minor units. Required.
line_items Each item’s title, unit price and quantity
shipping_address Where the order ships, in the same fields as a checkout address
context.intent What the shopper asked for, in their words: 100 to 500 characters with no links, card details, email addresses or postal addresses. It can’t be changed later.

Send the items, the shipping address and the intent whenever you know them, and never make up a term you don’t have.

From the CLI, put the arguments in a file:

Shell
shop approval create --arguments @approval.json

Without an idempotency key, each create_approval is a new request. If a create fails before returning an id, you can’t tell whether the approval exists, so tell the shopper and don’t create another. To make a create safe to repeat, send a UUID as meta.idempotency-key and repeat it with the same key.

Shopper review

The result is the approval, with status: "pending":

Field What it holds
id The approval’s ID, a UUID. The other approval tools take it as id.
status Where the approval stands
continue_url The page in Shop where the shopper reviews and approves
expires_at The deadline for the next step
messages Warnings, such as review_link_unavailable when Shop returned no review link

Give the shopper continue_url unchanged. If it’s missing, tell them the approval is waiting in Shop, and read it again later for a new link. Never put together a review link yourself.

expires_at isn’t the card’s expiry date. While the approval is pending, it’s when the shopper has to approve by. Once it’s approved, it’s when the agent has to collect the card by.

Get the card

JSON
{ "id": "0198e63b-1234-7abc-8def-123456789abc" }

get_approval isn’t read-only. Once the shopper has approved, the call has Shop issue the card, and later calls return the same card again. It doesn’t wait for the shopper itself. Call it again only after a successful pending result, and never run two calls at once.

The CLI does the waiting for you. This checks every 5 seconds for up to 90 seconds, and stops at the first result that isn’t pending:

Shell
shop approval get 0198e63b-1234-7abc-8def-123456789abc --wait 90
status Meaning
pending The shopper hasn’t approved yet
approved The shopper approved, and the card hasn’t been issued
consumed Shop issued the card, which is in instrument
cancelled The approval was canceled
expired The deadline passed

A consumed approval without an instrument doesn’t show that the card reached you. A materialization_in_progress error means Shop is already preparing the card, and a later get_approval may return it. In either case, don’t create a new approval to get around it.

Use the card

instrument has the issued card’s credential (the number and other details a checkout form asks for), a billing_address, and a display object:

  • display describes the issued card, including its last_digits.
  • display.funding_instrument describes the shopper’s saved card, which pays for the issued one.

Use the card only on the checkout the shopper approved, for that purchase. Never repeat its number, expiry date, security code or cardholder name in chat, logs or error messages.

When you tell the shopper which card paid, name the saved card: its description exactly as given, or else its brand and last digits. If the store needs to find the payment, give the issued card’s last digits.

Report the outcome

After you try to pay, record the result with complete_approval. Report success only when the store confirms the transaction, such as with an order confirmation, and include the order details you have:

JSON
{
  "id": "0198e63b-1234-7abc-8def-123456789abc",
  "result": { "status": "success", "order": { "id": "1042", "total": 12000, "currency": "USD" } }
}

Otherwise, report error with a failure_code and a short message:

JSON
{
  "id": "0198e63b-1234-7abc-8def-123456789abc",
  "result": { "status": "error", "failure_code": "payment_declined", "message": "The payment was declined by the processor." }
}

Example codes are captcha, anti_bot_script, cdn_block, waf_block, dns_block, rate_limited, login_required, 3ds_challenge, page_inaccessible, timeout, site_error, payment_declined and other. Other codes are allowed; with other, say what happened in message. If you can’t tell whether the card was charged, for example after a timeout, report error and say in message that the charge is unknown.

complete_approval only records your report. It doesn’t pay, prove a payment, close the approval or cancel the card. Its result’s result.status and result.recorded_at say Shop received the report. get_approval doesn’t show the report afterwards.

Change or cancel an approval

update_approval takes the approval’s id and an approval object with any of approval_limit, line_items, shipping_address and totals. The payee, currency, card and intent stay as they were. Shop refuses the update with approval_terms_immutable once the approval’s state doesn’t allow changes.

Shell
shop approval update --arguments '{"id":"0198e63b-1234-7abc-8def-123456789abc","approval":{"approval_limit":13000}}'

cancel_approval takes the id. It cancels the approval only: it doesn’t cancel or refund a purchase made with it, or revoke a card that was already issued.

Both accept a UUID in meta.idempotency-key, and make one from the request when you leave it out. After an uncertain update or cancel, you can repeat the exact same request, with the same key if you sent one.

When approvals are turned off

Shop can turn approvals off. While they’re off, every approval tool returns isError: true with this text and no error code, and nothing runs:

Text
This operation is currently unavailable and was not executed. Do not retry automatically.

Tell the shopper, and don’t retry automatically.