Concatenated billing attempts refused since 2026-09-24 (API 2026-01)

Concatenated billing attempts refused since 2026-09-24 (API 2026-01)

Summary

Since 2026-09-24 01:04 UTC, subscriptionBillingAttemptCreate refuses a billing attempt on a contract whose billing cycle has other contracts’ cycles concatenated onto it, whenever one of the involved cycles was already billed. The same calls were accepted until at least 2026-09-22, and we found no changelog entry for the change.

  • Error: “A billing cycle concatenated with this one has already been billed, or has a billing attempt that hasn’t finished. Wait for any unfinished attempt, then dissolve the concatenation before charging.” (userErrors, field subscriptionContractId, code INVALID)
  • Impact: in our first nightly run after the change, 41 combined orders could not be charged. The 30 days before had none.
  • API version: 2026-01.
  • Still working: a plain billing attempt (no concatenation) on an already billed cycle is accepted.

How we create a combined order

We charge all due subscription contracts of one customer as one order: the other contracts’ cycles are concatenated onto the lead contract’s current cycle, then the lead is billed.

  1. subscriptionBillingCycleContractEdit on the lead contract, selector {date: <now>}
  2. subscriptionBillingCycleContractDraftConcatenate with each other contract, selector {date: <now>}
  3. subscriptionBillingCycleContractDraftCommit
  4. subscriptionBillingAttemptCreate on the lead contract, with an idempotency key and no billingCycleSelector
  5. After the attempt, successful or not, we remove the cycle edits again with subscriptionBillingCycleEditsDelete.

We set next billing dates ourselves (customer requests, delivery planning), so they do not follow Shopify’s billing-cycle schedule. The cycle that contains “now” can therefore already have been billed by an earlier order.

Previous behaviour (until at least 2026-09-22)

The same calls were accepted when the lead’s current cycle already held an earlier order inside its date range. In production, combined orders were accepted this way on 2026-09-10, 2026-09-13, 2026-09-17, 2026-09-18, 2026-09-21 and 2026-09-22.

On our development store, two contracts were billed together 12 times inside one billing cycle between 2026-09-01 and 2026-09-17. All 12 were accepted.

Current behaviour (since 2026-09-24)

The first refusal came at 2026-09-24 01:04:44 UTC. That night, 41 combined billing attempts were refused on every one of their 5 tries. The other 426 combined billing attempts that night did not get this error.

In all 41, at least one involved cycle (the lead’s, or a concatenated one) had status BILLED from an earlier order. In the cases we checked, those earlier attempts had finished (ready, with an order), so nothing was left to wait for.

Example: two contracts were billed together on 2026-09-07 and share a cycle running 2026-09-06 → 2026-11-15. Their combined billing on 2026-09-24 was refused.

Reproduction on a development store

We reproduced it on 2026-09-24 (API version 2026-01) with contracts created that day on a 4-week billing policy. “Billed” means the cycle already carried an order.

Test Setup Result
1 Lead cycle unbilled, other contract’s cycle unbilled, concatenated by date Accepted, one order
2 Lead cycle unbilled, other contract’s cycle billed, concatenated by date Refused with the error above
3 As test 2, but the other contract concatenated by its next unbilled cycle (selector by index) Accepted
4 Lead cycle billed, other contract’s cycle unbilled Refused with the error above
5 Lead cycle billed, other contract concatenated by its next unbilled cycle Refused with the error above
6 Bill the lead’s next unbilled cycle now (billingCycleSelector by index) Refused: ORIGIN_TIME_OUT_OF_RANGE
7 Move that cycle’s billing date to now (subscriptionBillingCycleScheduleEdit) Refused: OUT_OF_BOUNDS
8 Bill the next cycle with originTime inside it Accepted, but the order is scheduled for that future date
9 Plain billing attempt, no concatenation, on a billed cycle Accepted

So a combined order is only possible when the lead’s cycle and every concatenated cycle are unbilled.

Questions

  1. Was this validation introduced on purpose between 2026-09-22 and 2026-09-24? Is there an announcement we missed?
  2. What is the supported way to bill several contracts of one customer as one order when a cycle involved was already billed earlier? Apps that set billing dates themselves run into this regularly.
  3. Can concatenation onto an already billed cycle be allowed again, as it was until 2026-09-22? If not, is there a supported way to get an unbilled cycle for “now”, for example by re-anchoring the billing-cycle schedule?
  4. When we bill a future cycle with originTime inside it, can the order be fulfilled right away instead of being scheduled for that date?

We can share store, contract and order IDs privately with Shopify staff.

Hi @Fabian_Bosler, thanks for the detailed writeup. This one was on us.

A change on September 23 meant to prevent duplicate charges across concatenated billing cycles was also blocking legitimate subsequent billing. That impact was unintended, which is why there was no changelog entry. The fix deployed on September 30 and it covers exactly your failing cases. Concatenating onto an already-billed cycle is accepted again, whether the billed cycle is the lead’s or a concatenated one. No re-anchoring or workaround is needed, and your existing combined-order flow should work as before.

To recover the missed charges, first confirm Shopify didn’t create a new order for those requests and that you haven’t already recovered them another way. Wait for any in-flight billing attempt to finish, then retry your original request with the same idempotency key and inputs. Don’t generate new keys. A refused attempt doesn’t create an order or take payment, so those 41 requests shouldn’t have produced charges. The reconcile step is still worth running first, since an attempt that was in flight on those cycles could have finished on its own.

On the originTime question, an attempt with a future origin time creates a scheduled order. You can still open and fulfill the resulting fulfillment order early with the Fulfill early option in the admin or the fulfillmentOrderOpen mutation. Check the fulfillment order’s status first, since only scheduled orders are eligible.

If a retried request still fails or returns an old order, send over the request ID from the response header and I can take a closer look. Thanks again for your understanding on this!

Hi Donal,

Thanks for the answer. Yea, def not ideal. We had to spend like 2 days to firefight this and in essence rebuild the concatenation logic.

In your opinion, is there benefit to go back to concatenation vs. temporarily adding all items to the lead contract and then removing them after charging?

I remember having raised an issue with Shopify about the linked subscriptions on an order resulting from concatenated contracts being incorrect like 2 years ago. All products on the order would reference the lead contract and not the contract they are coming from. Do you happen to know if that has been addressed?

Is there another advantage when it comes to discounts maybe?

I want to decide if we should go back to concatenation.

Cheers
Fabian

@Fabian_Bosler sorry to hear the issue cost you two days of rebuilding.

The main benefit of going back is convenience. The billing cycle edit natively merges each contract’s lines and discounts into the draft, so you don’t have to reproduce that mapping yourself. That’s a maintenance difference, not a pricing one. If your rebuilt flow already preserves the pricing and discount scope you intend, I wouldn’t switch back now that the fix is deployed.

The attribution behavior has yet to be fixed. Line items on concatenated orders still resolve to the contract that was billed rather than the contract each item came from. I’ve re-checked and it’s still tracked with the team, so I can’t promise a fix date.

On discounts, concatenation carries each contract’s item discounts with their scoped line entitlements. One caveat: recurring cycle limits on the non-lead contracts’ discounts aren’t reliably tracked when billing runs through the lead contract, so don’t count on usageCount incrementing correctly for those. I can’t say whether your temporary approach handles limits better without seeing how you apply those discounts.

If you want a precise comparison, share how your rebuilt flow applies the discounts and I can look closer.

@Donal-Shopify thanks for the answer Donal!

I just realized the last payment status also does not get updated by the concatenated contracts. So it really is a bit of a half-baked feature :smiley: