Skip to content
PharmaFlow Knowledgebase
Esc
↑↓navigate↵open⌘Jpreview
On this page

Coupons and Discounts

Validate coupon codes at the register and manage them as an admin or branch manager.

You apply coupons by code at sale time, and you manage the coupon table from apps/server/src/features/coupons/route.ts.

Log in first

Validate calls are public, but management calls need a token. Log in once for the admin examples.

TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@demo.pharmaflow","password":"Demo@1234"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['token'])")

Validate a code before tendering

You call GET /coupons/validate/:code from the register before the customer pays, so you can show the discount or the reason it fails. The code lookup is case insensitive.

curl http://localhost:8000/api/v1/coupons/validate/DEMO10

The response carries is_valid plus reason_invalid, which is one of deactivated, not yet valid, expired, or max uses reached. DEMO10 is the seeded demo coupon and it applies 10 percent off.

Apply a coupon on the sale

You pass coupon_code on POST /sales and the sale core in apps/server/src/features/sales/creation/service.ts rechecks everything inside the transaction. A percentage coupon discounts the subtotal, a fixed coupon subtracts its value, and the discount never exceeds the subtotal.

curl -X POST http://localhost:8000/api/v1/sales \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: sale-coupon-001" \
  -d '{
    "items": [{"medicine_id":"MED-001","quantity":2,"unit_price":12.5}],
    "payment_method": "cash",
    "coupon_code": "demo10"
  }'

Each successful use writes a coupon_usage row and increments usage_count on the coupon, so a code with max_uses stops working exactly at its cap.

List coupons as an admin

You list every coupon with GET /coupons, and only the admin role may call it. Pharmacists and cashiers validate codes but never see this list.

curl http://localhost:8000/api/v1/coupons -H "Authorization: Bearer $TOKEN"

Create a coupon

You create coupons as admin or branch_manager with POST /coupons. The fields are code, discount_type (percentage or fixed), discount_value, plus optional valid_from, valid_until, and max_uses.

curl -X POST http://localhost:8000/api/v1/coupons \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "RAMADAN15",
    "discount_type": "percentage",
    "discount_value": 15,
    "description": "Seasonal counter promotion",
    "valid_from": "2026-01-01",
    "valid_until": "2026-12-31",
    "max_uses": 500
  }'

Codes are trimmed and uppercased, duplicates return 409, percentage values above 100 return 400, and non-positive values return 400.

Update or deactivate a coupon

You update with PUT /coupons/:couponId as admin or branch_manager. You can change is_active, valid_until, max_uses, and description, and setting is_active to false emits a sync DELETE for the coupon.

curl -X PUT http://localhost:8000/api/v1/coupons/COUPON_ID \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_active": false}'

Next steps

Ring the discounted sale in /sales/selling-at-the-counter, handle post-sale refunds in /sales/returns-and-credit-notes, and review shift totals in /sales/sessions-and-parked.

Last updated on October 20, 2026