Hi,
I’m building a Shopify public app and I’m looking for some guidance on the best supported way to handle a fairly specific checkout flow.
The basic use case is that a merchant allocates a particular product and quantity to a particular customer. We then want that customer to be able to purchase their allocation through the merchant’s normal Shopify checkout.
The important requirements are:
- The customer needs to be logged into the correct Shopify customer account.
- The allocation is tied to specific products and quantities.
- The customer shouldn’t be able to change the basket and purchase something outside their allocation.
- The same allocation shouldn’t be usable to create two orders, including if the checkout is opened on two devices at the same time.
- We’d like this to work for normal non-Plus Shopify stores.
- As this will be a public app, we don’t want merchants to have to install/configure the Headless sales channel separately or do additional technical setup.
We’ve been testing a few different approaches on a development store.
We initially looked at Draft Orders, but found an issue for our use case. If a Draft Order is assigned to Customer A and Customer B opens the invoice checkout and authenticates, our Cart and Checkout Validation Function still sees Customer A (the customer attached to the Draft Order), rather than Customer B who actually authenticated. That means we can’t use the Function to verify that the person who logged in is the customer the allocation belongs to.
With a normal Shopify cart/checkout, this works as we’d expect — the Function sees the customer who is actually logged in.
So the approach we’re currently looking at is:
Our app creates the cart → adds the allocated products/quantities and a signed entitlement/app-owned cart marker → sends the customer to Shopify’s checkout URL → our Cart and Checkout Validation Function verifies the marker, logged-in customer and exact cart contents → Shopify handles checkout normally.
We’ve done some initial testing with a Storefront API-created cart. We opened the exact same checkout URL in two separate browser profiles, both logged in as the same customer. Both ended up on the same cart and same checkout token. We then attempted payment from both and only one order was created; both browsers ultimately showed the same order confirmation.
That’s encouraging, but I’m conscious that a development-store test isn’t the same thing as a documented platform guarantee, so I don’t want to build around behaviour Shopify doesn’t actually guarantee.
There are a couple of specific things I’m struggling to establish from the documentation:
1. Can a normal OAuth-installed public app create Storefront API carts in this way?
Can the public app create/manage its own Storefront API access token for the merchant’s store without the merchant separately installing/configuring the Headless sales channel?
If so, what scopes are required, and is this a supported approach for non-Plus production stores?
2. Can we use an app-owned cart metafield as trusted state?
If our public app creates the Storefront cart and writes a $app cart metafield, will that resolve to the same app-owned namespace that our Cart and Checkout Validation Function can read?
And importantly, is that value actually protected as app-owned state, i.e. can the customer/storefront or another app modify or forge a value in our $app namespace?
3. Is a Shopify cart/checkout effectively single-use for ordering?
Is there a documented guarantee that one cart/checkout can only result in one completed order?
For example, if the same checkout URL is open on two devices and both submit payment at almost exactly the same time, can that ever result in two orders from that same cart/checkout?
And finally, I’m also interested in whether we’re approaching this the right way at all.
If you were building a public app that needed to provide a customer-bound, product/quantity-bound, effectively single-use allocation through normal Shopify checkout, is the Storefront cart + Checkout Validation Function approach the recommended way to do it?
Or is there another Shopify primitive/pattern designed for this that we should be using instead?
I’d much rather build around Shopify’s intended architecture than find a workaround that happens to work in our development store.
Thanks!