Support stacking multiple discount codes #2

Open
greptile-bot wants to merge 4 commits from add-discount-stacking into main
Collaborator

Sums percentage discounts and applies them as one factor.

Sums percentage discounts and applies them as one factor.
greptile-bot added 1 commit 2026-08-19 14:08:26 +00:00
greptile-bot added 1 commit 2026-08-19 14:14:32 +00:00
greptile-bot added 1 commit 2026-08-19 14:20:05 +00:00
greptile-bot reviewed 2026-08-19 14:20:08 +00:00
greptile-bot left a comment
Author
Collaborator

Your trial has ended. Reactivate Greptile to resume code reviews.

Your trial has ended. [Reactivate Greptile](https://app.staging.greptile.com/-/pull-requests) to resume code reviews.
greptile-bot added 1 commit 2026-08-19 14:25:30 +00:00
greptile-bot reviewed 2026-08-19 14:26:55 +00:00
@@ -0,0 +6,4 @@
for (const discount of discounts) {
totalPercentOff += discount.percentOff
}
return Math.round(subtotalCents * (1 - totalPercentOff / 100))
Author
Collaborator

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)) ```
@@ -0,0 +7,4 @@
totalPercentOff += discount.percentOff
}
return Math.round(subtotalCents * (1 - totalPercentOff / 100))
}
Author
Collaborator

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.
Author
Collaborator

Greptile Summary

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  ·  View in Greptile

  • 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 &nbsp;&middot;&nbsp; <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.
git fetch -u origin add-discount-stacking:add-discount-stacking
git checkout add-discount-stacking
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: abhinav/test-repo#2