Hi @Ian_Bale,
Thanks so much for your reply 
The percentage workaround you’ve found is fantastic - I think for your exact use case it’s actually a better fit than what I suggested.
Please see the following with regards to each of your points & questions:
Your percentage workaround
100 / quantity * discountedQuantity should the right formula - my understanding is that Shopify POS divides the percentage before allocating per unit, so the £2.99 ÷ 3, round, × 3 rounding cascade that affects FixedAmount never happens. For 4 coffees with 1 free → 25%, 25% of £11.96 = £2.99 exactly, which is much cleaner.
Split-row UX concern
I do agree with you about your concerns with using split rows for this. Once you have a quantity-3 paid line and a quantity-1 free line, the cashier can change either quantity independently - and as you say, they could push the free line above the redeemable stamp count without realising. The percentage approach keeps everything on one line, which is the natural UX for “X out of Y of these are free.” Line splitting is realy good for intrinsic per-item differences (monograms, allergen flags, “no cream”) rather than “this one’s free” situation.
The one tradeoff you’ve already mentioned: when the cashier changes the quantity, the percentage has to be recalculated. That means subscribing to cart changes and reapplying.
It’s churn, but it’s predictable churn - and arguably easier to reason about than defending a split row from quantity edits.
On the timing question
Yes - I think your suspicion about the earlier attempt is right. The sequencing has to be:
const uuidA = await shopify.cart.addLineItem(variantId, 1);
await shopify.cart.addLineItemProperties(uuidA, { … }); // must finish first
const uuidB = await shopify.cart.addLineItem(variantId, 1);
Without the await in between, both adds can land at POS before the property write does, and they get merged into a single quantity-2 line - the property eventually arrives but the split has already been lost. Awaiting each step in order what you need.
On bulkCartUpdate and change events
I presume the bulk update would result in just one change event?
That’s my understanding of the docs too - it’s described as a single bulk operation that returns the updated cart once everything has been applied. I’d expect one consolidated change-event emission rather than one per field touched. Worth a 30-second sanity check on your end with a shopify.cart.current.subscribe(c => console.log('tick', c)) while you fire the bulk update - but the design intent clearly points that way, and it’s exactly the reason to prefer it over your current lineItemProperty + cartProperty + cartDiscount sequence.
For your specific 3-event pattern, bulkCartUpdate handles all three of those in a single input shape:
await shopify.cart.bulkCartUpdate({
lineItems: rebuiltLineItems, // includes the line-item-property change
properties: { …updatedCartProperties }, // cart-level properties
cartDiscount: { …yourDiscount }, // cart-level discount
note: currentCart.note,
customer: currentCart.customer,
});
So even if you’re already doing a merge of mutations, that is the next rung down.
Re-applying line-item discounts after a bulk update
This is a good question, and definitely worth confirming. The docs describe LineItem discounts as read-only output on the cart - there isn’t a documented field for handing discounts back in via the LineItem[] you pass to bulkCartUpdate. So while you could try it, I wouldn’t rely on it surviving a future SDK change.
The pattern I’d use instead is to chain a bulkSetLineItemDiscounts call right after, which batches every line-item discount into a single mutation:
const updatedCart = await shopify.cart.bulkCartUpdate({
lineItems: rebuiltLineItems,
note: currentCart.note,
customer: currentCart.customer,
});
// Re-apply (or freshly apply) line-item discounts in one batched call.
await shopify.cart.bulkSetLineItemDiscounts([
{
lineItemUuid: someUuid,
lineItemDiscount: { type: 'Percentage', title: 'Stampcard reward', amount: '100' },
},
// …more entries if you have them
]);
Two mutations total - but each is its own bulk operation, so you should see two change-events instead of the 3+ you currently get, and the line-item-discount step covers all discounts at once rather than one per call.
On FixedAmount and percentage-as-property
I definitely agree on both. A truly fixed FixedAmount (no per-unit re-allocation) would eliminate exactly the cart churn you’re trying to escape, and exposing the percentage value itself on the line item - not just the calculated money amount - would make quantity-reactive logic dramatically simpler.
Hope that’s useful - and nice find on the percentage formula. I’d honestly recommend that as the primary approach for the stampcard flow over what I suggested.
Thanks so much Ian,
Adam 