CLI updates, discount functions no longer run

Recently updated the Shopify CLI and I can no longer get my discount functions to trigger on my development store. The exact same code that is running in my production app no longer triggers in my development store and Im at my wits end with how to fix it. Suddenly yesterday afternoon my functions now no longer trigger on cart updates.

Things Ive tried:

  • Deleted the discount function and tried recreating it
  • Reconfigure the discounts UI in the dev store
  • Downgrade shopify CLI to 84.1
  • Adding console logs and watching the function in the console, never triggers
  • Uninstalling, reinstalling the app on different stores, recreating discounts, nothing
  • Blew away my whole repo to get rid of any build artifacts, same
  • Tried resetting the app config, nada

I know all the values are being set its just that when I post a value into the cart that my discount function should pickup, the cart never renders the discount. The function that hasnt been deployed since August continues to work fine in my store on shop.xpnetwork.com

My Development app renders fine as does the discount UI for the function. I can see the proper cart attributes being set in the cart for the function to pick them up. I have no build errors in watch or on deploy.

fetch(‘/cart.js’).then(r => r.json()).then(cart => console.log(‘Cart attributes:’, cart.attributes))
Promise {}
VM519:1 Cart attributes:
{_xp_points_to_use: ‘280’, _xp_user_token: ‘eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4Y…DAwfQ.Ih2_V8zrYyi2R1QEnhl8jpwquF8Cq8N1qgZs3tEsqO4’}
_xp_points_to_use
:
“280”

$ shopify --version
@shopify/cli/3.85.5 darwin-x64 node-v22.17.1

The only thing of note is that I converted the function to typescript at one point and after that is when it stopped working. I reverted it back to JS and it still doesn’t work as it did before. These runs were all right before it stopped working with the exact same code. Dont know why this would matter as it is compiling into dist folder just fine even with the change …

