DiscountFixed.get_discount_amount does a direct self.amounts[currency] lookup with no fallback. If a merchant edits a DiscountFixed discount (e.g. via DiscountService.update, especially through the legacy amount+currency fields, which replaces the whole amounts map) while an open, unconfirmed checkout still references that discount and a now-removed currency, the checkout ends up in an inconsistent state. DiscountService.update() only blocks the edit when redemptions_count > 0, which doesn't cover this case. Reading the checkout afterwards (e.g. GET /v1/checkouts/client/{client_secret}) then serializes discount_amount and crashes with an unhandled KeyError.
Example: SERVER-4VF, checkout 7e54d8a5-f92c-423e-aaf9-d04a9b48afd5, KeyError: 'czk'.
Instead of only guarding against the edit or patching the read path, DiscountService.update() should proactively reconcile any open checkouts referencing the discount when amounts/currency change: re-run the existing is_applicable(product, currency) check for each affected open checkout and, when it no longer holds, null out the checkout's discount and recompute its totals (net_amount, tax_amount, etc.) the same way _update_checkout already does. This keeps checkouts consistent immediately after the discount edit rather than deferring to (or crashing on) the next checkout read/update.
Related prior art: #13140 fixed the same underlying amounts[currency] indexing issue for the subscription resolve path only (feedback#171); #12339 fixed a similar currency-mismatch crash in checkout product/price updates. Neither covers a discount being edited out from under an already-open checkout.
Sent by @frankie567 from Stilla investigation.
DiscountFixed.get_discount_amountdoes a directself.amounts[currency]lookup with no fallback. If a merchant edits aDiscountFixeddiscount (e.g. viaDiscountService.update, especially through the legacyamount+currencyfields, which replaces the wholeamountsmap) while an open, unconfirmed checkout still references that discount and a now-removed currency, the checkout ends up in an inconsistent state.DiscountService.update()only blocks the edit whenredemptions_count > 0, which doesn't cover this case. Reading the checkout afterwards (e.g.GET /v1/checkouts/client/{client_secret}) then serializesdiscount_amountand crashes with an unhandledKeyError.Example: SERVER-4VF, checkout
7e54d8a5-f92c-423e-aaf9-d04a9b48afd5,KeyError: 'czk'.Instead of only guarding against the edit or patching the read path,
DiscountService.update()should proactively reconcile any open checkouts referencing the discount whenamounts/currencychange: re-run the existingis_applicable(product, currency)check for each affected open checkout and, when it no longer holds, null out the checkout's discount and recompute its totals (net_amount,tax_amount, etc.) the same way_update_checkoutalready does. This keeps checkouts consistent immediately after the discount edit rather than deferring to (or crashing on) the next checkout read/update.Related prior art: #13140 fixed the same underlying
amounts[currency]indexing issue for the subscription resolve path only (feedback#171); #12339 fixed a similar currency-mismatch crash in checkout product/price updates. Neither covers a discount being edited out from under an already-open checkout.Sent by @frankie567 from Stilla investigation.