Custom Discount based on Cart Subtotal

Solved

Custom Discount based on Cart Subtotal

ginacostanzo
Shopify Partner
13 2 7

New to working with Shopify functions. I've read a ton of the documentation and followed the tutorial to create a discount experience for 10% off for a volume order. 

 

I want to customize this now. Ideally, I want to eventually have a function for a tiered discount where a gift card of a certain value will be automatically added to the cart when the user enters a certain discount code. The gift card amount will be determined by the cart subtotal. 

 

To work up to that, I'm currently just trying to have the cart subtotal discounted 10% when it is over $50. I've read the documentation for discounts and cart API and objects, but the code doesn't work. This is what I have (photos attached for easier reading): 

// @TS-check
import { DiscountApplicationStrategy } from "../generated/api";

/**
* @typedef {import("../generated/api").InputQuery} InputQuery
* @typedef {import("../generated/api").FunctionResult} FunctionResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/

/**
* @type {FunctionResult}
*/
const EMPTY_DISCOUNT = {
discountApplicationStrategy: DiscountApplicationStrategy.First,
discounts: [],
};

// The @Shopify/shopify_function package will use the default export as your function entrypoint
export default /**
* @Anonymous {InputQuery} input
* @returns {FunctionResult}
*/
(input) => {
// Calculate the total pre-tax cost of items in the cart
let totalPreTaxCost = 0;
for (const line of input.cart.lines) {
const lineSubtotal = line.cost.subtotalAmount.amount;
totalPreTaxCost += lineSubtotal;
}

// Check if the total pre-tax cost is greater than $50
if (totalPreTaxCost > 50) {
// Add the free product to the cart
// Replace this with the code to add the free product to the cart
// ...

// Apply a 10% discount to all items in the cart
const targets = input.cart.lines.map(line => {
const variant = /** @type {ProductVariant} */ (line.merchandise);
return /** @type {Target} */ ({
productVariant: {
id: variant.id
}
});
});


if (!targets.length) {
console.error("No cart lines qualify for volume discount.");
return EMPTY_DISCOUNT;
}

return {
discounts: [
{
targets,
value: {
percentage: {
value: "10.0"
}
}
}
],
discountApplicationStrategy: DiscountApplicationStrategy.First
};
}

// If the total pre-tax cost is not greater than $50, return an empty discount
return EMPTY_DISCOUNT;
};


Screen Shot 2023-08-28 at 12.10.41 PM.png
Screen Shot 2023-08-28 at 12.10.51 PM.pngScreen Shot 2023-08-28 at 12.10.56 PM.png

Accepted Solution (1)
ginacostanzo
Shopify Partner
13 2 7

This is an accepted solution.

for (const line of input.cart.lines) { const lineSubtotal = line.cost.subtotalAmount.amount; totalPreTaxCost += lineSubtotal; }
Ended up needing to change this line. Instead of getting the cost attribute of each line, just got the cost attribute of the cart directly. 

So input.cart.cost.subtotalAmount.amount

View solution in original post

Replies 5 (5)

Liam
Community Manager
3108 341 879

Hi Ginacostanzo,

 

Your code logic seems almost correct except that you closed your if (totalPreTaxCost > 50) condition too early. This is causing your function to always return EMPTY_DISCOUNT, regardless of the subtotal amount.

 

Here's a corrected version of your code:

// @TS-check
import { DiscountApplicationStrategy } from "../generated/api";

/**
* @typedef {import("../generated/api").InputQuery} InputQuery
* @typedef {import("../generated/api").FunctionResult} FunctionResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/

/**
* @type {FunctionResult}
*/
const EMPTY_DISCOUNT = {
  discountApplicationStrategy: DiscountApplicationStrategy.First,
  discounts: [],
};

// The @Shopify/shopify_function package will use the default export as your function entrypoint
export default /**
* @param {InputQuery} input
* @returns {FunctionResult}
*/
(input) => {
  // Calculate the total pre-tax cost of items in the cart
  let totalPreTaxCost = 0;
  for (const line of input.cart.lines) {
    const lineSubtotal = line.cost.subtotalAmount.amount;
    totalPreTaxCost += lineSubtotal;
  }

  // Check if the total pre-tax cost is greater than $50
  if (totalPreTaxCost > 50) {
    // Apply a 10% discount to all items in the cart
    const targets = input.cart.lines.map(line => {
      const variant = /** @type {ProductVariant} */ (line.merchandise);
      return /** @type {Target} */ ({
        productVariant: {
          id: variant.id
        }
      });
    });

    if (!targets.length) {
      console.error("No cart lines qualify for volume discount.");
      return EMPTY_DISCOUNT;
    }

    return {
      discounts: [
        {
          targets,
          value: {
            percentage: {
              value: "10.0"
            }
          }
        }
      ],
      discountApplicationStrategy: DiscountApplicationStrategy.First
    };
  }
  // If the total pre-tax cost is not greater than $50, return an empty discount
  return EMPTY_DISCOUNT;
};

Remember to replace the "10.0" string with a numerical value 10.0 as the discount percentage should be a number, not string.

 

Hope this helps!

Liam | Developer Advocate @ Shopify 
 - Was my reply helpful? Click Like to let me know! 
 - Was your question answered? Mark it as an Accepted Solution
 - To learn more visit Shopify.dev or the Shopify Web Design and Development Blog

ginacostanzo
Shopify Partner
13 2 7

This is an accepted solution.

