Bug
When a subscription's product is changed with an immediate proration_behavior (e.g. invoice), and the same request also sends discount_id: null to explicitly clear an existing discount, the discount is not reliably cleared before the immediate order/billing entries are generated. The stale discount can still apply to the newly generated order, reducing (or fully zeroing) the amount charged, even though the request asked to unset it.
A separate follow-up request that changes only the discount (no product_id in the payload) does correctly clear the discount on the subscription going forward, but by then the mispriced order has already been created and paid. That order can't be corrected afterward: void only applies to pending orders, and this one is already paid.
Impact
- Customer isn't charged the intended amount for the plan switch.
- The only recovery path today is a fully separate manual one-time order against a one-time product, since the original order can't be voided or recomputed.
Suspected cause
SubscriptionService.update_product() threads a discount_unset sentinel through generate_subscription_update() / apply_update(), which should clear subscription.discount before billing. Existing test coverage (test_proration_with_discount_change) doesn't clearly include the immediate invoice (bill-now) proration behavior specifically combined with a discount removal. Worth checking whether, for proration_behavior=invoice, billing entries/the order get built from the subscription's discount before the unset is actually applied and persisted.
Suggested fix
- Add test coverage for
proration_behavior=invoice combined with clearing an existing discount as part of the same product-change request.
- Ensure discount resolution/clearing happens before billing entries and the order are generated, consistently across all immediate proration behaviors.
Sent by @allison-polar from Subscription c6baa936 charge investigation.
Bug
When a subscription's product is changed with an immediate
proration_behavior(e.g.invoice), and the same request also sendsdiscount_id: nullto explicitly clear an existing discount, the discount is not reliably cleared before the immediate order/billing entries are generated. The stale discount can still apply to the newly generated order, reducing (or fully zeroing) the amount charged, even though the request asked to unset it.A separate follow-up request that changes only the discount (no
product_idin the payload) does correctly clear the discount on the subscription going forward, but by then the mispriced order has already been created and paid. That order can't be corrected afterward: void only applies topendingorders, and this one is alreadypaid.Impact
Suspected cause
SubscriptionService.update_product()threads adiscount_unsetsentinel throughgenerate_subscription_update()/apply_update(), which should clearsubscription.discountbefore billing. Existing test coverage (test_proration_with_discount_change) doesn't clearly include the immediateinvoice(bill-now) proration behavior specifically combined with a discount removal. Worth checking whether, forproration_behavior=invoice, billing entries/the order get built from the subscription's discount before the unset is actually applied and persisted.Suggested fix
proration_behavior=invoicecombined with clearing an existing discount as part of the same product-change request.Sent by @allison-polar from Subscription c6baa936 charge investigation.