Alright, I have a functional workaround for this. I arrived at this solution after finding this Shopify response where they said:
So when a customer is associated with a checkout, we require them to re-login to validate that they have access.
That post was from 2019 and was related to the Checkout Storefront API and not the Cart Storefront API, though. And it’s contradicted by this page of the docs where it’s said:
You can associate a customer with a checkout so that the customer doesn’t have to enter customer information when checking out. You can associate the customer to Shopify’s web checkoutor to a checkout that will be completed through the API.
And then they go on to demo associating a customer with the checkoutCustomerAssociateV2 mutation. So either the docs are wrong or there is a bug.
Anyhow, this page also talks about using Multipass to pull this off and that’s what I ended up getting working. Rather than redirecting a user on checkout directly to the checkoutUrl from the cart, I am sending the checkoutUrl as well as the customer’s email to a server side script. In my case, I’m using a Netlify function. In other words, I’m doing this:
POST https://domain.com/.netify/functions/checkout?checkoutUrl=https://me.myshopify.com/cart/c/123&email=me@domain.com
The Netlify function (aka an AWS lambda) looks like this:
// Deps
const Multipassify = require('multipassify')
// Make a mulitpass url that returns the user to the checkout URL with their
// customer data intact
exports.handler = async function(req) {
// Get the checkout url from query params
const { checkoutUrl, email } = req.queryStringParameters
// Disable remote_ip when running Netlify Dev
const remote_ip = req.headers['client-ip'] == '::1' ?
undefined : req.headers['client-ip']
// Assemble multipass payload
const customerData = { email, remote_ip, return_to: checkoutUrl, }
// Create a multipass URL
const multipassify = new Multipassify(process.env.SHOPIFY_MULTIPASS_SECRET),
storeDomain = process.env.SHOPIFY_URL.replace(/^https:\/\//, ''),
redirectUrl = multipassify.generateUrl(customerData, storeDomain)
// Redirect to the multipass-bound checkout URL
return {
statusCode: 302,
headers: {
'Location': redirectUrl,
'Cache-Control': 'no-cache, no-store, must-revalidate',
}
}
}
Thus, I’m building a Multipass URL that redirects the user on to the checkoutUrl while binding their customer session to the URL. This is working for me.