for (const line of input.cart.lines) { const lineSubtotal = line.cost.subtotalAmount.amount; totalPreTaxCost += lineSubtotal; }
Ended up needing to change this line. Instead of getting the cost attribute of each line, just got the cost attribute of the cart directly. 

So input.cart.cost.subtotalAmount.amount

ginacostanzo
Shopify Partner
13 2 7

Thoughts on this updated code? API-KEY is being replaced with the appropriate API key. The subtotal is calculate correctly now, I tested the ALL_PRODUCTS query in graphQL and it correctly retrieves gift cards, the tiered discount was working when it was a percentage off.

I'm thinking the issue is with the CartTransform mutation? Can this be combined with a discount like I'm trying to do to add the gift card to the cart?

// @TS-check
import { DiscountApplicationStrategy } from "../generated/api";

/**
* @typedef {import("../generated/api").InputQuery} InputQuery
* @typedef {import("../generated/api").FunctionResult} FunctionResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/

/**
* @type {FunctionResult}
*/
const EMPTY_DISCOUNT = {
  discountApplicationStrategy: DiscountApplicationStrategy.First,
  discounts: [],
};

// Define a GraphQL query to fetch all products
const ALL_PRODUCTS_QUERY = `
  query AllProducts {
    products(first: 100) {
      edges {
        node {
          id
          title
          productType
        }
      }
    }
  }
`;

export default /**
* @Anonymous {InputQuery} input
* @returns {Promise<FunctionResult>}
*/
async (input) => {
  // Fetch all products from the shop using the ALL_PRODUCTS_QUERY
  const productsResponse = await fetch('https://gina-test-store123.myshopify.com/admin/api/2023-07/graphql.json', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Shopify-Access-Token': 'API-KEY',
    },
    body: JSON.stringify({ query: ALL_PRODUCTS_QUERY }),
  });
  const productsData = await productsResponse.json();
  const allProducts = productsData.data.products.edges.map(edge => edge.node);

  // Calculate the total pre-tax cost of items in the cart
  const totalPreTaxCost = input.cart.cost.subtotalAmount.amount;

  // Check if the total pre-tax cost is greater than $50
  let freeGiftVariantId;
  let giftAmount;
  if (totalPreTaxCost > 50) {
    if (totalPreTaxCost >= 50 && totalPreTaxCost <= 100) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 10;
    } else if (totalPreTaxCost > 100 && totalPreTaxCost <= 200) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 20;
    } else if (totalPreTaxCost > 200 && totalPreTaxCost <= 300) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 30;
    } else if (totalPreTaxCost > 300 && totalPreTaxCost <= 500) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 40;
    } else if (totalPreTaxCost > 500 && totalPreTaxCost <= 750) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 50;
    } else if (totalPreTaxCost > 750 && totalPreTaxCost <= 1000) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 80;
    } else if (totalPreTaxCost > 1000 && totalPreTaxCost <= 1500) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 100;
    } else if (totalPreTaxCost > 1500 && totalPreTaxCost <= 2000) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 150;
    } else if (totalPreTaxCost > 2000 && totalPreTaxCost <= 2500) {
      freeGiftVariantId = "gid://shopify/ProductVariant/46597569413437";
      giftAmount = 200;
    }
  }
  // Find the free gift product variant
  const freeGiftVariant = allProducts.find(
    product => product.id === freeGiftVariantId
  );

  // If the free gift variant is found, add it to the cart
  if (freeGiftVariant) {
    const firstCartItem = input.cart.lines[0].id;

  const cartTransformMutation = `
  mutation {
    cartTransformCreate(functionId: "388841f9-e433-4b44-9fa6-4f93cc1803e1") {
      cartTransform {
        expand {
          cartLineId: "${firstCartItem}"
          expandedCartItems: [
            {
              merchandiseId: "gid://shopify/ProductVariant/46597569413437"
              quantity: 1
            }
          ]
        }
      }
      userErrors {
        field
        message
      }
    }
  }
  `

  // Execute the cartTransformCreate mutation
  const cartTransformResponse = await fetch('https://gina-test-store123.myshopify.com/admin/api/2023-07/graphql.json', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Shopify-Access-Token': 'API-KEY',
    },
    body: JSON.stringify({ query: cartTransformMutation }),
  });
  }

  const targets = [
    {
      productVariant: {
        id: freeGiftVariant.id
      }
    }
  ];

  if (freeGiftVariant) {
    return {
      discounts: [
        {
          targets,
          value: {
            fixedAmount: {
              amount: giftAmount,
            },
          },
        },
      ],
      discountApplicationStrategy: DiscountApplicationStrategy.First,
    };
  }

  // Return a default empty discount if none of the conditions were met
  return EMPTY_DISCOUNT;
};

  

ginacostanzo
Shopify Partner
13 2 7

update: cannot use "fetch" in shopify functions. 

paulcooksome
Shopify Partner
2 0 0

Hi @ginacostanzo 

 

I have a similar need to add free gift based on cart total price.  But I need to check the total price where discount code is applied. For example if the original total cart price was $120 and customer applied 20% discount code, it will be $96.

And I'd like to offer a free gift which is provided by handle in shop's global metafield and cart total price threshold.

Assume, there is a free gift setting by total cart price >= $100, then gift should be added only when discount code is not applied. If discount code (20% discount) has been applied, this gift should not be added.  How can we check this price where discount code is applied in Shopify Function?

 

Also, how can we access global metafields of Shop?