Skip to main content
This guide makes the same purchase as Complete a purchase on a Shopify store with Kernel, a $0.99 PDF from a Shopify store paid with the user’s Vault card, from the Google Chrome installed on the machine that runs the script. There is no browser provider in the loop. The browser is a process you own, which changes how you watch it, how long you keep it, and who gets the approval link. Every line of output below is from the run that produced order 33230218 on 2026-10-05, including the two runs before it that bought nothing. Your own browser shows how the SDK attaches to a Playwright page you launched, to a raw CDP connection, or to your own interception. This guide does not repeat it. It takes the Playwright level and runs the whole purchase through it. The shopping leg, the attach call, the placeholder card and the merchant result reader are the same as in the Kernel guide, so this page shows only what differs and links to that guide for the rest.

Before you start

A real purchase needs production credentials. The sandbox recognises the card request and pauses it, but no approval link is sent and nothing is charged.
  • @agent-cards/sdk and playwright-core installed, and a Chromium to launch: the Google Chrome on the machine (BROWSER_CHANNEL=chrome), or Playwright’s own build after npx playwright-core install chromium (leave BROWSER_CHANNEL unset).
  • An Agentcard organization with production credentials, and a user of that organization with a card in the Vault. Adding a card gets you both.
  • A way to reach the user: the approval link has to leave your process. The first run of this guide forgot that, and the section on delivering the link shows what that costs.
  • The Kernel guide’s steps 1 to 4 read once: open the checkout, fill it and read the total, attach, pay. They run unchanged here.

Tools

playwright

Playwright

agentcard

Agentcard Vault

shopify

Shopify checkout

What changes without a provider

The last row is the one that bites. With a hosted browser you can exit your script and come back to the session. Here the browser dies with the process, so a run that is waiting on an approval or on the merchant’s answer has to keep running.

1. Launch the browser

serviceWorkers: 'block' is as necessary here as on a hosted browser: a service worker owns requests the SDK cannot see, and Shopify’s card request is one of them. Headless Chrome shopped and paid on this run without a bot check; a store that shows one is a reason to run headed, or to take the Kernel guide’s path. The product page in headless Chrome: High School Biology Answer Key (PDF), 99 cents, Add to Cart

2. Shop, attach, and pay as in the Kernel guide

Steps 1 to 4 of the Kernel guide run here line for line: product page, cart, the cookie notice, the checkout form, the total read once it settles, attachToPlaywright with the same options, the placeholder card in Shopify’s iframes, Pay now once. The companion script for this guide differs from the Kernel one in the launch above and in how it closes the browser below, and nowhere else. The checkout filled in from headless Chrome: billing address and the order summary at 99 cents Shopify's card fields filled with the placeholder card, Pay now below onApprovalUrl hands your code the link. Nothing happens until a person opens it. The first run of this guide printed the link to the terminal and nothing else; nobody was reading the terminal, and this is what the SDK printed fifteen minutes later:
The authorization read expired afterwards, with no replay and nothing charged. The page sat on “Processing…” the whole time, and Shopify never saw a card. The second run delivered the link but the user was away from the phone; same output, same result. Make the delivery part of the run. The companion script runs a shell command with the link in APPROVAL_URL when APPROVAL_LINK_COMMAND is set; in your product it is whatever already reaches the user, such as the thread you have with them.
The link opens only for the cardholder it is bound to; another account sees “That approval link is not valid for your account.” It still goes to the user and nowhere else: not to the agent, not to a log the agent reads. The checkout after Pay now in headless Chrome: Processing while the card request is held

4. Expect Shopify to ask twice

On the run that bought the PDF, the user approved and the SDK replayed the real card. Three seconds later Shopify sent a second card request, and the SDK paused it and opened a second approval:
Shopify’s first request produced a card token the checkout did not turn into a payment, so the checkout asked for the card again. Each card request is its own authorization, so the user got a second link for the same purchase. They approved it, and the store’s order confirmation email arrived eight seconds later. The Kernel run of the day before completed on the first request; the difference is Shopify’s, and your code cannot tell in advance which it gets. Headless Chrome between the two approvals: the checkout still on Processing, the second card request held Three consequences for your code:
  • A second awaiting_approval after an authorized is not an error. Tell the user that the store asked again and that it is the same purchase, and deliver the new link the same way.
  • Do not click Pay again, and do not start a new checkout. The second request came from the page you already have.
  • Keep the process alive through it. The wait in the Kernel guide’s step 6 ended during this gap; the browser stayed open only because the script keeps it open while the merchant’s answer is pending, and the second approval landed into that open page.

5. Read the merchant’s record and close the browser

The checkout page is one witness; the store’s own record is the one you report. Shopify sends two emails for a digital order, the order confirmation with the total and the card’s last four digits, and the download notice with the order number, and the order status page says the same.
Shopify's order status page: Order 33230218, Confirmed Oct 5 Close the browser when the merchant’s record is in hand, or when the result is failed or declined. Until then keep it open and the process running, and keep asking: a pending answer has nothing else to read from, and the page is where the answer will appear. Three rules for the loop. A card request the user declined, or one that timed out, ends the run as that state, so read getState() first and stop on it. While a card request is paused on a new approval, the state is awaiting_approval and reconcile() has nothing to read yet, so wait rather than call it. And never stop the loop on a clock: a purchase that is still pending after an hour is still a purchase, and closing the browser then leaves it unreported. The companion script asks every fifteen seconds until the result settles, and a run that never clicked Pay closes the browser at once.
One order, one charge: two approvals for one checkout are two card tokens, and Shopify used the second. Confirm it the way this run did, from the store’s order record, before you tell the user anything.

Run it

Screenshots of every step land in shots-local/.

Handle a run that stalls

Where to go next