Medusa spend budgets can fail carts at checkout: what to check
In Medusa v2.22.2, spend-capped campaigns can show a discount that fails at cart completion. How to spot affected campaigns and guard checkout before peak.
- Medusa.js
- Migrations
- Operations

A shopper adds two items, enters a promo code and sees the discount in the cart. They enter payment details and click place order. The request comes back 400 not_allowed: Promotion usage exceeds the budget limit. No order is created. The only way through is to remove the code and pay full price, and most shoppers won't do that. They leave.
That is what Medusa issue #17598 describes for campaigns with a spend budget. It was reproduced on v2.22.2 (the develop branch at commit 7e797dfd). If you run capped promotions on Medusa, especially ones you plan to push hard in late November, you need to know exactly when this triggers.
The cart and the completion step check the budget differently
A Medusa campaign can carry a budget. A usage budget caps how many times its promotions are used. A spend budget (and spend_by_attribute) caps the total discount amount given out. The bug only affects the spend types.
Two parts of the promotion module check that budget:
computeActionsruns when the cart is priced. For each adjustment it creates, it checksbudget.used + this_adjustment_amount <= limit.registerUsageruns when the cart is completed. It adds up all of the cart's adjustments for the campaign and checks the total against the same limit.
According to the issue, computeActionForBudgetExceeded in packages/modules/promotion/src/utils/compute-actions/usage.ts only sees the amount of the adjustment it was called for. Nothing keeps track of amounts already given out earlier in the same pricing pass. The line-item, shipping-method and buy-get helpers call it once per item. Each adjustment fits on its own, so the cart shows the full discount, and then completion rejects the total.
The reproduction is short. Create a campaign with a spend budget of 100 USD. Attach a fixed promotion worth 150 that targets items with allocation: across. Build a cart with two $100 items. The cart gets two adjustments of 75. Each one passes the per-adjustment check, because 0 + 75 is under 100. Completion adds them up to 150, which exceeds 100, and returns the 400. budget.used stays at 0, so the failed attempt doesn't use up any budget. The next shopper with the same cart shape hits the same wall.
Which promotions are exposed
Any setup that creates more than one adjustment against the same spend-budgeted campaign in one cart is exposed. The issue lists:
- fixed
acrosspromotions spread over several line items - fixed
eachpromotions on several items (60 + 60 against a limit of 100) - shipping-method promotions on several shipping methods
buygetpromotions that apply more than once- two promotions in the same campaign applied to one cart
The single-adjustment case is already handled. If one adjustment would exceed the budget by itself, it is skipped at pricing time with campaignBudgetExceeded. The trap is only the multi-adjustment case. Campaigns with usage or use_by_attribute budgets behave as before.
The budget doesn't need to be close to empty. In the reproduction it hasn't been touched. Any cart whose combined discount exceeds the remaining budget fails. That window grows toward the end of a campaign. Once the remaining budget is smaller than a typical multi-item discount, a large share of carts using the code will fail.
How to tell whether your campaigns are affected
Go through your active and scheduled campaigns and answer three questions for each one:
- Is the budget type
spendorspend_by_attribute? - Can a promotion in it create more than one adjustment per cart? That means across or each allocation over several items, several shipping methods, or a buy-get that repeats. Or does the campaign hold more than one promotion that can stack?
- Can the total discount in one cart exceed the remaining budget?
If the answer to all three is yes, a shopper can reach checkout with a discount they can't keep.
Then check what has already happened. Search your logs for cart completion requests that returned not_allowed with the message Promotion usage exceeds the budget limit., and match them to the promo codes on those carts. The other signal is carts that have a promo code and a payment session but never completed. If your storefront turns every error into a generic failure message, support tickets won't have told you about this.
You can also reproduce it on staging. Copy the campaign setup with a small limit, build a cart with two items and call POST /store/carts/:id/complete. If it returns a 400, you have the problem.
Guards to put in place before peak traffic
The person who reported the issue says they have a fix with regression tests ready. It keeps a per-call map of the amount already given out from each budget, so the check becomes used + allotted + amount. Until a fix ships in a release you run and you have tested it, assume v2.22.2 behaves as described. Don't plan Black Friday around a pull request that hasn't been merged.
Leave headroom. Set the limit to what you can actually afford, then alert well before it is reached. Failures start once the remaining budget drops below the largest discount one cart can get. For an across promotion worth 150, any remaining budget under 150 is a live risk. At that point, end the campaign yourself instead of letting checkout enforce the limit.
Prefer setups that create one adjustment. If the business goal allows it, a usage budget instead of a spend budget avoids the problem entirely. Don't put several stackable promotions in one spend-capped campaign.
Check the budget before completion. Before calling complete, add up the cart's adjustments per campaign and compare each total to limit - used. If it doesn't fit, remove the code and show the shopper a clear message on the cart page, where they can still decide what to do. This check can go in a validation step in your cart-completion flow, or in the storefront just before it calls complete. It doesn't prevent races, since two carts can pass at the same moment. But in the common case the shopper sees a message they can act on instead of a failed checkout.
Handle the error in the storefront. If completion returns this specific 400, don't show a generic failure. Tell the shopper the promotion is no longer available, remove the code, refresh the totals and let them pay. Losing the discount annoys the shopper. Losing the order costs you the sale.
Alert on failed completions. Count completion failures by error type and promo code, and send an alert when the rate climbs. During a sale, a dashboard someone checks the next morning is too late.
Why this matters for migrations
Teams moving from Magento 2 usually bring a promotions model built on cart price rules, with caps on uses per coupon and uses per customer. A spend-capped Medusa campaign looks like a natural replacement, and for many rules it is. But it enforces its cap at a different point, and this issue shows that even Medusa's own two checks can disagree. When we plan a migration, we map promotion rules one at a time. We test carts with several items and several shipments, not just a single-item order. Our approach is described on Magento 2 to Medusa or Vendure migrations.
What to do this week
List every spend-capped campaign scheduled for November and run the two-item reproduction on staging for each one. Add storefront handling for the budget error whatever the result, because it's cheap. Then decide for each campaign: switch to a usage budget, set a limit with headroom, or add the pre-completion check. Watch issue #17598 and the Medusa release notes for a fix, and test again when one ships.
If you want someone else to look over your promotion setup or checkout flow before peak, get in touch.
Need this done on a real stack?
Magento 2, Adobe Commerce, migrations to Medusa.js or Vendure, enterprise Next.js, WordPress, and AI automation.
Contact us