Shopify / ui-extensions

MIT License
268 stars 36 forks source link

Customer metafields are not available in the Checkout UI extensions for the Order Status Page #1904

Open lynthius opened 6 months ago

lynthius commented 6 months ago

Please list the package(s) involved in the issue, and include the version you are using

"@shopify/app": "3.58.2",
"@shopify/checkout-ui-extensions-react": "0.27.3",
"@shopify/cli": "3.59.0",

Describe the bug

Customer metafield should be available in the Shopify Checkout for the Order Status Page but they are not. It's not possible to retrieve the value of customer-type metafields within the checkout. It seems to only work with other metafields types like product metafields.

Steps to reproduce the behavior:

  1. Create a checkout_ui_extension using the useAppMetafields() hook like the example below.
    
    #shopify.extension.toml
    [[extensions.targeting]]
    module = "./src/Checkout.tsx"
    target = "purchase.checkout.payment-method-list.render-after"

[[extensions.metafields]] namespace = "custom" key = "referral_code"

[[extensions.metafields]] namespace = "test_data" key = "snowboard_length"

Checkout.tsx

import { reactExtension, TextBlock, BlockStack, useAppMetafields, } from "@shopify/ui-extensions-react/checkout";

export default reactExtension("purchase.checkout.payment-method-list.render-after", () => );

function Extension() { const referralCode = useAppMetafields({ type: "customer", namespace: "custom", key: "referral_code", });

const appMetafields = useAppMetafields(); console.log(appMetafields); console.log(referralCode);

return ( <>

Share the art of living well and enjoy savings together. Share it with your friends or through social: {referralCode[0].metafield.value}
</>

); }



2. Create the referral_code metafield in your store for the customer.
3. Put some value in the customer's metafield.
4. Add the extension to your Thank you page and/or your Order status page or in the checkout in general.
5. Go to the checkout and check if the console showing you referralCode (there is only an empty array for that metafield).

## Expected behavior

Customer metafields should be available in the Shopify Checkout - but they are not available. You can only see other types of metafields values like product metafield.
<!-- A clear and concise description of what you expected to happen. -->

