Repository navigation
feat: discount coupons with usage limits, expiry and minimum cart value - #3
bahriy-dev wants to merge 1 commit into
Conversation
Adds percent and fixed-amount coupons. Admins create, list and deactivate them; customers apply one coupon per cart. Usage is counted inside the checkout transaction and returned when an order is cancelled. VAT and the free-shipping threshold are calculated on the discounted amount. Orders store the discount and coupon code (migration 2). Adds 9 tests. Closes #2 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYiLwGkrgfn7ftZh1WDFpq
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fef3d10da9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const coupon = findCoupon(db, row.code); | ||
| return coupon && coupon.isActive ? coupon : null; |
There was a problem hiding this comment.
Exclude expired and exhausted coupons from cart totals
If a coupon expires after being applied, or another customer consumes its last permitted use, appliedCoupon still returns it because only isActive is checked. The cart view passes this result directly to calculateTotals, so GET /cart continues advertising a discount and total that checkout rejects with a 409. Check expiry and remaining uses when calculating the cart view, and expose the unavailable coupon without presenting its discount as usable.
Useful? React with 👍 / 👎.
| const discount = coupon ? couponDiscount(coupon, subtotal) : 0; | ||
| const net = subtotal - discount; | ||
| // A discount lowers the taxable amount, so the VAT inside it shrinks proportionally. | ||
| const vatTotal = subtotal === 0 ? 0 : Math.round((grossVat * net) / subtotal); |
There was a problem hiding this comment.
Calculate discounted VAT before rounding
When the original VAT requires rounding, scaling the already-rounded grossVat introduces a second rounding error instead of recalculating VAT on the discounted amount. For example, a single item priced at 3 kuruş with 20% VAT and a fixed 1-kuruş discount produces vatTotal: 1, although vatIncluded(2, 20) is 0. This incorrect value is persisted on the order. Allocate the discount to taxable lines and calculate VAT from their discounted amounts before rounding, rather than scaling the rounded original VAT.
Useful? React with 👍 / 👎.
Stores run campaigns with codes like
HOSGELDIN10; the API had no way to model them.What changed
GET /coupons,POST /coupons,DELETE /coupons/:code(deactivate, past orders keep their data)PUT /cart/coupon,DELETE /cart/coupon; the cart shows the applied coupon anddiscountUPDATE … WHERE used_count < max_uses), so it cannot be over-usedcoupons,cart_coupons,orders.discount,orders.coupon_codeCloses #2