VillaCoupon3
Validates coupons and computes discount amounts. It is the source of truth for what a coupon is worth; it never touches the order total. calculatecost2 calls it and applies the result.
Replaces the older coupon1/coupon2 services. It supports rules they could not: specific-SKU targeting, two-for-X with per-set caps, cross-SKU BOGO pooling, order-source restrictions, and auto-applied promotions.
Where it lives
| Value | |
|---|---|
| Account | villa-ecommerce / 017176210331 |
| Checker (dev) | villacoupon3-checker-dev |
| API Gateway | qe1hv09c68 |
| Local clone | ~/stacks/villacoupon3 |
calculatecost2 invokes it cross-account via COUPON_FUNCTION_ARN:
arn:aws:lambda:ap-southeast-1:017176210331:function:villacoupon3-checker-dev
The villa-ecommerce side grants invoke permission by resource policy. If that statement is removed, calculatecost2 falls back to the legacy checker and coupons quietly stop applying.
Response contract
{
"discounts": [{
"couponCode": "TSHIPDIS",
"discountType": "free_shipping",
"discountAmount": 0.0,
"shippingDiscount": 50,
"freeShipping": false,
"bogoDiscount": 0.0,
"two4Discount": 0.0,
"appliedToProducts": []
}],
"failedCoupons": [],
"error": {}
}discountAmount is already final — caps like maxDiscountAmount are applied before it is returned. Consumers must not re-derive it from percentageRate × subTotal.
discountTypeis not enough to know the behaviourA
free_shippingcoupon withmaxDiscountAmountset waives only part of the fee. VillaCoupon3 signals that correctly withfreeShipping: falseand a partialshippingDiscount, but a consumer that infers “free shipping” from the type alone will waive the whole fee. That exact bug shipped — see 2026-07-29-coupon-test-sheet-failures. The boolean is authoritative; the type is a category label.
Calculators
Each coupon type has a calculator in src/villacoupon3/coupon/calculator.py:
| Type | Function | Notes |
|---|---|---|
fixed | _calculate_fixed | Flat baht off the cart |
percentage | _calculate_percentage | Honours maxDiscountAmount |
bogo | _calculate_bogo | Pools units across SKUs |
two_for_x | _calculate_two_for_x | Per-set cap; sorts units by price descending |
free_shipping | _calculate_free_shipping | maxDiscountAmount turns it into a partial waiver |
shipping | _calculate_shipping_discount | Explicit partial shipping discount |
specific_sku | _calculate_specific_sku | Percentage of the matching SKUs’ total |
Note that free_shipping + maxDiscountAmount and shipping express the same intent by different routes. Both appear in production data.
Track record
As of 2026-07-29 every coupon failure investigated on dev traced to a consumer, not to VillaCoupon3. Its amounts and flags were correct in all cases in coupon-test-matrix. Verify the checker’s raw response before changing anything here:
curl -sS -X POST 'https://dev.villamarket.net/api/coupon2/couponChecker' \
-H "Authorization: Bearer $APIKEY" -H 'Content-Type: application/json' \
-d '{"couponCodeList":["TSHIPDIS"],"branchId":"1000","subTotal":3862,"deliveryFee":90,"orderSource":"WEB","productList":[]}'