## Screenshots
![Screenshot 2024-04-18 at 18 36 26](https://github.com/Shopify/ui-extensions/assets/54120503/c726dde9-5783-41e7-b912-491ba7ff0022)

![Screenshot 2024-04-18 at 18 37 02](https://github.com/Shopify/ui-extensions/assets/54120503/298e5cae-da3b-48f2-9ec2-0bd24eb1306d)

![Screenshot 2024-04-18 at 18 37 30](https://github.com/Shopify/ui-extensions/assets/54120503/b76ac723-c1ba-438d-940e-f95fbb853f44)

![Screenshot 2024-04-18 at 18 37 53](https://github.com/Shopify/ui-extensions/assets/54120503/41b638d7-ff47-41a0-b49a-a8d192043d8e)

<!-- If applicable, add screenshots or a short video to help explain your problem. -->

## Additional context
I need to retrieve the value of customer-type metafields within the checkout because every customer have unique referral code, which I need to display in the checkout/order/thank you page.

<!-- Add any other context about the problem here, examples could be the package version, troubleshooting steps you’ve tried, etc. -->
lynthius commented 6 months ago

Hey, any chance to put this issue on the radar in the near future?

ctrlaltdylan commented 6 months ago

Seeing metafield update issues in Checkout UI Extensions too without any response yet.

Hopefully it's just a large backlog, or a refactor coming that improves metafield support for all APIs.

jamesvidler commented 6 months ago

@lynthius I wasn't able reproduce this issue. Is the customer logged into checkout and have the appropriate metafield value set already?

lynthius commented 6 months ago

@lynthius I wasn't able reproduce this issue. Is the customer logged into checkout and have the appropriate metafield value set already?

@jamesvidler yes, you have to create a metafield for the user and pass there some value. Then try to read that value :) I can't do this. I'm not able to read any customer's metafield. I'm logged into checkout.

EDITED: 13/05/24

ctrlaltdylan commented 5 months ago

Also, I've noticed the useCustomer hook doesn't return the customer data either on the customer-account.order-status.block.render extension point.

The useOrder hook works just fine, and it returns the customer data:


const order = useOrder();
const customer = useCustomer();
const email = useEmail()

<>
      <View>{JSON.stringify(order, null, 4)}</View>
      <View>{JSON.stringify(customer, null, 4) || "no customer data"}</View>
      <View>{email}</View>
</>

Results in:

CleanShot 2024-05-17 at 10 29 48

Actually, my comment is probably more closely aligned with this other bug report: https://github.com/Shopify/ui-extensions/issues/1614

lynthius commented 5 months ago

Hello again, please recheck this issue. Still, I don't know how to resolve it, and is it even possible?

Vinitpal commented 5 months ago

Hello there, anyone found solution on this?

lynthius commented 4 months ago

@Vinitpal nope, still waiting. I hope we will find a solution.

jamesvidler commented 4 months ago

@lynthius I reviewed the code you provided and attempted to reproduce again. I have some ideas of what you might be running into:

  1. Does your app have access to protected customer data? This could be one reason why you aren't getting any values for customer metafields.
  2. appMetafields are loaded asynchronously, after checkout loads which means that the initial value of appMetafields may be empty. However, since you are using useAppMetafields(), your extension will re-render once it has a value. Your code, as is isn't checking if referralCode[0] has a value before accessing it so the extension is crashing before it has a chance to re-render. I took your exact code example and reproduced this. I removed the reference for referralCode[0].metafield and I can see the metafield values returned now in the console.log.

Let me know any this helps!

lynthius commented 4 months ago

@jamesvidler but I test it in the development environment with my sandbox store. There is no way to add "protected customer data" access, at least I don't see it. What scope should I add to the toml file?

I will share with you my new implementation and please tell me if something is wrong with it. It's a simple VAT field. It should add VAT metafield with value for ORDER and CUSTOMER objects. It works for ORDER, but again not with CUSTOMER.

EDIT: I can read the customer's metafield with VAT now if it's not empty. But still, I can't overwrite it or add value to the empty one.

EDIT2: Is it possible I couldn't read the customer's metafield value earlier because I tried to do it on the thank you status page, and I migrated only partly on the new checkout extensibility (without thank you, orders status pages)? If yes, then it is a little bit tricky because I can migrate fully until August 2025, but on the other hand, I can't use all of the functionalities.

Checkout.tsx

import {
  reactExtension,
  BlockStack,
  TextField,
  useApplyMetafieldsChange,
  useAppMetafields,
  Checkbox,
  Heading,
  useSettings,
  useExtensionCapability,
  useBuyerJourneyIntercept,
  useApi,
  useTranslate,
} from "@shopify/ui-extensions-react/checkout";

import { useState } from "react";

// export default reactExtension("purchase.checkout.block.render", () => <Extension />);
export default reactExtension("purchase.checkout.contact.render-after", () => <Extension />);

function Extension() {
  const [checked, setChecked] = useState(false);
  const metafieldNamespaceCheckout = "checkout";
  const metafieldNamespaceCustomer = "customer";
  const metafieldKey = "vat_number";
  const translate = useTranslate();

  const invoiceFieldTranslation = translate("invoice_field_heading");
  const invoiceCheckboxTranslation = translate("invoice_checkbox_info");
  const invoicePlaceholderTranslation = translate("invoice_placeholder_info");
  const invoicePlaceholderOptionalTranslation = translate("invoice_placeholder_optional_info");
  const invoiceVatRequiredTranslation = translate("invoice_vat_required_info");
  const invoiceVatNotCorrectTranslation = translate("invoice_vat_not_correct_info");
  const invoiceProvideVatTranslation = translate("invoice_provide_vat_info");
  const invoiceFixVatTranslation = translate("invoice_fix_vat_not_info");

  const settings = useSettings();
  const visibilityFlag = settings.vat_visibility as boolean;

  const applyMetafieldsChange = useApplyMetafieldsChange();
  const whatMetafields = useAppMetafields();

  const handleChangeCheckbox = () => {
    console.log("metafields list:", whatMetafields);
    setChecked(!checked);
  };

  const [vat, setVat] = useState("");
  const [validationError, setValidationError] = useState("");
  const canBlockProgress = useExtensionCapability("block_progress");
  const label = canBlockProgress ? invoicePlaceholderTranslation : invoicePlaceholderOptionalTranslation;

  useBuyerJourneyIntercept(({ canBlockProgress }) => {
    if (canBlockProgress && checked && !isVatSet()) {
      return {
        behavior: "block",
        reason: invoiceVatRequiredTranslation,
        perform: (result) => {
          if (result.behavior === "block") {
            setValidationError(invoiceProvideVatTranslation);
          }
        },
      };
    }

    if (canBlockProgress && checked && !isVatValid(vat)) {
      return {
        behavior: "block",
        reason: invoiceVatNotCorrectTranslation,
        perform: (result) => {
          if (result.behavior === "block") {
            setValidationError(invoiceFixVatTranslation);
          }
        },
        // errors: [
        //   {
        //     message: "The VAT number entered is incorrect. Please ensure it starts with a country code (uppercase), followed by 8 to 12 alphanumeric characters. Example: XX0123456789.",
        //   },
        // ],
      };
    }

    return {
      behavior: "allow",
      perform: () => {
        clearValidationErrors();
      },
    };
  });

  const handleChangeVatInput = (value: string) => {
    setVat(value);
    if (isVatValid(value)) {
      applyMetafieldsChange({
        type: "updateMetafield",
        namespace: metafieldNamespaceCheckout,
        key: metafieldKey,
        valueType: "string",
        value,
      });
      applyMetafieldsChange({
        type: "updateMetafield",
        namespace: metafieldNamespaceCustomer,
        key: metafieldKey,
        valueType: "string",
        value,
      });
    }
  };

  function isVatSet() {
    return vat !== "";
  }

  function isVatValid(value: string): boolean {
    const vatTarget: RegExp = /^[A-Z]{2}[0-9A-Z]{8,12}$/; // Example regex for VAT number validation
    return vatTarget.test(value);
  }

  function clearValidationErrors() {
    setValidationError("");
  }

  if (!visibilityFlag) return null;

  return (
    <>
      <BlockStack padding={["base", "none", "base", "none"]}>
        <Heading level={2}>{invoiceFieldTranslation}</Heading>
      </BlockStack>
      <BlockStack>
        <BlockStack border="base" borderRadius="base" padding="base">
          <Checkbox checked={checked} onChange={handleChangeCheckbox}>
            {invoiceCheckboxTranslation}
          </Checkbox>
        </BlockStack>
        {checked && (
          <TextField
            label={label}
            value={vat}
            onInput={clearValidationErrors}
            onChange={(value) => handleChangeVatInput(value)}
            required={canBlockProgress && checked}
            error={validationError}
          />
        )}
      </BlockStack>
    </>
  );
}

shopify.app.toml

[access_scopes]
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes
scopes = "customer_read_customers,customer_read_draft_orders,customer_read_markets,customer_read_orders,customer_write_customers,read_cart_transforms,read_checkouts,read_content,read_customers,write_content,write_customers"

shopify.extension.toml

# Learn more about configuring your checkout UI extension:
# https://shopify.dev/api/checkout-extensions/checkout/configuration

# The version of APIs your extension will receive. Learn more:
# https://shopify.dev/docs/api/usage/versioning
api_version = "2024-04"

[[extensions]]
type = "ui_extension"
name = "checkout-ui"
handle = "checkout-ui"

# Controls where in Shopify your extension will be injected,
# and the file that contains your extension’s source code. Learn more:
# https://shopify.dev/docs/api/checkout-ui-extensions/unstable/extension-targets-overview

[[extensions.targeting]]
module = "./src/Checkout.tsx"
target = "purchase.checkout.block.render"

[extensions.capabilities]
# Gives your extension access to directly query Shopify’s storefront API.
# https://shopify.dev/docs/api/checkout-ui-extensions/unstable/configuration#api-access
api_access = true
block_progress = true

# Gives your extension access to make external network calls, using the
# JavaScript `fetch()` API. Learn more:
# https://shopify.dev/docs/api/checkout-ui-extensions/unstable/configuration#network-access
network_access = true

# Loads metafields on checkout resources, including the cart,
# products, customers, and more. Learn more:
# https://shopify.dev/docs/api/checkout-ui-extensions/unstable/configuration#metafields

[[extensions.metafields]]
namespace = "checkout"
key = "vat_number"

[[extensions.metafields]]
namespace = "customer"
key = "vat_number"

# [[extensions.metafields]]
# namespace = "my_namespace"
# key = "my_other_key"

# Defines settings that will be collected from merchants installing
# your extension. Learn more:
# https://shopify.dev/docs/api/checkout-ui-extensions/unstable/configuration#settings-definition

[extensions.settings]
[[extensions.settings.fields]]
key = "vat_visibility"
type = "boolean"
name = "VAT visibility"
description = "Show or hide VAT number filed"
lynthius commented 4 months ago

Fun fact, I use the Flow app to fix my problem (adding the same VAT value for the customer's metafield from input inside checkout by copying it from the last order's VAT metafield. But it would be great to send it from the checkout using applyMetafieldsChange.

Screenshot 2024-07-04 at 08 39 22
rodrigo-jantsch commented 3 months ago

@lynthius, I am facing the exact same issue. Every other metafield is being returned except the customer related ones. I have one test app that I created a while back and that one is returning it. I have compared the toml files of both and the apps are exactly the same, they both have the same scope permission as well.

I compared it with yours and it seems to have the same logic. I am not sure what is going on. I know that you shared your updates, but any additional information would help. I am a little stuck with this issue. Thank you in advance!

lynthius commented 3 months ago

@rodrigo-jantsch have you fully migrated to the Checkout UI extensions? If you want to grab the customer's metafields from the Thank you page, this could be the problem. I finally returned the customer's metafield value, but for the Checkout page, not the Thank you page. Still, I can't overwrite it or add value to the empty one, and I'm not sure why. I have all the permissions. Could you help us @jamesvidler?

rodrigo-jantsch commented 3 months ago

@lynthius , no, we are migrating the entire store, so we are starting from the scratch using the Checkout Uo extensions. I am actually trying to get the customer data from the Checkout Page once the customer is logged in....

rodrigo-jantsch commented 2 months ago

Apparently you have to deploy it when there are scope changes. It was that!