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))
```
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.
This PR adds src/discounts.ts with a new applyDiscounts function that sums multiple percentOff values from an array of Discount objects and applies the combined reduction to a subtotal in cents.
The function correctly computes a single-pass, additive discount factor and rounds the result to whole cents, consistent with the existing applyDiscount implementation in cart.ts.
Stacked discounts can sum above 100%, making the factor negative and producing a negative price; the total should be clamped to 100 before use.
The Discount interface does not constrain percentOff to [0, 100], so individual entries with negative or out-of-range values are silently accepted and will produce incorrect output.
Confidence Score: 3/5 ·
The change introduces a real pricing bug: stacked discounts summing past 100% produce a negative price, which would need to be caught before this reaches production.
The core logic is missing a clamp on totalPercentOff, so any combination of discounts that exceeds 100% returns a negative cents value. This is straightforward to fix with one line, but it's a functional defect on the primary code path of the new feature.
src/discounts.ts — the missing clamp and the unvalidated per-discount percentOff values both live here.
Important Files Changed
Filename
Overview
src/discounts.ts
New file implementing multi-discount stacking; missing a clamp on totalPercentOff, which can produce negative prices when discounts sum past 100%.
Sequence Diagram
sequenceDiagram
participant Caller
participant applyDiscounts
participant Math
Caller->>applyDiscounts: applyDiscounts(subtotalCents, discounts[])
loop for each Discount
applyDiscounts->>applyDiscounts: "totalPercentOff += discount.percentOff"
end
Note over applyDiscounts: ⚠️ No clamp — totalPercentOff can exceed 100
applyDiscounts->>Math: "subtotalCents * (1 - totalPercentOff / 100)"
Math-->>applyDiscounts: result (may be negative!)
applyDiscounts->>Math: Math.round(result)
Math-->>applyDiscounts: rounded cents
applyDiscounts-->>Caller: finalPriceCents
Reviews (1): Last reviewed commit: "Trigger review with entitlement granted"
<h3>Greptile Summary</h3>
This PR adds `src/discounts.ts` with a new `applyDiscounts` function that sums multiple `percentOff` values from an array of `Discount` objects and applies the combined reduction to a subtotal in cents.
- The function correctly computes a single-pass, additive discount factor and rounds the result to whole cents, consistent with the existing `applyDiscount` implementation in `cart.ts`.
- Stacked discounts can sum above 100%, making the factor negative and producing a negative price; the total should be clamped to 100 before use.
- The `Discount` interface does not constrain `percentOff` to `[0, 100]`, so individual entries with negative or out-of-range values are silently accepted and will produce incorrect output.
<h3>Confidence Score: 3/5 · <a href="https://app.staging.greptile.com/abhinav-testing/-/pull-requests/abhinav/test-repo/2"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptileDark.svg?v=1"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=1"><img alt="View in Greptile" src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewInGreptile.svg?v=1" align="absmiddle"></picture></a></h3>
- The change introduces a real pricing bug: stacked discounts summing past 100% produce a negative price, which would need to be caught before this reaches production.
- The core logic is missing a clamp on `totalPercentOff`, so any combination of discounts that exceeds 100% returns a negative cents value. This is straightforward to fix with one line, but it's a functional defect on the primary code path of the new feature.
- src/discounts.ts — the missing clamp and the unvalidated per-discount percentOff values both live here.
<details><summary><h3>Important Files Changed</h3></summary>
| Filename | Overview |
|----------|----------|
| src/discounts.ts | New file implementing multi-discount stacking; missing a clamp on totalPercentOff, which can produce negative prices when discounts sum past 100%. |
</details>
<details open><summary><h3>Sequence Diagram</h3></summary>
```mermaid
sequenceDiagram
participant Caller
participant applyDiscounts
participant Math
Caller->>applyDiscounts: applyDiscounts(subtotalCents, discounts[])
loop for each Discount
applyDiscounts->>applyDiscounts: "totalPercentOff += discount.percentOff"
end
Note over applyDiscounts: ⚠️ No clamp — totalPercentOff can exceed 100
applyDiscounts->>Math: "subtotalCents * (1 - totalPercentOff / 100)"
Math-->>applyDiscounts: result (may be negative!)
applyDiscounts->>Math: Math.round(result)
Math-->>applyDiscounts: rounded cents
applyDiscounts-->>Caller: finalPriceCents
```
</details>
<!-- greptile_other_comments_section -->
<sub>Reviews (1): Last reviewed commit: "Trigger review with entitlement granted"</sub>
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Sums percentage discounts and applies them as one factor.
Your trial has ended. Reactivate Greptile to resume code reviews.
@@ -0,0 +6,4 @@for (const discount of discounts) {totalPercentOff += discount.percentOff}return Math.round(subtotalCents * (1 - totalPercentOff / 100))If two or more discount codes sum to more than 100%,
(1 - totalPercentOff / 100)becomes negative andMath.roundreturns 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. ClampingtotalPercentOffto100prevents this.@@ -0,0 +7,4 @@totalPercentOff += discount.percentOff}return Math.round(subtotalCents * (1 - totalPercentOff / 100))}When
discountsis an empty array,totalPercentOffstays0and the function returnssubtotalCentsunchanged — which is mathematically correct. However, there is also no guard against individualpercentOffvalues that are negative (a negative discount would increase the price) or individually greater than 100. TheDiscountinterface places no constraint onpercentOff, so a caller could pass{ code: "BAD", percentOff: -20 }and silently inflate the total. Consider validating each entry or documenting the expected range.Greptile Summary
This PR adds
src/discounts.tswith a newapplyDiscountsfunction that sums multiplepercentOffvalues from an array ofDiscountobjects and applies the combined reduction to a subtotal in cents.applyDiscountimplementation incart.ts.Discountinterface does not constrainpercentOffto[0, 100], so individual entries with negative or out-of-range values are silently accepted and will produce incorrect output.Confidence Score: 3/5 ·
totalPercentOff, so any combination of discounts that exceeds 100% returns a negative cents value. This is straightforward to fix with one line, but it's a functional defect on the primary code path of the new feature.Important Files Changed
Sequence Diagram
Reviews (1): Last reviewed commit: "Trigger review with entitlement granted"
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.