Support stacking multiple discount codes #2

Open
greptile-bot wants to merge 4 commits from add-discount-stacking into main
+10
View File
@@ -0,0 +1,10 @@
import type { Discount } from './cart'
/** Combine multiple discount codes into one effective factor. */
export function applyDiscounts(subtotalCents: number, discounts: Discount[]): number {
let totalPercentOff = 0
for (const discount of discounts) {
totalPercentOff += discount.percentOff
}
return Math.round(subtotalCents * (1 - totalPercentOff / 100))
Review

P1 Negative price when stacked discounts exceed 100%

If two or more discount codes sum to more than 100%, (1 - totalPercentOff / 100) becomes negative and Math.round returns a negative cents value — effectively charging the customer. For example, two 60%-off codes produce a factor of -0.2, turning a $10 subtotal into -$2. Clamping totalPercentOff to 100 prevents this.

  let totalPercentOff = 0
  for (const discount of discounts) {
    totalPercentOff += discount.percentOff
  }
  totalPercentOff = Math.min(totalPercentOff, 100)
  return Math.round(subtotalCents * (1 - totalPercentOff / 100))
<a href="#"><img alt="P1" src="https://greptile-static-assets.s3.amazonaws.com/badges/p1.svg?v=9" align="top"></a> **Negative price when stacked discounts exceed 100%** If two or more discount codes sum to more than 100%, `(1 - totalPercentOff / 100)` becomes negative and `Math.round` returns a negative cents value — effectively charging the customer. For example, two 60%-off codes produce a factor of `-0.2`, turning a $10 subtotal into `-$2`. Clamping `totalPercentOff` to `100` prevents this. ```suggestion let totalPercentOff = 0 for (const discount of discounts) { totalPercentOff += discount.percentOff } totalPercentOff = Math.min(totalPercentOff, 100) return Math.round(subtotalCents * (1 - totalPercentOff / 100)) ```
}
Review

P2 No guard against an empty discount list

When discounts is an empty array, totalPercentOff stays 0 and the function returns subtotalCents unchanged — which is mathematically correct. However, there is also no guard against individual percentOff values that are negative (a negative discount would increase the price) or individually greater than 100. The Discount interface places no constraint on percentOff, so a caller could pass { code: "BAD", percentOff: -20 } and silently inflate the total. Consider validating each entry or documenting the expected range.

<a href="#"><img alt="P2" src="https://greptile-static-assets.s3.amazonaws.com/badges/p2.svg?v=9" align="top"></a> **No guard against an empty discount list** When `discounts` is an empty array, `totalPercentOff` stays `0` and the function returns `subtotalCents` unchanged — which is mathematically correct. However, there is also no guard against individual `percentOff` values that are negative (a negative discount would _increase_ the price) or individually greater than 100. The `Discount` interface places no constraint on `percentOff`, so a caller could pass `{ code: "BAD", percentOff: -20 }` and silently inflate the total. Consider validating each entry or documenting the expected range.