Discussing Shopify Functions development, deployment, and usage in Shopify apps.
Hi all --
Our set up is somewhat complex, but I think other people will have the same set up and issues.
We have "membership" pricing for our Subscription oriented store, any non-logged in user can purchase a recurring subscription of a product at a lower price than a one time purchase. They can also purchase it as a one time at a MUCH higher price. When logged in with the tag "Active Subscriber" our subscribers (we use Recharge and Recharge adds the tag "Active Subscriber" to those with recurring subscriptions) can purchase from that same product as a one time, with a substantial reduction in price (the benefit for subscribing).
This is a key factor in our subscription retention strategy -- subscribers can purchase one-time products at much lower prices than non-subscribers.
We don't have duplicate products -- it became too much of a problem for our limited staff to manage every month (we are monthly based). Instead the discount for subscribers purchasing one times is done through Shopify scripts. Shopify Scripts will go away soon, and the script we have is buggy and runs out of memory when we have big promotions and lots of purchases.
However the Shopify Script will stack with BOGO discounts, for example, if I have the Membership Discount product and one other product that SHOULD give me a free other product. However ONLY when I add another of the other product will the cart and checkout update to the free product with the Shopify Script disabled and the Shopify function active:
the Discount is set up like this:
Note, again this works with the Shopify Script, but not with the Shopify Function which duplicates the functionality of the script.
The script was installed to stack:
So what am I doing wrong? Do I need to push in the app another function that does the BOGO? Is there a trick to setting up a Discount manually in Shopify as BOGO to make it work with a Shopify Function discount?
Here is mutation used to install the discount on the (test/staging) store:
mutation { discountAutomaticAppCreate(automaticAppDiscount: { title: "Member Pricing", functionId: "the_id_of_the_function", startsAt: "2024-03-10T00:00:00", combinesWith: { orderDiscounts: true, productDiscounts: true, shippingDiscounts: true }, }) { automaticAppDiscount { discountId } userErrors { field message } } }
Note the function's discountApplicationStrategy is set to All :
discountApplicationStrategy: DiscountApplicationStrategy.All
No doubt someone has run into this before, any help greatly appreciated.
Hello again!
Um, this one might be a little harder, so here is an function a wrote a little bit ago that does work with other discounts.
// @TS-check
import { DiscountApplicationStrategy } from "../generated/api";
/**
* @typedef {import("../generated/api").RunInput} RunInput
* @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/
const EMPTY_DISCOUNT = {
discountApplicationStrategy: DiscountApplicationStrategy.All,
discounts: [],
};
/**
* @Anonymous {RunInput} input
* @returns {FunctionRunResult}
*/
export function run(input) {
const configuration = JSON.parse(input?.shop?.metafield?.value ?? "{}");
const discounts = [];
// Check for customer tags
const customerTags = input.cart.buyerIdentity.customer.hasTags;
let discountTag = null;
if (customerTags.some(tagInfo => tagInfo.tag === "vip" && tagInfo.hasTag)) {
discountTag = "VIP";
} else if (customerTags.some(tagInfo => tagInfo.tag === "Pro" && tagInfo.hasTag)) {
discountTag = "Pro";
}
if (!discountTag) {
return EMPTY_DISCOUNT;
}
input.cart.lines.forEach(line => {
if (line.merchandise.__typename === "ProductVariant") {
const variant = /** @type {ProductVariant} */ (line.merchandise);
const product = variant.product;
product.inCollections.forEach(collection => {
if (collection.isMember) {
const collectionDiscount = configuration[discountTag].collections[collection.collectionId];
if (collectionDiscount) {
const discountPercentage = parseInt(collectionDiscount.replace('%', ''), 10);
discounts.push({
message: `${collectionDiscount} off`,
targets: [{ productVariant: { id: variant.id } }],
value: {
percentage: { value: discountPercentage }
}
});
}
}
});
}
});
return discounts.length > 0 ? { discountApplicationStrategy: DiscountApplicationStrategy.All, discounts } : EMPTY_DISCOUNT;
};
Without seeing your discount function it would be really hard to troubleshoot, so maybe you can use my code to fix your issue? 🙂
Hi My discount works, but excludes the Membership Discount function.
This is my run.js function:
// @TS-check
import { DiscountApplicationStrategy } from "../generated/api";
// Use JSDoc annotations for type safety
/**
* @typedef {import("../generated/api").RunInput} RunInput
* @typedef {import("../generated/api").FunctionRunResult} FunctionRunResult
* @typedef {import("../generated/api").Target} Target
* @typedef {import("../generated/api").ProductVariant} ProductVariant
*/
/**
* @type {FunctionRunResult}
*/
const EMPTY_DISCOUNT = {
discountApplicationStrategy: DiscountApplicationStrategy.All,
discounts: [],
};
// The configured entrypoint for the 'purchase.product-discount.run' extension target
/**
* @Anonymous {RunInput} input
* @returns {FunctionRunResult}
*/
export function run(input) {
const customer1 = input.cart.buyerIdentity?.customer?.active_sub;
console.error("customer1 value = " + customer1);
const targets = input.cart.lines
// Only include cart lines with a quantity of two or more
// and a targetable product variant
.filter(line => line.quantity >= 1 &&
line.merchandise.__typename == "ProductVariant" && customer1 == true && line.merchandise.product.member_price == true && line.sellingPlanAllocation?.sellingPlan.name != "Delivery every 1 Month" && line.sellingPlanAllocation?.sellingPlan.name != "Delivery every 1 Month, Charge every 3 Months")
.map(line => {
const variant = /** @type {ProductVariant} */ (line.merchandise);
return /** @type {Target} */ ({
// Use the variant ID to create a discount target
productVariant: {
id: variant.id
}
});
});
if (!targets.length) {
// You can use STDERR for debug logs in your function
console.error("No cart lines qualify for volume discount.");
return EMPTY_DISCOUNT;
}
// The @Shopify/shopify_function package applies JSON.stringify() to your function result
// and writes it to STDOUT
return {
discounts: [
{
// Apply the discount to the collected targets
targets,
// Define a percentage-based discount
value: {
percentage: {
value: "44.47"
}
}
}
],
discountApplicationStrategy: DiscountApplicationStrategy.All
};
};
Do I need to include the BOGO function in the run.js as well? If so, how would I go about doing that? Is there some template or code base with an example of multi-discount functions? I know an app can have up to 100 functions, do I need to create another function and push that to the App?
I am new to Shopify functions, this is my first attempt.
Thanks.