Help getting metafields in run.graphql/run.ts

Hello, I’m fairly new to developing Shopify applications, and I’m still trying to figure stuff out and how to do things the right way.

I have created a “Shipping Discount Function” through the CLI, I am then trying to use a custom meta field like so:

run.graphql:

query RunInput {
  cart {
    deliveryGroups {
      id,
      deliveryOptions {
        cost {
          amount
          currencyCode
        }
      }
    }
    cost {
      subtotalAmount {
        amount
        currencyCode
      }
      totalAmount {
        amount
        currencyCode
      }
    }
  }
  discountNode {
    metafield(namespace: "checkout_champ_free_shipping", key: "settings") {
      jsonValue
      value
    }
  }
}

And in my run.ts file I’m trying to get it like so:

interface ShippingDiscountSettings {
  targetSetting: number;
  discountValue: number;
  discountType: "fixed" | "percent" | "free";
}

export function run(input: RunInput): FunctionRunResult {
  const settingsArray: ShippingDiscountSettings[] = JSON.parse(
      input.discountNode?.metafield?.jsonValue || input.discountNode?.metafield?.value || "[]"
  );

  // Get the first row of settings, or fallback to an empty object if none found
  const { targetSetting, discountValue, discountType }: ShippingDiscountSettings = settingsArray[0] || {};

  /*  if (parseFloat(input.cart.cost.subtotalAmount.amount) < Number(targetSetting)){
      return EMPTY_DISCOUNT;
    }*/

  let value: Value;
  switch (discountType) {
    case 'free':
      value = {percentage: {value: '100'}};
      break;
    case 'fixed':
      value = {fixedAmount: {amount: `${discountValue}`}};
      break;
    case 'percent':
      value = {percentage: {value: `${Number(discountValue) * 100}`}};
      break;
  }

  // Apply the discount to all delivery groups
  const targets: Target[] = input.cart.deliveryGroups.map((group) => ({
    deliveryGroup: { id: group.id },
  }));

  return {
    discounts: [{
      value: value || {fixedAmount: {amount: `${discountValue || '5'}`}},
      targets: targets
    }],
  };
}

But nothing seem to work, the discount amount is always ‘5’, I have hard coded the namespace as “checkout_champ_free_shipping” and the key as “settings”, I haven’t used a namespace like “$app:discount-shipping” because I haven’t figured out how to load that namespace in my .liquid files.

In my admin interface have built a .tsx file that uses a loader function to retrieve the metafields (which works) and an action function that saves the metafields (which works)

Here is the loader and its helper function:

export const loader: LoaderFunction = async ({request}) => {
    const {admin} = await authenticate.admin(request);
    const metaFields = await getShopMetafields(admin, "checkout_champ_free_shipping", 10);

    let settings = {};
    if (metaFields && metaFields[0]) {
        settings = metaFields[0];
    }

    return {
        settings: settings,
    }
};

export async function getShopMetafields(
    admin: AdminApiContext<RestResources>,
    namespace: string,
    first: number = 10
) {
    return await admin.graphql(`#graphql
    query {
        shop {
            metafields(namespace: "${namespace}", first: ${first}) {
                edges {
                    node {
                        id
                        namespace
                        key
                        value
                        type
                    }
                }
            }
        }
    }`
    ).then((e: any) => e.json())
    .then((e: any) => e.data.shop.metafields.edges.map((edge: any) => edge.node));
}

Here is the action function and its helper:

export async function action({request}: {request: Request}) {
    const formData = await request.formData();
    const {admin} = await authenticate.admin(request);
    const shop = await getShop(admin);

    const responseMetafieldSet = await setMetaField(
        admin,
        "checkout_champ_free_shipping",
        "settings",
        "json",
        JSON.stringify({
            targetSetting: formData.get("targetSetting"),
            discountValue: Number(formData.get("discountValue")),
            discountType: formData.get("discountType"),
            textAlignment: formData.get("textAlignment"),
            completedBackground: formData.get("completedBackground"),
            missingBackground: formData.get("missingBackground"),
        }),
        shop.id
    );

export async function setMetaField(
    admin: AdminApiContext<RestResources>,
    namespace: string,
    key: string,
    type: string,
    value: any,
    ownerId: any,
): Promise<{
    metafield?: {
        id: string,
        namespace: string,
        key: string,
        value: string,
    },
    userErrors?: {
        field: string[],
        message: string,
        code: string,
    }[]
}> {
    // Send the mutation to create or update the metafield
    const response = await admin.graphql(
            `
            mutation SetMetafields($metafields: [MetafieldsSetInput!]!) {
                metafieldsSet(metafields: $metafields) {
                    metafields {
                        id
                        namespace
                        key
                        value
                    }
                    userErrors {
                        field
                        message
                        code
                    }
                }
            }
        `,
        {
            variables: {
                metafields: [
                    {
                        namespace: namespace,
                        key: key,
                        type: type,
                        value: value,
                        ownerId: ownerId
                    }
                ]
            }
        }
    );

    const jsonResponse = await response.json();
    const result = jsonResponse.data.metafieldsSet;

    if (result.userErrors && result.userErrors.length > 0) {
        return { userErrors: result.userErrors };
    }

    return { metafield: result.metafields[0] };
}

So basically the above loader/action function can retrieve and save the meta field with the metafields and setMetafields graphql function.

I am able to read the metafield in my .liquid file to build up an interface like so:

{% assign free_shipping_settings_json = shop.metafields.checkout_champ_free_shipping.settings %}

But I am not able to load the metafield in my run.ts/graphql.ts in my actual Shopify Shipping Extension

Can someone guide me to the right path? i would appreciate it a lot.

Also, it would be awesome if someone could explain to me why I can’t use an app owned namespace like: $app:shipping-discount as the namespace when using .liquid files.

I found out why, it was because I did not save the meta to the discountId, but instead to the shopId.