villaCalculateCost4 API Reference¶
Complete documentation of the endpoint, request/response schemas, and calculation logic.
Endpoint¶
| Method | Path | Handler | Purpose |
|---|---|---|---|
| POST | /calculatecost4 |
calculatecost2Lambda.lambda_handler |
Calculate order cost |
Deployment note: The current SAM template.yaml still routes both POST /calculatecost2
and POST /calculatecost3 to the same handler. These accept the identical JSON body.
The canonical endpoint name going forward is /calculatecost4.
Request Schema -- POST /calculatecost4¶
Top-Level Fields¶
{
"productList": [
{
"cprcode": 146098,
"productName": "ice cream",
"quantity": 2
}
],
"branchId": "1023",
"voucherId": ["WNOB0OWQ"],
"couponCodeList": ["9I7Y2KEY"],
"basketId": "optional-uuid",
"basketName": "optional string",
"orderId": "172400000244",
"orderDate": 1718989200,
"ownerId": "567636a5-8794-40e0-8455-a85dea7a637d",
"requestSubstitute": false,
"specialComment": null,
"noPlasticBags": false,
"shipping": { "..." : "..." },
"cartDiscountInfo": []
}
Extra keys in the request body are silently dropped. The Order.fromDict constructor
filters to dataclass fields only.
Field Definitions¶
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
productList |
array[Product] | Yes | -- | One or more products |
voucherId |
array[string] | No | [] |
Voucher IDs to apply |
basketName |
string | No | "" |
Passed through unchanged |
basketId |
string | No | "" |
Passed through unchanged |
branchId |
string | No | "1000" |
Store branch ID; applied to all products |
orderId |
string | No | "" |
Passed through unchanged |
orderDate |
number | No | 0 |
Unix timestamp; passed through |
ownerId |
string | No | "" |
Used for voucher ownership validation |
requestSubstitute |
boolean | No | false |
Passed through unchanged |
specialComment |
string | No | null |
Passed through unchanged |
noPlasticBags |
boolean | No | false |
Passed through unchanged |
shipping |
Shipping | No | -- | Shipping and delivery configuration |
cartDiscountInfo |
array | No | [] |
Cart-level discount metadata |
couponCodeList |
array[string] | No | [] |
Coupon codes to validate and apply |
Product Object (Input)¶
Minimum required fields: cprcode, productName, quantity.
{
"cprcode": 146098,
"productName": "ice cream",
"quantity": 1,
"scheduleId": 2,
"isPreOrder": false,
"remark": ""
}
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
cprcode |
integer | Yes | -- | Product code; used as DynamoDB key |
productName |
string | Yes | -- | Display name; passed through |
quantity |
integer | Yes | -- | Units to order |
scheduleId |
integer | No | 0 |
Links product to shipping schedule slot |
isPreOrder |
boolean | No | false |
Pre-order flag; passed through |
remark |
string | No | "" |
Passed through unchanged |
Computed product fields (price, originalPrice, rowTotal, discountedRowTotal,
settlementPrice, weight, isControlledProduct) in the request are silently ignored.
Shipping Object¶
{
"shippingType": "DELIVERY",
"shippingPostcode": "10110",
"shippingLat": 13.73204799,
"shippingLon": 100.56762289,
"shippingFirstName": "tor",
"shippingLastName": "intanon",
"shippingAddress": "595 Sukhumvit Rd",
"shippingSubDistrict": "",
"shippingProvince": "Bangkok",
"shippingPhone": "061-002-0199",
"shippingEmail": "",
"shippingDate": 0,
"brcode": 1023,
"scheduleList": [ { "..." : "..." } ]
}
| Field | Type | Default | Notes |
|---|---|---|---|
scheduleList |
array[Schedule] | -- | Delivery schedule slots |
shippingType |
enum | "PICKUP" |
"DELIVERY" or "PICKUP" |
shippingPostcode |
string | "10110" |
Used for nationwide shipping rate lookup |
shippingLat |
float | 0 |
Latitude; used for Bangkok polygon check |
shippingLon |
float | 0 |
Longitude; used for Bangkok polygon check |
shippingFirstName |
string | "" |
Passed through |
shippingLastName |
string | "" |
Passed through |
shippingAddress |
string | "" |
Passed through |
shippingSubDistrict |
string | "" |
Passed through |
shippingProvince |
string | "" |
Passed through |
shippingPhone |
string | "" |
Passed through |
shippingEmail |
string | "" |
Passed through |
shippingDate |
number | 0 |
Passed through |
brcode |
integer | 1000 |
Branch code for polygon lookup |
Schedule Object¶
{
"scheduleId": 0,
"mode": "NATIONWIDE",
"dateTime": "2022-04-09T13:00:00.000Z",
"deliveryFee": 0,
"expressFee": 0,
"nationwideMode": "DRY",
"nationwideConfig": "",
"pickingStatus": "",
"date_slot": "",
"booking_hour": 0
}
| Field | Type | Values | Notes |
|---|---|---|---|
mode |
enum | REGULAR, EXPRESS, NATIONWIDE, PICKUP |
Determines fee calculator |
dateTime |
string | ISO-8601 | Passed through |
scheduleId |
integer | 0--N | Links to productList[].scheduleId |
deliveryFee |
number | -- | Overwritten by calculation |
expressFee |
number | -- | Overwritten -- 50 THB if mode=EXPRESS and first schedule |
nationwideMode |
enum | DRY, FRESH, MIX |
Affects nationwide rate lookup |
nationwideConfig |
string | -- | Set to "none" if non-nationwide |
pickingStatus |
any | -- | Passthrough |
date_slot |
any | -- | Passthrough |
booking_hour |
any | -- | Passthrough |
Response Schema¶
Success (HTTP 200)¶
The response body is the enriched order object produced by Order.toDict().
{
"productList": [ "..." ],
"shipping": { "..." : "..." },
"basketName": "",
"basketId": "",
"branchId": "1023",
"orderId": "",
"orderDate": 0,
"ownerId": "",
"requestSubstitute": false,
"specialComment": null,
"noPlasticBags": false,
"subTotal": 189.0,
"grandTotal": 441.0,
"totalExcludeControlledProducts": 441.0,
"voucherDiscount": 0.0,
"cartDiscount": 18.0,
"shippingDiscount": 0.0,
"bogoDiscount": 0.0,
"expressShippingCost": 0.0,
"totalDiscount": 18.0,
"totalWeight": 0.5,
"deliveryFee": 270.0,
"discountedDeliveryFee": 270.0,
"isFreeShipping": false,
"validVouchers": [],
"invalidVouchers": [],
"discountResult": { "..." : "..." },
"summary": { "..." : "..." }
}
Top-Level Computed Fields¶
| Field | Type | Description |
|---|---|---|
subTotal |
number | Sum of price * quantity across all products (min 0) |
grandTotal |
number | Final order total after fees and discounts (min 0) |
totalExcludeControlledProducts |
number | Grand total excluding controlled products |
voucherDiscount |
number | Total discount from applied vouchers |
cartDiscount |
number | Total cart-level discount |
shippingDiscount |
number | Discount applied to delivery fee |
bogoDiscount |
number | Buy-one-get-one discount total |
expressShippingCost |
number | Express delivery surcharge |
totalDiscount |
number | Aggregate discount amount |
totalWeight |
number | Sum of product weights |
deliveryFee |
number | Raw delivery fee before discount |
discountedDeliveryFee |
number | Delivery fee after shipping discount (min 0) |
isFreeShipping |
boolean | Whether shipping is free |
Passthrough Fields¶
Returned unchanged from the request:
basketName, basketId, branchId, orderId, orderDate, ownerId,
requestSubstitute, specialComment, noPlasticBags
Nested Objects¶
| Field | Type | Description |
|---|---|---|
productList |
array[Product] | Enriched product objects with computed prices |
shipping |
Shipping | Shipping object with calculated fees |
validVouchers |
array | Vouchers that were successfully applied |
invalidVouchers |
array | Vouchers that failed validation |
discountResult |
object | Detailed breakdown of coupon/discount results |
summary |
TotalSummary | Aggregated totals for the order |
Product Object (Output)¶
Each product in the response includes all input fields plus computed fields.
{
"cprcode": 146098,
"productName": "ice cream",
"quantity": 1,
"scheduleId": 2,
"isPreOrder": false,
"originalPrice": 189.0,
"price": 189.0,
"rowTotal": 189.0,
"discountedRowTotal": 189.0,
"settlementPrice": 189.0,
"weight": 0.5,
"isControlledProduct": false
}
| Field | Type | Description |
|---|---|---|
cprcode |
integer | Product code |
productName |
string | Display name |
quantity |
integer | Units ordered |
scheduleId |
integer | Linked schedule slot |
isPreOrder |
boolean | Pre-order flag |
originalPrice |
number | Price before any discount |
price |
number | Effective unit price |
rowTotal |
number | originalPrice * quantity |
discountedRowTotal |
number | price * quantity |
settlementPrice |
number | Settlement price from product master |
weight |
number | Product weight (kg) |
isControlledProduct |
boolean | Whether product is controlled |
Grand Total Calculation¶
grandTotal = subTotal
+ discountedDeliveryFee
+ expressShippingCost
- cartDiscount
- bogoDiscount
- voucherDiscount
(max(0) guard applied)
discountedDeliveryFee = deliveryFee - shippingDiscount (max(0) guard)
subTotal = sum(product.price * product.quantity) (max(0) guard)
deliveryFee = sum(schedule.deliveryFee for schedule in calculated scheduleList)
two4Discount and percentageDiscount are computed but not subtracted from
grandTotal. They may be folded into cartDiscount depending on the discount type.
Test Fixtures¶
Minimal and representative request bodies for testing:
| File | Description |
|---|---|
test/testData/orderInput.yaml |
Minimal order with basic product list |
test/testData/orderInput1.yaml |
Extended order with shipping and coupons |
test/testData/noShipping.yaml |
Order with no shipping object |
Error Handling¶
No structured error codes are defined. On any unhandled exception, the handler returns
HTTP 500 with the error details in the response body. There is no fallback to
resolve-coupon-master.