Shopify Remix Discount App Tutorial (Part 2/3)

Shopify Remix Discount App Tutorial (Part 2/3)

Learn how to create a discount App using the Shopify Remix App Framework

ยท

6 min read

Intro

Welcome back! This is the second part of a series where we build a Shopify Discount App. If you didn't read part 1 you can read it here.

Before we start

Recap

In part one of this series you successfully created a Shopify Discount Function and added it to your store. To keep things simple and focus on the function itself, we hardcoded the discountMessage, discountProducts, discountValue, and discountType values into our Function.

It would be ok to keep it like this if you only needed one specific discount, but our goal is to create a user interface to allow merchants to create as many discounts as they want with different values.

Function Metafield and Function Owner

To pass dynamic values to your query and function you will need to use meta fields defined on a function owner.

๐Ÿ’ก
The object associated with a function is known as the function owner. For example, the owner of a Discount API function is a discount.
๐Ÿ’ก
All Function APIs provide access to the function owner, and its meta fields, as part of their GraphQL schema. This means that you can access the metafield values set by merchants using input queries, and use those values in your function's logic.

With that knowledge in our hands, we can get stra to the implementation.

Development

Specifying Function Input Variable

The first thing you need to do is to Specify the meta field that your function will use. Open the extensions/customer-tag-discount/shopify.extension.toml file and paste this at the end of it:

  [extensions.input.variables]
  namespace = "$app:customer-tag-discount"
  key = "function-configuration"

This is how you tell your function which meta field defined on its function owner should it use. If you are familiar with Shopify meta fields it will look very familiar to you as we always have to specify namespace and key .

๐Ÿ’ก
You should use a reserved prefix ($app:) in your metafield namespace, this will ensure that other apps can't use it.

Setting up Discount Metafield

To set up the metafield you will use the metafieldsSet mutation, but first, you need to query discountNodes and retrieve your Discount ID.

๐Ÿ’ก
In part one of this series when you created your discount using the discountAutomaticAppCreate mutation it returned a discountId , that's the one we need!

In the root of your project run npm run dev and press 'g' to open the GraphiQL tool. Create a new tab and paste this query in:

query discountNodes{
  discountNodes(first: 100) {
    edges {
      node {
        id
        __typename
        discount {
          ... on DiscountAutomaticApp {
            title
          }
        }
      }
    }
  }
}

This will query all the discounts that are currently in your store. Look for the one we created in part 1 of this series and copy it's id

Now that you have an ID of your discount you can create a meta field. Create a new tab in the GraphiQL tool, and paste this query:

mutation SetMetafields {
  metafieldsSet(metafields: [
    {
      namespace: "$app:customer-tag-discount",
      key: "function-configuration",
      ownerId: "[Paste you discount node ID here]",
      type: "json",
      value: "{\"discountType\":\"orderDiscount\",\"discountProducts\":[],\"discountValue\":20,\"discountMessage\":\"This discount is using dynamic metafield values!\",\"discountTags\":[\"VIP\",\"Loyal\"]}"
    }
  ]) {
    metafields {
      id
    }
  }
}
๐Ÿ’ก
REMEMBER namespace and key has to match the one defined in extensions/customer-tag-discount/shopify.extension.toml.

Now when you run this query and everything goes well it should return you a meta field ID:

Updating Function GraphQL query

It's time to update your function query. Replace code in the run.graphql file with:

query RunInput($discountTags: [String!]!) {
  cart {
    buyerIdentity {
      customer {
        hasAnyTag(tags: $discountTags)
      }
    }
    lines {
      merchandise {
        ... on ProductVariant {
          id
          product {
            id
          }
        }
        __typename
      }
    }
  }
  discountNode {
    metafield(
      namespace: "$app:customer-tag-discount"
      key: "function-configuration"
    ) {
      value
    }
  }
}

You should notice that now you also query the discountNode and meta field that you just set up on it. In addition, you can also use its values as your function input. Notice how the $discountTags input variable is the same as the key defined in your meta field JSON value.

Run npm run typegen to generate new types and validate your function (remember to navigate to your extension folder).

Updating Function Logic

Now that your function is also querying the meta field defined on it you need to update the Function logic.

Open run.js file and replace its content with:

import { DiscountApplicationStrategy } from "../generated/api";

const EMPTY_DISCOUNT = {
  discountApplicationStrategy: DiscountApplicationStrategy.First,
  discounts: [],
};

export function run(input) {
  if (!input?.cart?.buyerIdentity?.customer) {
    return EMPTY_DISCOUNT;
  }

  const customerTagFound = input.cart.buyerIdentity.customer.hasAnyTag;

  if (!customerTagFound) {
    return EMPTY_DISCOUNT;
  }

  //---

  // const discountType = "orderDiscount";
  // const discountProducts = [];
  // const discountValue = 10;
  // const discountMessage = "10% VIP or Loyal Customer Discount";

  // Get your parsed metafield object from function input
  const configuration = JSON.parse(
    input?.discountNode?.metafield?.value ?? "{}",
  );

  // Instead of using hardcoded values you will now use the ones passed in function input metafield
  const discountType = configuration.discountType;
  const discountProducts = configuration.discountProducts;
  const discountValue = configuration.discountValue;
  const discountMessage = configuration.discountMessage;

  console.log(discountType);
  console.log(discountProducts);
  console.log(discountValue);
  console.log(discountMessage);

  //---

  let targets = input.cart.lines
    .filter((line) => {
      return line.merchandise.__typename === "ProductVariant";
    })
    .map((line) => {
      return {
        productVariant: {
          id: line.merchandise.id,
        },
      };
    });

  if (discountType === "productsDiscount") {
    targets = targets.filter((target) => {
      return discountProducts.some((product) => {
        return product === target.productVariant.id;
      });
    });
  }

  if (targets == []) {
    return EMPTY_DISCOUNT;
  }

  const DISCOUNTED_ITEMS = {
    discountApplicationStrategy: DiscountApplicationStrategy.First,
    discounts: [
      {
        targets: targets,
        value: {
          percentage: {
            value: discountValue,
          },
        },
        message: discountMessage,
      },
    ],
  };

  return DISCOUNTED_ITEMS;
}

Test your Function

Let's start our dev server again, run npm run dev, open the store preview, and add some products to the cart.

๐Ÿ’ก
Remember, you must be logged in and have one of the tags defined in your function meta field to see the discount applied!

Amazing, now your function uses a metafield to pass in variables like discount amount, discount Type, and discount message!

Function run logs

Open your partner Dashboard, navigate to Apps > customer-tags-discount-app > Extensions > customer-tag-discount, and open the latest function run to view its details.

This is where you can see console.logs that we added to the function logic, very useful when you need to debug your functions!

Outro

That's it for this part, I hope I will see you in the next one where we will build a UI so instead of creating discounts and populating its meta fields via GraphiQL tool you will be able to do it in the Admin Discounts page!

See ya!

ย