Skip to main content

Returns & Cancellations

When a customer returns products, the platform automatically reverses the points earned and the cashback spent on those items. This page covers the full return flow with ticket examples, cancellation, product exchanges, edge cases, and rounding.

Original purchase​

Consider a customer with 459 available points who makes a purchase and redeems all available points as cashback:

POST /carts
{
"id": 1407113012324022967,
"currency": "EUR",
"total": 91.8,
"location_id": 12112221111,
"customer_id": 99000000000020,
"creation_date": "2024-11-12T13:05:33Z",
"lines": [
{
"order": 1,
"total": 16.8,
"quantity": 1,
"product_id": 77010380,
"original_total": 33.6
},
{
"order": 2,
"total": 75.0,
"quantity": 2,
"product_id": 2,
"original_total": 150.0
}
],
"cashback_score_desired": -1
}

Response (excerpt):

{
"id": 1407113012324022967,
"score": 184,
"score_as_money": 1.84,
"score_used": -459,
"score_used_as_money": -91.8,
"score_returned_to_customer": 0,
"score_returned_from_customer": 0,
"cashbacks": [
{ "line_order": 1, "score": 84, "discount": 16.8 },
{ "line_order": 2, "score": 375, "discount": 75.0 }
],
"scores": [
{ "score": 34, "line_order": 1, "loyalty_score_id": 555252320,
"available_from": "2024-11-13", "available_to": "2026-03-28" },
{ "score": 150, "line_order": 2, "loyalty_score_id": 555252320,
"available_from": "2024-11-13", "available_to": "2026-03-28" }
]
}

Summary of this purchase:

Line 1Line 2Total
Points earned34150184
Cashback spent (pts)84375459
Cashback spent (€)€16.80€75.00€91.80

Partial return​

The customer wants to return line 1 (1 unit) and 1 unit from line 2 (which had 2 units). A new ticket is sent referencing the original:

POST /carts
{
"id": 1407113012324022968,
"currency": "EUR",
"total": -54.3,
"location_id": 12112221111,
"customer_id": 99000000000020,
"creation_date": "2024-11-12T17:05:33Z",
"lines": [
{
"order": 1,
"total": -16.8,
"quantity": -1,
"product_id": 77010380,
"original_total": -16.8
},
{
"order": 2,
"total": -37.5,
"quantity": -1,
"product_id": 2,
"original_total": 150.0
}
],
"returns": [
{
"ticket_id": 1407113012324022967,
"quantity": 1,
"line_order": 1,
"from_line_order": 1
},
{
"ticket_id": 1407113012324022967,
"quantity": 1,
"line_order": 2,
"from_line_order": 2
}
]
}

returns[] field reference:

FieldDescription
ticket_idID of the original purchase ticket
quantityNumber of units being returned (positive integer)
line_orderLine number in the original ticket
from_line_orderLine number in the current return ticket
caution

Cart lines in a return must have negative total and quantity. If a wrong ticket_id is sent in returns[], the API returns an error.

What the engine reverses per line:

Line 1Line 2 (1 of 2 units)
Points subtracted (earned β†’ taken back)34 pts75 pts (150 Γ· 2)
Cashback returned to customer (spent β†’ given back)84 pts188 pts (375 Γ· 2)

Response fields for score movements:

FieldMeaning
score_returned_from_customerPoints deducted from customer because they had been earned on the returned items
score_returned_to_customerPoints restored to customer because they had been spent as cashback on the returned items

Full ticket cancellation​

To cancel an entire purchase in a single atomic operation, use a dedicated endpoint β€” do not send a return cart with all lines negative:

POST /carts/cancellation
{
"cart_to_cancel_id": 1407113012324022967
}

Alternatively, identify the original cart by code:

POST /carts/cancellation
{
"cart_to_cancel_code": "POS-REF-00123"
}

What the engine reverses atomically:

Effect reversedDescription
Points earnedAll accrued score is deducted from the customer's balance
Cashback spentAll points redeemed as cashback are restored to the customer's wallet
Discounts appliedAll promotional line modifications are rolled back
Vouchers / coupons usedAll voucher redemptions tied to the original ticket are reversed β€” the voucher returns to available status and can be re-used
caution

A cart can only be cancelled once. If cart_to_cancel_id refers to a cart already cancelled or returned, the API returns an error.


Product exchange (no score impact)​

When a customer returns a product and immediately receives a replacement in the same transaction (different size, colour, etc.) with no net monetary change, the operation uses the same cancellation endpoint with an additional new_cart_id field:

POST /carts/cancellation
{
"cart_to_cancel_id": 1407113012324022967,
"new_cart_id": 1407113012324022999
}

The engine cancels the original cart (reversing its loyalty effects) and links the replacement cart as the authoritative new purchase. Points and cashback are recalculated on the new cart only β€” the exchange is a single atomic swap from the loyalty program's perspective.

When using codes instead of IDs:

POST /carts/cancellation
{
"cart_to_cancel_code": "POS-REF-00123",
"new_cart_code": "POS-REF-00124"
}

Return edge cases​

Cashback spent on the returned purchase​

When a customer used cashback on the original purchase and then returns items, the platform restores the spent points to the customer's score_available:

  • Restored balance carries the same expiry dates as the original balance that was used.
  • If any portion of the original balance had already expired between the purchase date and the return date, only the non-expired portion is restored. Expired points are not recovered.

Earned points already spent before the return​

When a customer earns points on purchase A, spends them on purchase B, and then returns purchase A:

  1. The engine must deduct the points originally earned on purchase A.
  2. Since those points are already spent, the customer's balance goes negative.
  3. Compensation: any future points earned are first applied to offset the negative balance before they become spendable.

Example:

EventPoints balanceAvailable
Buy Product A β†’ earn 200 pts200200
Use 200 pts as cashback on purchase B00
Return Product A β†’ subtract 200 ptsβˆ’200βˆ’200
Next purchase β†’ earn 300 pts100100

The 300 newly earned points first compensate the βˆ’200 deficit, leaving 100 spendable.

Direct value campaigns on partial return​

The reversal behaviour depends on the campaign's distribution type:

Campaign effectPartial return behaviour
direct_value with distribution: "total"Points are not reversed on partial return β€” only reversed on full cancellation (POST /carts/cancellation)
Any effect with distribution: "individual_items"Points reversed proportionally to the returned lines
ratio or percentagePoints reversed proportionally to the returned amount

Rounding​

Points awarded at earn time are rounded to the nearest integer β€” see Earning rules β€” Rounding for the full explanation.

Partial return amounts​

When calculating the amount to reverse on a partial return, the engine uses banker's rounding (round-half-to-even) to avoid systematic bias across many small returns:

return_proportion = returned_quantity / original_quantity
reversed_value = original_value Γ— (-return_proportion) [RoundBank to ValuePrecision]

Example: original effect value = €37.50 on 2 units, returning 1 unit:

return_proportion = 1 / 2 = 0.5
reversed_value = 37.50 Γ— -0.5 = -18.75

No rounding difference here, but for amounts like €0.005 banker's rounding ensures consistency.