// extensions/xpn-redeem-function/node_modules//shopify_function/run.tsfunction run_default(userfunction) {const input_obj = Javy.JSON.fromStdin();const output_obj = userfunction(input_obj);Javy.JSON.toStdout(output_obj);}
// extensions/xpn-redeem-function/src/cart_discounts_generate_run.jsfunction cartDiscountsGenerateRun(input) {const config = parseDiscountConfiguration(input.discount?.metafield?.value);const userXPPoints = getUserXPPointsFromCart(input.cart);console.log(“userXPPoints”, userXPPoints);if (userXPPoints === 0) {return { operations:  };}const discountClasses = input.discount?.discountClasses || ;const hasOrderDiscountClass = discountClasses.includes(“ORDER” /* Order /);const hasProductDiscountClass = discountClasses.includes(“PRODUCT” / Product /);const hasShippingDiscountClass = discountClasses.includes(“SHIPPING” / Shipping /);if (!hasOrderDiscountClass && !hasProductDiscountClass && !hasShippingDiscountClass) {return { operations: [] };}let activeDiscountType = null;if (hasOrderDiscountClass) {activeDiscountType = “order”;} else if (hasProductDiscountClass) {activeDiscountType = “product”;} else if (hasShippingDiscountClass) {activeDiscountType = “shipping”;}const isPureDeliveryFunction = activeDiscountType === “shipping”;if (isPureDeliveryFunction) {return handleDeliveryDiscounts(input, config, userXPPoints);} else {return handleCartLineDiscounts(input,config,userXPPoints,activeDiscountType === “order”,activeDiscountType === “product”);}}function handleDeliveryDiscounts(input, config, userXPPoints) {const operations = [];if (userXPPoints >= config.shippingXPRate && input.cart.deliveryGroups?.length > 0) {const firstDeliveryGroup = input.cart.deliveryGroups[0];const maxPossibleDiscount = Math.floor(userXPPoints / config.shippingXPRate);const cartSubtotal = parseFloat(input.cart.cost?.subtotalAmount?.amount || “0”);const discountAmount = cartSubtotal <= 0 ? 0 : Math.min(maxPossibleDiscount, Math.ceil(cartSubtotal));const xpUsed = discountAmount * config.shippingXPRate;if (discountAmount > 0) {operations.push({deliveryDiscountsAdd: {candidates: [{message: $${discountAmount} OFF SHIPPING (${xpUsed} XP),targets: [{deliveryGroup: {id: firstDeliveryGroup.id}}],value: {fixedAmount: {amount: discountAmount.toString()}}}],selectionStrategy: “ALL” / All /}});}}return { operations };}function handleCartLineDiscounts(input, config, userXPPoints, hasOrderDiscountClass, hasProductDiscountClass) {const operations = [];let remainingXP = userXPPoints;const cartSubtotal = parseFloat(input.cart.cost?.subtotalAmount?.amount || “0”);if (hasOrderDiscountClass && remainingXP >= config.orderXPRate) {const maxPossibleDiscount = Math.floor(remainingXP / config.orderXPRate);const discountAmount = cartSubtotal <= 0 ? 0 : Math.min(maxPossibleDiscount, Math.ceil(cartSubtotal));const xpUsed = discountAmount * config.orderXPRate;if (discountAmount > 0) {operations.push({orderDiscountsAdd: {candidates: [{message: $${discountAmount} OFF ORDER (${xpUsed} XP),targets: [{orderSubtotal: {excludedCartLineIds: []}}],value: {fixedAmount: {amount: discountAmount.toString()}}}],selectionStrategy: “FIRST” / First /}});remainingXP -= xpUsed;}}if (!hasOrderDiscountClass && hasProductDiscountClass && remainingXP >= config.cartLineXPRate && input.cart.lines?.length > 0) {const eligibleCartLines = getEligibleCartLines(input.cart.lines, config.collectionIds);if (eligibleCartLines.length > 0) {const maxPossibleDiscount = Math.floor(remainingXP / config.cartLineXPRate);const discountAmount = cartSubtotal <= 0 ? 0 : Math.min(maxPossibleDiscount, Math.ceil(cartSubtotal));const xpUsed = discountAmount * config.cartLineXPRate;const maxEligibleLine = eligibleCartLines.reduce((maxLine, line) => {if (line.cost.subtotalAmount.amount > maxLine.cost.subtotalAmount.amount) {return line;}return maxLine;}, eligibleCartLines[0]);if (discountAmount > 0) {operations.push({productDiscountsAdd: {candidates: [{message: $${discountAmount} OFF PRODUCT (${xpUsed} XP),targets: [{cartLine: {id: maxEligibleLine.id}}],value: {fixedAmount: {amount: discountAmount.toString()}}}],selectionStrategy: “FIRST” / First /}});remainingXP -= xpUsed;}}}return {operations};}function deliveryDiscountsGenerateRun(input) {const config = parseDiscountConfiguration(input.discount?.metafield?.value);const userXPPoints = getUserXPPointsFromCart(input.cart);if (userXPPoints === 0) {return { operations: [] };}const discountClasses = input.discount?.discountClasses || [];const hasShippingDiscountClass = discountClasses.includes(“SHIPPING” / Shipping /);const hasProductDiscountClass = discountClasses.includes(“PRODUCT” / Product /);const hasOrderDiscountClass = discountClasses.includes(“ORDER” / Order */);if (!hasShippingDiscountClass || hasProductDiscountClass || hasOrderDiscountClass) {return { operations:  };}return handleDeliveryDiscounts(input, config, userXPPoints);}function parseDiscountConfiguration(metafieldValue) {try {const parsed = JSON.parse(metafieldValue || “{}”);return {cartLineXPRate: Number(parsed.cartLineXPRate || 100),orderXPRate: Number(parsed.orderXPRate || 100),shippingXPRate: Number(parsed.shippingXPRate || 100),collectionIds: parsed.collectionIds || };} catch {return {cartLineXPRate: 100,orderXPRate: 100,shippingXPRate: 100,collectionIds: };}}function getEligibleCartLines(cartLines, collectionIds) {if (!cartLines || collectionIds.length === 0) {return cartLines || ;}return cartLines.filter((line) => {return line.merchandise?.product?.inAnyCollection && collectionIds.some((collectionId) => line.merchandise.product.inCollections?.some((collection) => collection.collectionId === collectionId));});}function getUserXPPointsFromCart(cart) {const xpPointsToUse = cart.attribute?.value;if (!xpPointsToUse) {return 0;}const xpAmount = parseInt(xpPointsToUse, 10);return Number.isFinite(xpAmount) ? Math.max(0, xpAmount) : 0;}
// function cartLinesDiscountsGenerateRun() {return run_default(cartDiscountsGenerateRun);}function cartDeliveryOptionsDiscountsGenerateRun() {return run_default(deliveryDiscountsGenerateRun);}export {cartDeliveryOptionsDiscountsGenerateRun,cartLinesDiscountsGenerateRun};

Theres the compiled JS. Please help im cooked here.

Hey @Xpncto thanks for reaching out and for the detailed steps there. If possible, can you check Chrome Dev Tools(both Console and Network tabs) when you add items to cart and see if you’re seeing any errors there or failed API calls if you haven’t checked already?

Additionally, can you run shopify app deploy --verbose and share any warnings or errors you see? The verbose flag can sometimes reveal configuration issues or function registration problems that don’t show up in normal output. Since you mentioned converting to TypeScript and then reverting back to JavaScript, can you also double-check your shopify.app.toml configuration to make sure the function target and entry point are still correct?

If you’ve checked all of those, and you’re not seeing any errors/things to flag, feel free to ping me here anyhow and we can take a look. More than happy to investigate this with you for sure.

No errors here in console. Cart attributes appear to be set:

Dont see anything sus in the verbose deploy beyond warnings that have been fine in the past: Context – share whatever you see with others in seconds

Ive also created a ticket here so don’t want to replicate effort if thats going on in the background

Thank you for contacting Shopify Plus Support today. Your chat has been logged below and can be referenced by the ticket number 60879965.

Thanks @Xpncto , appreciate you trying that. If you’re open to sharing the myshopify.com URL for your dev store (I can see part of it in your screenshot there), I can do some further digging on my end and get this looked into for you.

Thank you!

1 Like

Thanks, @Xpncto , I’ll look into this more deeply for you on my end and loop back once I have more to share.