Skip to main content

Discounts & Coupons

Complete guide for discount rules and coupon validation.


Overview

LosCenotes supports two types of discounts:

  1. Automatic Discounts - Applied automatically based on eligibility rules
  2. Coupon Codes - Applied manually by entering a code

Automatic Discount Rules

GET /public/discount-rules/auto-apply

Get all active automatic discount rules.

curl -X GET "https://service-gateway.loscenotes.com/public/discount-rules/auto-apply" \
-H "Content-Type: application/json"

Response:

{
"success": true,
"message": "discount_rule.list_retrieved_successfully",
"data": [
{
"id": "discount-uuid-1",
"name": "Descuento de Temporada Baja",
"description": "10% de descuento en todos los cenotes durante temporada baja",
"discountType": "percentage",
"appliesTo": "cenote",
"value": 10,
"minAmount": 10000,
"maxDiscountAmount": 50000,
"currency": "MXN",
"priority": 10,
"stackable": false,
"autoApply": true,
"excludeTransportFromDiscount": true,
"userEligibility": "all",
"isActive": true,
"promotion": {
"id": "promo-uuid",
"name": "Temporada Baja 2025",
"slug": "temporada-baja-2025",
"status": "active",
"promotionType": "seasonal",
"startDate": "2025-05-01T00:00:00.000Z",
"endDate": "2025-06-30T23:59:59.000Z"
}
},
{
"id": "discount-uuid-2",
"name": "Descuento Primera Compra",
"description": "15% de descuento en tu primera reservación",
"discountType": "percentage",
"appliesTo": "all",
"value": 15,
"minAmount": null,
"maxDiscountAmount": 100000,
"currency": "MXN",
"priority": 5,
"stackable": false,
"autoApply": true,
"excludeTransportFromDiscount": false,
"userEligibility": "first_purchase",
"isActive": true
}
]
}

Discount Types

TypeDescriptionExample
percentagePercentage off total10% off → value: 10
fixedFixed amount off$50 off → value: 5000 (cents)
quantity_basedBased on quantity purchasedBuy 3, get 1 free

User Eligibility

EligibilityDescription
allAll users
authenticatedRegistered users only
vipVIP members only
first_purchaseFirst-time buyers
local_residentLocal residents (Quintana Roo)
mexican_nationalMexican nationals

Applies To

ValueDescription
allAll items (cenotes and tours)
cenoteCenotes only
tourTours only
specificSpecific items (see promotion metadata)

Coupon Validation

POST /coupons/validate

Validate a coupon code before applying it.

curl -X POST "https://service-gateway.loscenotes.com/coupons/validate" \
-H "Content-Type: application/json" \
-d '{
"code": "VERANO2025"
}'

Request Fields:

FieldTypeRequiredDescription
codestringCoupon code to validate

Success Response:

{
"success": true,
"message": "coupons.validation_completed",
"data": {
"isValid": true,
"message": "Coupon is valid and can be applied",
"coupon": {
"id": "coupon-uuid",
"code": "VERANO2025",
"name": "Descuento Verano 2025",
"discountType": "percentage",
"discountValue": 10,
"minPurchaseAmount": 20000,
"maxDiscountAmount": 50000,
"usageLimit": 1000,
"usageCount": 245,
"validFrom": "2025-06-01T00:00:00.000Z",
"validUntil": "2025-08-31T23:59:59.000Z",
"isActive": true
},
"potentialDiscount": 10000,
"errors": []
},
"currency": {
"code": "MXN",
"symbol": "$"
}
}

Invalid Response:

{
"success": true,
"message": "coupons.validation_completed",
"data": {
"isValid": false,
"message": "Coupon has expired",
"coupon": null,
"potentialDiscount": null,
"errors": ["Coupon has expired"]
}
}

POST /coupons/check

Check if a coupon code exists and is available (alias for validate).

curl -X POST "https://service-gateway.loscenotes.com/coupons/check" \
-H "Content-Type: application/json" \
-d '{
"code": "VERANO2025"
}'

Using Coupons in Pricing

Include the coupon code when calling /pricing/calculate-complete:

{
"itemType": "cenote",
"itemId": "cenote-uuid",
"ageBreakdown": {
"adult": 2
},
"couponCode": "VERANO2025"
}

The response will show the coupon discount in the discounts section:

{
"discounts": {
"automatic": [],
"coupon": {
"code": "VERANO2025",
"type": "percentage",
"amount": 7000
}
},
"summary": {
"subtotal": 70000,
"discounts": 7000,
"total": 63000
}
}

Discount Stacking

Stackable Discounts

If stackable: true, the discount can be combined with other discounts.

Non-Stackable Discounts

If stackable: false, only the highest priority discount will be applied.

Priority Order:

  1. Higher priority value wins
  2. If same priority, automatic discounts apply before coupons

Transport Exclusion

When excludeTransportFromDiscount: true:

  • Discount applies only to: Base price + Optional services
  • Discount does NOT apply to: Transportation costs

Note: This option is only available for percentage type discounts.


Coupon Validation Errors

ErrorDescription
Coupon not foundCode doesn't exist
Coupon has expiredPast valid date
Coupon not yet validBefore start date
Coupon usage limit reachedMax uses exceeded
Coupon is inactiveManually deactivated
Minimum purchase not metBelow minimum amount

Best Practices

1. Always Validate First

Before showing discount in UI, validate the coupon:

// Step 1: Validate coupon
const validation = await fetch('/coupons/validate', {
method: 'POST',
body: JSON.stringify({ code: userInput })
});

// Step 2: Only if valid, calculate price with coupon
if (validation.data.isValid) {
const pricing = await fetch('/pricing/calculate-complete', {
method: 'POST',
body: JSON.stringify({
itemId: cenoteId,
ageBreakdown: { adult: 2 },
couponCode: userInput
})
});
}

2. Display Savings

Show users how much they're saving:

const { discounts, summary } = pricingResponse.data;
const totalSavings = discounts.discounts; // In cents
console.log(`You save: $${(totalSavings / 100).toFixed(2)} MXN`);

3. Handle Auto-Apply

Automatic discounts are applied automatically. Check the response:

const autoDiscounts = pricingResponse.data.discounts.automatic;
if (autoDiscounts.length > 0) {
autoDiscounts.forEach(d => {
console.log(`Applied: ${d.name} - Save $${d.amount / 100}`);
});
}