Loyalty 101
Loyalty 101
Handling loyalty
Lightspeed Retail (X-Series) Loyalty system allows customers to earn virtual currency to spend on subsequent purchases within the same Lightspeed Retail (X-Series) account. This article describes how to handle loyalty earning and redeeming via the API. More detailed description of how the systems works from Retailer's point of view can be found in the following article: Setting Up and Using Loyalty in Lightspeed Retail (X-Series)
Determining loyalty value
There are a few things that need to be done before an application can post sales which will earn loyalty for customers.
The first is finding out if loyalty on a given Lightspeed Retail (X-Series) account and if so, what is the default ratio at which loyalty should be calculated. After that, it is important to check if there are any product specific settings. The final step is checking if the customer associated with the sale should be earning loyalty at all.
Account config
The place where the majority of account-wide settings can be retrieved is the /api/config endpoint.
The payload from this endpoint will (amongst many others) contain the following attributes.
{
"config": {
"retailer_id": "b1c50056-f019-11e3-a0f5-b8ca3a64f8f4",
...
"enable_loyalty": true,
...
"loyalty_ratio": 0.1,
...
}
}
}
The first one, enable_loyalty shows if loyalty should be calculated for sales on this account. The second, loyalty_ratio determines the quantity of loyalty that should be incurred on a sale. It should be calculated as price * loyalty_ratio.
Product specific settings
The retailer also has the ability to modify the modify the loyalty_value on a per-product basis.
If that happens it will be reflected in the loyalty_value attribute for every price_book_entry in the payload from /api/products:
{
"products": [
{
"id": "b8ca3a65-0183-11e4-fbb5-6ba391393b6e",
...,
"price_book_entries": [
{
"id": "1cf727b7-144f-b3b7-2ba2-37af832cff18",
"product_id": "b8ca3a65-0183-11e4-fbb5-6ba391393b6e",
"price_book_id": "b1cc4593-f019-11e3-a0f5-b8ca3a64f8f4",
"price_book_name": "General Price Book (All Products)",
"customer_group_name": "All Customers",
"customer_group_id": "b1ca8902-f019-11e3-a0f5-b8ca3a64f8f4",
...
"loyalty_value": 333,
...
}
],
...
}
]
}
If his attribute has a non-zero value it should override the value calculated in the previous step.
Customer settings
It is also possible to control loyalty at the customer level, so before adding loyalty_value to a sale an application should check if the customer associated with this sale has loyalty enabled on their account.
{
"customers": [
{
"id": "7df8156a-015d-11e4-a0f5-b8ca3a64f8f4",
"name": "Tony Stark",
"customer_group_id": "b1ca8902-f019-11e3-a0f5-b8ca3a64f8f4",
"customer_group_name": "All Customers",
...
"enable_loyalty": 1,
"loyalty_balance": "0.00000",
...
}
]
}
Note:
It is also possible to modify loyalty settings at the pricebook level. This will be visible at the intersection of a given customer with the corresponding price_book_entry from the product payload, determined by customer_group_id.
Earning loyalty
In order, for a sale to cause an increase in the loyalty_balance for a customer, the loyalty_value attribute has to be added to every line item (register_sale_product) of that sale like this:
{
"id": "a604d16b-a999-bd9c-11e5-504fe9bb1492",
"register_id": "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4",
...,
"register_sale_products": [
{
"product_id": "b1db66d5-f019-11e3-a0f5-b8ca3a64f8f4",
"quantity": 1,
"price": 20,
"tax": 3,
"tax_id": "b1d192bc-f019-11e3-a0f5-b8ca3a64f8f4",
"loyalty_value": 2
}
],
...
}
Note:
loyalty_value is unit value and will be multiplied by the quantity.
Redeeming loyalty
The only way loyalty can be redeemed or the loyalty_balance decreased for a customer is by using it for a payment on a sale.
Payment types
The first thing required to add a loyalty payment to a sale is the payment_type_id. It is the id of a payment_type object which can be retrieved from the /api/payment_types endpoint. A typical payload from that endpoint will contain data like this:
{
"payment_types": [
{
"id": "b1e1d70e-f019-11e3-a0f5-b8ca3a64f8f4",
"name": "Cash",
...
},
{
"id": "b1e231c1-f019-11e3-a0f5-b8ca3a64f8f4",
"name": "Credit Card",
...
},
{
"id": "dc85058a-a683-11e5-e112-51cc5a8ffc96",
"name": "Loyalty",
"payment_type_id": "106",
...
}
]
}
For accounts with Loyalty enabled, there will always be 1 payment type with payment_type_id equal to 106. The application should use the id of that payment type to create payments on a sale.
Note:
In the example above the id to be used for sale would be dc85058a-a683-11e5-e112-51cc5a8ffc96 and NOT 106. 106 is just a code which allows for identifying the correct payment type.
Customer's loyalty balance
The next step in the redemption process should be chekcing how much loyalty customer has in their account. This can be done by checking the loyalty_balance for the particular cystomer, which can be requested from /api/customers?id={customer_id}.
Payment
The final step of redeeming loyalty is adding a payment of that type to a new or existing open sale.
Note:
Loyalty should not be incurred on the portion of the sale's total that was paid with the loyalty payment type.
Full payment on a new sale
This can be achieved by POSTing a payload like below to the /api/register_sales
endpoint:
{
"register_id": "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4",
"customer_id": "06e35f89-3783-11e6-ec7e-13193f7bd2ed",
"user_id": "b1ed6158-f019-11e3-a0f5-b8ca3a64f8f4",
"status": "CLOSED",
"register_sale_products": [{
"product_id": "b1d87b58-f019-11e3-a0f5-b8ca3a64f8f4",
"quantity": 1,
"price": 22,
"tax": 3.3,
"tax_id": "b1d192bc-f019-11e3-a0f5-b8ca3a64f8f4"
}],
"register_sale_payments": [{
"retailer_payment_type_id": "dc85058a-a683-11e5-e112-51cc5a8ffc96",
"amount": 20.3
}]
}
Partial payment on an existing sale
Let's say we have an existing on-account sale like this:
{
"id": "a604d16b-a999-80f7-11e6-a51bc47f6155",
"source": "USER",
"source_id": "",
"register_id": "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4",
"market_id": "1",
"customer_id": "06e35f89-3783-11e6-ec7e-10b1c032f05e",
"customer_name": "Johnny Bravo",
"customer": {...},
"user_id": "b1ed6158-f019-11e3-a0f5-b8ca3a64f8f4",
"user_name": "[email protected]",
"sale_date": "2016-11-07 18:57:45",
"created_at": "2016-11-07 18:58:29",
"updated_at": "2016-11-07 18:58:29",
"total_price": 60,
"total_cost": 32,
"total_tax": 9,
"tax_name": "GST",
"note": "",
"status": "ONACCOUNT",
"short_code": "xtxsqo",
"invoice_number": "MR-1603-NZ",
"return_for": "",
"register_sale_products": [{
"id": "a604d16b-a999-80f7-11e6-a51c06f2cb38",
"product_id": "b1d87b58-f019-11e3-a0f5-b8ca3a64f8f4",
"register_id": "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4",
"sequence": "0",
"handle": "tshirt",
"sku": "tshirt-white",
"name": "T-shirt",
"quantity": 5,
"price": 12,
"cost": 6.4,
"price_set": 0,
"discount": 0,
"loyalty_value": 1.38,
"tax": 1.8,
"tax_id": "b1d192bc-f019-11e3-a0f5-b8ca3a64f8f4",
"tax_name": "GST",
"tax_rate": 0.15,
"tax_total": 9,
"price_total": 60,
"display_retail_price_tax_inclusive": "1",
"status": "CONFIRMED",
"attributes": [{
"name": "line_note",
"value": ""
}]
}],
"totals": {
"total_tax": 9,
"total_price": 60,
"total_payment": 0,
"total_to_pay": 69
},
"register_sale_payments": [],
"taxes": [{
"id": "b1dfed8b-f019-11e3-a0f5-b8ca3a64f8f4",
"tax": 9,
"name": "GST",
"rate": 0.15
}]
}
The best way to add a partial payment is by taking that full object and adding the new payment details to the register_sale_payments
list.
"register_sale_payments": [{
"retailer_payment_type_id": "dc85058a-a683-11e5-e112-51cc5a8ffc96",
"amount": 20.3
}]
It is also not necessary to include read-only items like taxes
or totals
so the final payload used to add the payment could look like this:
{
"id": "a604d16b-a999-80f7-11e6-a51bc47f6155",
"source": "USER",
"source_id": "",
"register_id": "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4",
"market_id": "1",
"customer_id": "06e35f89-3783-11e6-ec7e-10b1c032f05e",
"customer_name": "Johnny Bravo",
"customer": {...},
"user_id": "b1ed6158-f019-11e3-a0f5-b8ca3a64f8f4",
"user_name": "[email protected]",
"sale_date": "2016-11-07 18:57:45",
"created_at": "2016-11-07 18:58:29",
"updated_at": "2016-11-07 18:58:29",
"total_price": 60,
"total_cost": 32,
"total_tax": 9,
"tax_name": "GST",
"note": "",
"status": "ONACCOUNT",
"short_code": "xtxsqo",
"invoice_number": "MR-1603-NZ",
"return_for": "",
"register_sale_products": [{
"id": "a604d16b-a999-80f7-11e6-a51c06f2cb38",
"product_id": "b1d87b58-f019-11e3-a0f5-b8ca3a64f8f4",
"register_id": "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4",
"sequence": "0",
"handle": "tshirt",
"sku": "tshirt-white",
"name": "T-shirt",
"quantity": 5,
"price": 12,
"cost": 6.4,
"price_set": 0,
"discount": 0,
"loyalty_value": 1.38,
"tax": 1.8,
"tax_id": "b1d192bc-f019-11e3-a0f5-b8ca3a64f8f4",
"tax_name": "GST",
"tax_rate": 0.15,
"tax_total": 9,
"price_total": 60,
"display_retail_price_tax_inclusive": "1",
"status": "CONFIRMED",
"attributes": [{
"name": "line_note",
"value": ""
}]
}],
"register_sale_payments": [{
"retailer_payment_type_id": "dc85058a-a683-11e5-e112-51cc5a8ffc96",
"amount": 20.00
}]
}
Note:
If the partial payment you are adding is going to bring the total paid amount to the full sale total, you should also change the status of the sale to an appropriate "closed" status.
Below are pending statuses and their respective closed variations:
SAVED -> CLOSED
ONACCOUNT -> ONACCOUNT_CLOSED
LAYBY -> LAYBY_CLOSED
Updated 7 months ago