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