Dedicated to the Hydrogen framework, headless commerce, and building custom storefronts using the Storefront API.
Hello,
I am working on a headless shopify store implementation that also uses subscriptions/contracts.
I set up a private app to create carts, add line items, login etc.
Users are required to login in order to check out.
Here are the graphQL calls I perform for any checkout:
mutation cartCreate
this creates a new cart object
mutation cartLinesAdd
this will add items to cart and assign a selling plan if possible
before checkout, the user is required to log in (if they are not logged in already)
mutation customerAccessTokenCreate
the mutation above logs them in and provides me with an accessToken
finally, I can use the accessToken above to perform the following:
mutation cartBuyerIdentityUpdate
using the accessToken, I am able to update the cart's buyer identity to the current user. I am also able to verify that this update has indeed taken place by querying the cart by id right after the final mutation.
the user is then redirected to the cart object's checkoutUrl. The checkout url leads to the unbranded/non-headless login page.
Is that behavior expected?
Has anyone been able to solve this double-login issue?
Thanks in advance!
I am encountering the same issue. I don't really get the purpose of `cartBuyerIdentityUpdate` if not to create the checkout in the context of an authenticated customer...
Same here.
Could anyone from Shopify confirm, if it is possible to add accessToken to the checkout url query params so that the users do not have to take the extra login step in the cart checkout page?
Here are some tighter steps to reproduce this issue. This is using API version 2022-01.
// QUERY
mutation customerAccessTokenCreate($input: CustomerAccessTokenCreateInput!) {
customerAccessTokenCreate(input: $input) {
customerAccessToken {
accessToken
}
customerUserErrors {
message
}
}
}
// VARIABLES
{
"input": {
"email": "me@domain.com",
"password": "password"
}
}
// RESPONSE
{
"data": {
"customerAccessTokenCreate": {
"customerAccessToken": {
"accessToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"customerUserErrors": []
}
}
}
// QUERY
mutation cartCreate($input: CartInput) {
cartCreate(input: $input) {
cart {
checkoutUrl
buyerIdentity {
customer {
id
email
}
}
estimatedCost {
totalAmount {
amount
}
}
}
userErrors {
message
}
}
}
// VARIABLES
{
"input": {
"buyerIdentity": {
"customerAccessToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
},
"lines": [
{
"merchandiseId": "Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC80MTA4OTUzNDc4Nzc2OA==",
"quantity": 1
}
]
}
}
// RESPONSE
{
"data": {
"cartCreate": {
"cart": {
"checkoutUrl": "https:\/\/domain.com\/cart\/c\/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz",
"buyerIdentity": {
"customer": {
"id": "Z2lkOi8vc2hvcGlmeS9DdXN0b21lci81NzM4NDExNDI1OTc2",
"email": "me@domain.com"
}
},
"estimatedCost": {
"totalAmount": {
"amount": "5.0"
}
}
},
"userErrors": []
}
}
}
In other words, go to https://domain.com/cart/c/zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz in the browser. At this point I would expect to be taken to the checkout flow with my customer state applied. Instead, my customer state has been lost and I am asked to login (see https://yo.bkwld.com/DOum4oNW).
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.
Hiya, @weotch thanks for posting your workaround... I was hoping that we'd come up with a solution that does not require shopify plus?
In any case, the documentation is misleading or there is abug as you mentioned in your post so it would be great if we could get a responsible answer or something fixed.