Usage Guide¶
Calling the API¶
villaCalculateCost4 accepts POST requests with a JSON body and returns an enriched order with complete financial breakdowns.
Endpoint:
Minimal request:
curl -X POST https://api.villaecommerce.com/calculatecost4 \
-H "Content-Type: application/json" \
-d '{
"productList": [
{"cprcode": 57822, "productName": "Olive Oil", "quantity": 1}
],
"branchId": "1049"
}'
Minimal response structure:
{
"productList": [
{
"cprcode": 57822,
"productName": "Olive Oil",
"quantity": 1,
"scheduleId": 0,
"isPreOrder": false,
"originalPrice": 299.0,
"price": 299.0,
"rowTotal": 299.0,
"discountedRowTotal": 299.0,
"settlementPrice": 299.0,
"weight": 0.75,
"isControlledProduct": false
}
],
"branchId": "1049",
"basketName": "",
"basketId": "",
"orderId": "",
"orderDate": 0,
"ownerId": "",
"requestSubstitute": false,
"specialComment": null,
"noPlasticBags": false,
"couponCodeList": [],
"voucherId": [],
"shipping": null,
"subTotal": 299.0,
"grandTotal": 299.0,
"totalWeight": 0.75,
"deliveryFee": 0,
"discountedDeliveryFee": 0,
"expressShippingCost": 0,
"shippingFee": 0,
"shippingPrice": 0,
"cartDiscount": 0,
"bogoDiscount": 0,
"two4Discount": 0,
"percentageDiscount": 0,
"shippingDiscount": 0,
"voucherDiscount": 0,
"totalDiscount": 0,
"totalExcludeControlledProducts": 299.0,
"cartDiscountInfo": [],
"validVouchers": [],
"invalidVouchers": [],
"discountResult": {},
"summary": {
"subTotal": 299.0,
"grandTotal": 299.0,
"voucherDiscount": 0,
"cartDiscount": 0,
"bogoDiscount": 0,
"two4Discount": 0,
"percentageDiscount": 0,
"shippingDiscount": 0,
"totalExcludeControlledProducts": 299.0,
"expressShippingCost": 0,
"deliveryFee": 0,
"discountedDeliveryFee": 0,
"totalWeight": 0.75,
"discountResult": {}
}
}
Request Body¶
Required Fields¶
| Field | Type | Description |
|---|---|---|
productList |
array |
List of product line items. Must contain at least one product. |
Each product in productList requires:
| Field | Type | Required | Description |
|---|---|---|---|
cprcode |
int |
Yes | Product code used for DynamoDB price lookup |
productName |
str |
Yes | Display name of the product |
quantity |
int |
Yes | Number of units ordered |
Optional Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
branchId |
str |
"1000" |
Branch code. Propagated to every product for price lookup. |
orderId |
str |
"" |
Order identifier (passthrough) |
orderDate |
float |
0 |
Unix timestamp (passthrough) |
ownerId |
str |
"" |
Owner identifier for voucher ownership validation |
basketName |
str |
"" |
Basket name (passthrough) |
basketId |
str |
"" |
Basket identifier (passthrough) |
requestSubstitute |
bool |
false |
Substitution flag (passthrough) |
specialComment |
str |
null |
Special instructions (passthrough) |
noPlasticBags |
bool |
false |
Plastic bag preference (passthrough) |
couponCodeList |
array[str] |
[] |
Coupon codes to validate and apply |
voucherId |
array[str] |
[] |
Voucher IDs to validate and apply |
shipping |
object |
null |
Shipping/delivery configuration (see below) |
Optional Product Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
scheduleId |
int |
0 |
Links product to a delivery schedule slot |
isPreOrder |
bool |
false |
Pre-order flag (passthrough) |
remark |
str |
"" |
Product-level remark (passthrough) |
Shipping Object¶
| Field | Type | Default | Description |
|---|---|---|---|
shippingType |
str |
"PICKUP" |
"DELIVERY" or "PICKUP" |
scheduleList |
array |
[] |
List of delivery schedule slots |
shippingPostcode |
str |
"10110" |
Postcode for nationwide rate lookup |
shippingLat |
float |
0 |
Latitude for Bangkok polygon check |
shippingLon |
float |
0 |
Longitude for Bangkok polygon check |
shippingFirstName |
str |
"" |
Recipient first name (passthrough) |
shippingLastName |
str |
"" |
Recipient last name (passthrough) |
shippingAddress |
str |
"" |
Delivery address (passthrough) |
shippingSubDistrict |
str |
"" |
Sub-district (passthrough) |
shippingProvince |
str |
"" |
Province (passthrough) |
shippingPhone |
str |
"" |
Contact phone (passthrough) |
shippingEmail |
str |
"" |
Contact email (passthrough) |
shippingDate |
float |
0 |
Delivery date as Unix timestamp (passthrough) |
brcode |
int |
1000 |
Branch code for polygon lookup |
Schedule Object (within scheduleList)¶
| Field | Type | Default | Description |
|---|---|---|---|
mode |
str |
"NATIONWIDE" |
Shipping mode: "REGULAR", "EXPRESS", "NATIONWIDE", "PICKUP" |
dateTime |
str |
-- | ISO-8601 timestamp |
scheduleId |
int |
0 |
Links to products via Product.scheduleId |
pickingStatus |
str |
-- | Picking status (passthrough) |
date_slot |
str |
-- | Date slot (passthrough) |
booking_hour |
int |
0 |
Booking hour (passthrough) |
nationwideConfig |
str |
-- | Set to "none" for non-nationwide modes |
nationwideMode |
str |
"DRY" |
Nationwide mode: "DRY", "FRESH", "MIX" |
Response Body¶
The response is a flat JSON object with 28 top-level keys plus nested structures.
Scalar Fields¶
| Field | Type | Description |
|---|---|---|
branchId |
str |
Branch code from the request |
basketName |
str |
Passthrough from request |
basketId |
str |
Passthrough from request |
orderId |
str |
Passthrough from request |
orderDate |
float |
Passthrough from request |
ownerId |
str |
Passthrough from request |
requestSubstitute |
bool |
Passthrough from request |
specialComment |
str |
Passthrough from request |
noPlasticBags |
bool |
Passthrough from request |
subTotal |
float |
Sum of all product rowTotal values |
grandTotal |
float |
Final order total after all adjustments |
totalWeight |
float |
Sum of all product weights (weight * quantity) |
deliveryFee |
float |
Total delivery fee before discounts |
discountedDeliveryFee |
float |
max(deliveryFee - shippingDiscount, 0) |
expressShippingCost |
float |
Express surcharge (50 THB if EXPRESS mode, else 0) |
shippingFee |
float |
Same as deliveryFee |
shippingPrice |
float |
Same as deliveryFee |
cartDiscount |
float |
Sum of flat cart-level coupon discounts |
bogoDiscount |
float |
Sum of buy-one-get-one discounts |
two4Discount |
float |
Sum of two-for discounts |
percentageDiscount |
float |
Maximum percentage discount multiplied by subTotal |
shippingDiscount |
float |
Total shipping discount from coupons |
voucherDiscount |
float |
Sum of valid voucher values |
totalDiscount |
float |
Sum of cartDiscount + bogoDiscount + two4Discount + voucherDiscount + percentageDiscount |
totalExcludeControlledProducts |
float |
grandTotal minus controlled product rowTotals |
Grand Total Formula¶
grandTotal = max(
subTotal
+ discountedDeliveryFee
+ expressShippingCost
- cartDiscount
- bogoDiscount
- voucherDiscount,
0
)
Nested Structures¶
productList (array)¶
Each product in the response contains enriched data:
| Field | Type | Description |
|---|---|---|
cprcode |
int |
Product code |
productName |
str |
Display name |
quantity |
int |
Units ordered |
scheduleId |
int |
Linked schedule slot |
isPreOrder |
bool |
Pre-order flag |
originalPrice |
float |
Original/list price from price database |
price |
float |
Current selling price from price database |
rowTotal |
float |
max(price * quantity, 0) |
discountedRowTotal |
float |
max(rowTotal - discountFromCoupon, 0) |
settlementPrice |
float |
Same as rowTotal |
weight |
float |
Product weight in kg (default 0.25 if not found) |
isControlledProduct |
bool |
Whether product is on the controlled list |
shipping (object or null)¶
Contains the shipping configuration with computed delivery fees. Each schedule in scheduleList includes a deliveryFee and expressFee set by the calculator.
couponCodeList (array)¶
Passthrough of the coupon codes from the request.
voucherId (array)¶
Passthrough of the voucher IDs from the request.
validVouchers (array)¶
List of voucher objects that passed all validity checks (active, unused, owner matches).
invalidVouchers (array)¶
List of voucher objects that failed one or more validity checks.
discountResult (object)¶
Full result from the coupon service. Contains:
| Field | Type | Description |
|---|---|---|
discounts |
array |
List of applied discount objects |
failedCoupons |
array[str] |
Coupon codes that failed validation |
error |
object |
Error details (empty dict on success) |
Each discount object in discounts:
| Field | Type | Description |
|---|---|---|
coupon |
str |
The coupon code |
discount |
float |
Flat cart-level discount amount |
bogoDiscount |
float |
Buy-one-get-one discount amount |
two4Discount |
float |
Two-for discount amount |
percentageDiscount |
float |
Percentage discount multiplier |
freeshipping |
bool |
Whether this coupon grants free shipping |
shippingDiscount |
float |
Shipping discount amount |
cartDiscountInfo (array)¶
Coupon application details. May contain JSON-encoded strings with per-coupon breakdown data.
summary (object)¶
Aggregated financial totals:
| Field | Type | Description |
|---|---|---|
subTotal |
float |
Sum of all product rowTotals |
grandTotal |
float |
Final order total |
voucherDiscount |
float |
Total voucher discount |
cartDiscount |
float |
Total flat coupon discount |
bogoDiscount |
float |
Total BOGO discount |
two4Discount |
float |
Total two-for discount |
percentageDiscount |
float |
Total percentage discount |
shippingDiscount |
float |
Total shipping discount |
totalExcludeControlledProducts |
float |
Grand total minus controlled products |
expressShippingCost |
float |
Total express surcharges |
deliveryFee |
float |
Total delivery fee before discounts |
discountedDeliveryFee |
float |
Delivery fee after shipping discount |
totalWeight |
float |
Total order weight |
discountResult |
object |
Serialized DiscountResult |
Example: Simple Order¶
A minimal order with a single product and no shipping.
curl -X POST https://api.villaecommerce.com/calculatecost4 \
-H "Content-Type: application/json" \
-d '{
"productList": [
{
"cprcode": 57822,
"productName": "Olive Oil",
"quantity": 1
}
],
"branchId": "1049"
}'
The response will include the product enriched with its current selling price, original price, weight, and computed totals. grandTotal equals subTotal since there is no shipping, coupons, or vouchers.
Example: With Shipping¶
A delivery order with REGULAR shipping to a Bangkok address.
curl -X POST https://api.villaecommerce.com/calculatecost4 \
-H "Content-Type: application/json" \
-d '{
"productList": [
{
"cprcode": 146098,
"productName": "Ice Cream",
"quantity": 2,
"scheduleId": 1
}
],
"branchId": "1023",
"shipping": {
"shippingType": "DELIVERY",
"shippingLat": 13.73204799,
"shippingLon": 100.56762289,
"brcode": 1023,
"scheduleList": [
{
"scheduleId": 1,
"mode": "REGULAR",
"dateTime": "2024-04-13T10:00:00.000Z",
"pickingStatus": "",
"date_slot": "",
"nationwideConfig": "none",
"nationwideMode": "DRY"
}
]
}
}'
The response will include deliveryFee set by the Bangkok polygon API, shippingFee and shippingPrice matching the delivery fee, and grandTotal equal to subTotal + deliveryFee.
Example: With Coupons¶
An order with coupon codes applied.
curl -X POST https://api.villaecommerce.com/calculatecost4 \
-H "Content-Type: application/json" \
-d '{
"productList": [
{
"cprcode": 57822,
"productName": "Olive Oil",
"quantity": 3
}
],
"branchId": "1049",
"couponCodeList": ["SAVE20"],
"ownerId": "user-12345"
}'
The response will include the discountResult object with per-coupon discount breakdowns. If the coupon is valid, cartDiscount, bogoDiscount, shippingDiscount, or other discount fields will be populated. grandTotal will reflect the applied discounts. Invalid coupons appear in discountResult.failedCoupons.
Example: Pickup Order¶
An in-store pickup order with zero shipping cost.
curl -X POST https://api.villaecommerce.com/calculatecost4 \
-H "Content-Type: application/json" \
-d '{
"productList": [
{
"cprcode": 57822,
"productName": "Olive Oil",
"quantity": 1
},
{
"cprcode": 146098,
"productName": "Ice Cream",
"quantity": 2
}
],
"branchId": "1049",
"shipping": {
"shippingType": "PICKUP"
}
}'
The response will have deliveryFee, shippingFee, shippingPrice, and expressShippingCost all equal to 0. grandTotal equals subTotal.
Error Handling¶
The API currently returns errors as HTTP 500 responses with an error field in the JSON body.
There are no structured error codes in the current version. All error conditions produce a 500 status with a human-readable error message string.
Common error scenarios:
| Scenario | Error Message Pattern |
|---|---|
| Product price not found in database | "No price for {cprcode}/{brcode}" |
| Branch not in delivery polygon | "Branch {brcode} not in polygon" or "Branch {brcode} is not available" |
| Nationwide Lambda invocation failure | Lambda error details in message |
| Coupon service failure | Lambda error details in message |
| Voucher not found | "Voucher {id} not found" |
| Invalid JSON body | "Failed to parse request body" |
Planned improvement: Future versions will add structured error codes (e.g., BRANCH_NOT_AVAILABLE, PRODUCT_PRICE_NOT_FOUND, VALIDATION_ERROR) with HTTP 400/422 status codes for client errors and 502 for downstream service failures.
Backward Compatibility¶
The /calculatecost2 and /calculatecost3 routes remain operational and produce identical behavior to their original implementations. Existing clients using those endpoints do not need to migrate.
All three endpoints accept the same request format with camelCase field names:
| Endpoint | Status |
|---|---|
/calculatecost2 |
Active, original implementation |
/calculatecost3 |
Active, original implementation |
/calculatecost4 |
Active, new implementation (this project) |
The /calculatecost4 endpoint accepts the same camelCase input format as the legacy endpoints and returns responses in the same structure. This was verified through 27 integration tests comparing field-by-field output between the new and legacy endpoints using 10 real paid production orders.
Using the Test UI¶
An interactive browser-based testing interface is available at https://test.villaecommerce.com.
The Test UI provides:
- A JSON editor for constructing request payloads
- Pre-built example orders for common scenarios
- One-click submission to the
/calculatecost4endpoint - Formatted response display with syntax highlighting
- Side-by-side comparison with legacy endpoint responses
Use the Test UI to experiment with different order configurations, shipping modes, coupon codes, and voucher IDs without writing any code or using the command line.