Synchronizing Sales from External Systems into Lightspeed Retail (X-Series)
Synchronizing sales from external systems into Lightspeed Retail (X-Series)
A common use of the Lightspeed Retail (X-Series) API is to synchronize sales from external systems, particularly for omni-channel concerns like ecommerce, or an outlet which uses a different POS system.
This tutorial will cover a set of steps for populating and creating a sale.
What you'll need
Please install Postman, or alternatively, use your favourite software development environment.
With simplicity in mind, we will use a GUI to connect to Lightspeed Retail (X-Series)'s API for this tutorial. Please feel free to use a software development platform instead, but this tutorial will use a GUI tool called Postman.
You should also know how to connect to Lightspeed Retail (X-Series)'s API. See the Quick Start tutorial.
Overview
Key concerns
Typically external systems will need to discover some key retailer information before making a sale, may need to create customer & product records, and may need to execute some fulfillment. When synchronizing entities from an external system into Lightspeed Retail (X-Series), it is important to avoid duplication of data, and to enter data in a way which will fit for your retailer. Sales have a number of data points which will need to be fulfilled.
- Some fields (user_id, tax_id, ...) will be different for each retailer: you will need to plan ahead for these.
- You should make a plan for handling tax, payment types, outlets and registers, products, customers, pricing, and users.
- Avoid duplicates: use unique identifiers and 'idempotency keys'.
- Parked sales vs Completed sales.
0. Set up Postman
Set up postman using the Quick Start tutorial. This will guide you through two different approaches to Auth. Take note of the Postman Tips about environments and variables.
- In Postman, create a 'New Collection'. Call it 'Sync a sale into Lightspeed Retail (X-Series)' or similar.
- Set up authorization on this collection, using either a Bearer Token (personal token) or OAuth flow, as explained in Quick Start.
1. Discover Dependencies
A sale has a number of dependencies, which are typically represented as UUIDs in the Sale request payload. These UUID values differ for each retailer.
1a. Fetch retailer information.
The retailer entity has a number of useful fields relating to tax, special product records, gift cards & store credit.
- Create a new Request within your collection.
GET
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/retailer
. Click Send.- From the result, you may want the
data.tax_exclusive
(this is described using javascript-style notation).
- From the result, you may want the
1b. Register ID
You will need a way to identify the register for your sale. The retailer may have/want a specific register for your sales channel. You can fetch the full list of registers. Each is linked to an outlet. A good time to agree on a register might be while pairing an addon with a retailer account.
- Create a new Request within your collection.
GET
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/registers
. Click Send.- Note the
data.[x].id
(having identified the correct register. - If you need to use the outlet to decide, use
/api/2.0/outlets
(or/api/2.0/outlets/{{outlet_id}}
for a single outlet)
- Note the
1c. User
Do you want sales to be associated with specific cashiers, with the retailer's primary user, or even the addon's user account?
- Let's assume the primary user.
- Create a new Request within your collection.
GET
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/users
. Click Send. - Note the
id
of the user whosedata.[x].is_primary_user === true
- For specific users, check the username.
- Create a new Request within your collection.
1d. Taxes
Tax values might be dictated by the external system, although it's important to recognise a few Lightspeed Retail (X-Series) configuration items. Lightspeed Retail (X-Series) retailer accounts can be either 'tax inclusive' or 'tax exclusive'.
- Taxes: tax codes, tax exclusive/inclusive?
- Pricing: promotions, pricebooks. calculate prices based on promotions, pricebooks and list prices?
- Customers
- Products
The tax amount and tax_id
should always be sent to /api/register_sales.
You can retrieve the relevant tax_id
from /api/2.0/taxes
.
Note: ALL retailers have a special tax with the name
"No Tax", which represents "No Tax".
To post a tax-free sale, please fill tax_id
field using the id
of the tax named "name": "No Tax"
, along with a value of zero: "tax": 0
.
1e. Payment types
Payment types: does the external system have matching payment types to Lightspeed Retail (X-Series)? In some cases, a retailer may have several payment types. A good time to agree on a payment type might be while pairing an addon with a retailer account.
- Create a new Request within your collection.
GET
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/payment_types
. Click Send.- NOTE: there may be several providers for a given category of payment type. If you don't know which type to use, it is best to agree directly with a retailer during setup.
- Payment types have an
id
, which is unique for that retailer, and should be used for the sale request. - The
payment_type
part of the record can be used to help identify/narrow your search for the correct payment type. Thedata.[x].payment_type
can be used to identify different types, such as"category": "cash"
or"name": "Gift cards"
.
2. Search or create a customer
You may need to verify that your customer record exists in Lightspeed Retail (X-Series). The search API allows you search by customer_code
or by email
, which is useful if your external system doesn't store the Lightspeed Retail (X-Series) customer codes.
If it doesn't exist, then create one:
- Create a new Request within your collection.
GET
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/search?type=customers&customer_code=abc
orhttps://<<domain_prefix>>.retail.lightspeed.app/api/2.0/search?type=customers&[email protected]
. Click Send. - If found, note the ID of the customer.
- If not found, create a customer.
- To create a customer, create a new Request within your collection. Method
POST
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/customers
. This will create the customer and then return the id.
3. Search or create a product
You may need to verify that your product record exists in Lightspeed Retail (X-Series) - search by sku
or handle
to uniquely identify the product. If it doesn't exist, then create one:
- Create a new Request within your collection.
GET
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/search?type=products&sku=abc
. Click Send. - If found, note the ID of the product in the response.
- If not found, create a product.
- To create a product, create a new Request within your collection. Method
POST
,https://<<domain_prefix>>.retail.lightspeed.app/api/2.0/products
. This will create the product and then return the id.
Warning: If this product you are creating is a variant of another product you will first need to read the documentation on variant products before doing this to ensure the new product is actually a variant of the parent product. The key thing to note about variants is that they have the same handle
.
4. Line Items
Line items are posted in the register_sale_products
array.
Each item has
Field | Source | Notes |
---|---|---|
register_id | /api/2.0/registers | Typically the same register as the sale itself. |
product_id | /api/2.0/search | Search or create a product. |
tax_id | /api/2.0/taxes | See 'default'. Also see /api/2.0/retailer for no_tax_group_id and tax_exclusive . |
price | /api/2.0/products/{id} | Typically, use product record for pricing. |
status | For CLOSED sales, prefer CONFIRMED . | |
price_set | Use 1 to avoid recalculating totals. | |
cost | Unit cost of the item. | |
discount | Discount value of the line item. | |
tax | /api/2.0/taxes | The unit tax value associated with this line item. |
loyalty_value | The value of loyalty that will be incurred by the customer for this line item. | |
sequence | Order number of the line item. | |
quantity | Quantity of products for the line item. |
5. Create the sale
Finally, we are ready to create the sale in Postman. To create a sale we need to POST
to a v0.9 endpoint https://{{domain_prefix}}/api/register_sales
. The description of the payload to send to this endpoint can be found here. However, not all the fields are required.
An example of a simple sales payload looks as follows:
{
"source_id": "Your-Source-ID",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"user_id": "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f",
"status": "CLOSED",
"register_sale_products": [
{
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"quantity": 1.0,
"price": 10.00,
"tax": 1.15,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
}
]
}
This contains enough information for the Lightspeed Retail (X-Series) system to be able to correctly register a sale. The source_id
is a field you can use to push the unique id for this sale from your system to Lightspeed Retail (X-Series) so that you are able to easily match sales in the two systems. It is not required.
Definitions
The sale object
Attribute | Sample Value | Req/Opt | Description |
---|---|---|---|
register_id | "02dcd191-ae2b-11e9-f336-cd2e6d8a318a" | required | Valid id of register to assign the sale to. |
user_id | "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f" | required | Valid id of a Lightspeed Retail (X-Series) user associated with the sale. |
status | "CONFIRMED" | required | For sale statuses see here. |
register_sale_products | [] | required | An array of line items - definition of attributes in a table below. When updating a sale all existing products need to be included in the payload or they will be deleted. |
The register sale product object
Attribute | Sample Value | Req/Opt | Description |
---|---|---|---|
product_id | "02dcd191-aeba-11e9-f336-cd2e6da4ef2e" | required | Lightspeed Retail (X-Series) product id . |
quantity | 1 | required | Product quantity. |
price | 10.00 | required | Unit price, tax exclusive. |
tax | 1.15 | required | Tax value. |
tax_id | "02dcd191-ae2b-11e9-f336-cd2e6d864598" | required | Tax id as retrieved from /api/2.0/taxes . |
The response to this POST
looks as follows:
{
"register_sale": {
"id": "0a6f6e36-8bba-11ea-f3d6-e713d01f1a4b",
"source": "USER",
"source_id": "Your-Source-ID",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"market_id": "1",
"customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
"customer_name": " ",
"customer": {
"id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
"name": " ",
"customer_code": "WALKIN",
"customer_group_id": "02dcd191-ae2b-11e9-f336-cd2e6d846a75",
"customer_group_ids": [],
"customer_group_name": "All Customers",
"first_name": "",
"last_name": "",
"company_name": "",
"phone": "",
"mobile": "",
"fax": "",
"email": "",
"do_not_email": "",
"twitter": "",
"website": "",
"physical_address1": "",
"physical_address2": "",
"physical_suburb": "",
"physical_city": "",
"physical_postcode": "",
"physical_state": "",
"physical_country_id": "",
"postal_address1": "",
"postal_address2": "",
"postal_suburb": "",
"postal_city": "",
"postal_postcode": "",
"postal_state": "",
"postal_country_id": "",
"updated_at": "2019-09-02 03:04:50",
"deleted_at": "",
"balance": "0",
"year_to_date": "0",
"date_of_birth": "",
"sex": "",
"custom_field_1": "",
"custom_field_2": "",
"custom_field_3": "",
"custom_field_4": "",
"note": "",
"contact": {
"company_name": "",
"phone": "",
"email": ""
}
},
"user_id": "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f",
"user_name": "Cashier1",
"sale_date": "2020-08-25 20:44:47",
"created_at": "2020-08-25 20:44:47",
"updated_at": "2020-08-25 20:44:47",
"total_price": 10,
"total_cost": 2,
"total_tax": 1.15,
"tax_name": "GST",
"note": "",
"status": "CLOSED",
"short_code": "zvgydg",
"invoice_number": "39",
"accounts_transaction_id": "",
"return_for": "",
"register_sale_products": [
{
"id": "0a6f6e36-8bba-11ea-f3d6-e713d0223fba",
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"sequence": "0",
"handle": "FreshlySqueezedJuice",
"sku": "10012",
"name": "Freshly Squeezed Juice",
"quantity": 1.0,
"price": 10,
"cost": 2,
"price_set": 0,
"discount": 0,
"loyalty_value": 0,
"tax": 1.15,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
"tax_name": "GST",
"tax_rate": 0.15,
"tax_total": 1.15,
"price_total": 10,
"display_retail_price_tax_inclusive": "1",
"status": "CONFIRMED",
"attributes": [
{
"name": "line_note",
"value": ""
}
],
"tax_components": [
{
"rate_id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
"total_tax": 1.15
}
]
}
],
"totals": {
"total_tax": 1.15,
"total_price": 10,
"total_payment": 0,
"total_to_pay": 11.15
},
"register_sale_payments": [],
"taxes": [
{
"id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
"tax": 1.15,
"name": "GST",
"rate": 0.15
}
]
}
}
As you can see a significant amount of information is returned if the sale is successful which can be used to update or validate entities like the customer and tax rate for example.
Next we will look at a sale with a complete payload.
{
"source_id": "Your-Source-ID",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
"user_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"sale_date": "2016-05-05 23:35:34",
"note": "On special",
"status": "CLOSED",
"short_code": "mlzs94",
"invoice_number": "MR-1484-NZ",
"invoice_sequence": 1484,
"accounts_transaction_id": "",
"register_sale_products": [
{
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"sequence": "0",
"quantity": 1.0,
"price": 22,
"cost": 20,
"price_set": 0,
"discount": 0,
"loyalty_value": 0,
"tax": 3.3,
"tax_id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
"status": "CONFIRMED",
"attributes": [
{
"name": "line_note",
"value": "large"
}
]
}
],
"register_sale_payments": [
{
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
"payment_date": "2016-05-05 23:35:34",
"amount": 25.3
}
]
}
Definitions
The sale object
Attribute | Sample Value | Req/Opt | Description |
---|---|---|---|
source_id | "Your-Source-ID" | optional | The id in your source system |
register_id | "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4" | required | Valid id of register to assign the sale to. |
customer_id | "06e35f89-3783-11e6-ec7e-13193f7bd2ed" | optional | Valid id of the customer associated with the sale. |
user_id | "b1ed6158-f019-11e3-a0f5-b8ca3a64f8f4" | required | Valid id of a Lightspeed Retail (X-Series) user associated with the sale. |
sale_date | "2016-05-05 23:35:34" | optional | By default current time will be assigned |
note | "" | optional | A note to be attached to the sale |
status | "CLOSED" | required | For sale statuses see here. |
short_code | "mlzs94" | optional | Used for loyalty claiming, can be overwritten but must be unique. |
invoice_number | "MR-1484-NZ" | optional | Invoice number, if omitted one will be assigned by Lightspeed Retail (X-Series). |
invoice_sequence | 1484 | optional | Numeric part of the invoice number. |
"accounts_transaction_id" | "" | optional | Xero reference invoice ID. Only editable for ONACCOUNT sales. |
register_sale_products | [] | required | An array of line items - definition of attributes in a table below. When updating a sale all existing products need to be included in the payload or they will be deleted. |
register_sale_payments | [] | optional | An array of payments - definition of attributes in a table below. |
The register sale product object
Attribute | Sample Value | Req/Opt | Description |
---|---|---|---|
product_id | "b1d87b58-f019-11e3-a0f5-b8ca3a64f8f4" | required | Lightspeed Retail (X-Series) product id . |
register_id | "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4" | optional | A line item can be added on a different register than the sale was initially created. |
sequence | 0 | optional | Order of the line item in the sale, safe to ignore. |
quantity | 1 | required | Product quantity. |
price | 22 | required | Unit price, tax exclusive. |
cost | 20 | optional | Cost to be used for margin calculations. |
price_set | 0 | optional | Boolean value describing if sale was modified manually. Setting this to 1 will prevent price recalculation in the sell screen. |
discount | 0 | optional | If the price was set manually, discount can be declared here for reporting. |
loyalty_value | 2.0 | optional | The value by which customer's loyalty balance should be increased. |
tax | 3.3 | required | Tax value. |
tax_id | "b1d192bc-f019-11e3-a0f5-b8ca3a64f8f4" | required | Tax id as retrieved from /api/2.0/taxes . |
status | "CONFIRMED" | optional | CONFIRMED is the only meaningful, non-default option. Makes it impossible to remove product from the sale. |
The register sale payment object
Attribute | Sample Value | Req/Opt | Description |
---|---|---|---|
register_id | "b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4" | optional | A payment can also be accepted in a sale different than the sale originate from. |
retailer_payment_type_id | "b1e1d70e-f019-11e3-a0f5-b8ca3a64f8f4" | required | Payment type id - that's the id of payment types retrieved from /api/2.0/payment_types . |
payment_date | "2016-05-05 23:35:34" | optional | By default current time will be assigned. |
amount | 25.3 | required | Payment amount. |
The response from this payload looks as follows:
{
"register_sale": {
"id": "0a6f6e36-8bba-11ea-f3d6-e72122b5c381",
"source": "USER",
"source_id": "Your-Source-ID",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"market_id": "1",
"customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
"customer_name": " ",
"customer": {
"id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
"name": " ",
"customer_code": "WALKIN",
"customer_group_id": "02dcd191-ae2b-11e9-f336-cd2e6d846a75",
"customer_group_ids": [],
"customer_group_name": "All Customers",
"first_name": "",
"last_name": "",
"company_name": "",
"phone": "",
"mobile": "",
"fax": "",
"email": "",
"do_not_email": "",
"twitter": "",
"website": "",
"physical_address1": "",
"physical_address2": "",
"physical_suburb": "",
"physical_city": "",
"physical_postcode": "",
"physical_state": "",
"physical_country_id": "",
"postal_address1": "",
"postal_address2": "",
"postal_suburb": "",
"postal_city": "",
"postal_postcode": "",
"postal_state": "",
"postal_country_id": "",
"updated_at": "2019-09-02 03:04:50",
"deleted_at": "",
"balance": "0",
"year_to_date": "0",
"date_of_birth": "",
"sex": "",
"custom_field_1": "",
"custom_field_2": "",
"custom_field_3": "",
"custom_field_4": "",
"note": "",
"contact": {
"company_name": "",
"phone": "",
"email": ""
}
},
"user_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a5806",
"user_name": "[email protected]",
"sale_date": "2016-05-05 23:35:34",
"created_at": "2020-08-25 22:20:09",
"updated_at": "2020-08-25 22:20:09",
"total_price": 22,
"total_cost": 2,
"total_tax": 3.3,
"tax_name": "GST",
"note": "On special",
"status": "CLOSED",
"short_code": "mlzs94",
"invoice_number": "MR-1484-NZ",
"accounts_transaction_id": "",
"return_for": "",
"register_sale_products": [
{
"id": "0a6f6e36-8bba-11ea-f3d6-e72122ba2156",
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"sequence": "0",
"handle": "FreshlySqueezedJuice",
"sku": "10012",
"name": "Freshly Squeezed Juice",
"quantity": 1.0,
"price": 22,
"cost": 2,
"price_set": 0,
"discount": 0,
"loyalty_value": 0,
"tax": 3.3,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
"tax_name": "GST",
"tax_rate": 0.15,
"tax_total": 3.3,
"price_total": 22,
"display_retail_price_tax_inclusive": "1",
"status": "CONFIRMED",
"attributes": [
{
"name": "line_note",
"value": "large"
}
],
"tax_components": [
{
"rate_id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
"total_tax": 3.3
}
]
}
],
"totals": {
"total_tax": 3.3,
"total_price": 22,
"total_payment": 25.3,
"total_to_pay": 0
},
"register_sale_payments": [
{
"id": "0a6f6e36-8bba-11ea-f3d6-e72122c0e15c",
"payment_type_id": "1",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
"name": "Cash",
"label": "Cash",
"payment_date": "2016-05-05T23:35:34Z",
"amount": 25.3
}
],
"taxes": [
{
"id": "6d897140-cd2e-11e9-9336-02dcd191ae2b",
"tax": 3.3,
"name": "GST",
"rate": 0.15
}
]
}
}
Gotchas
- An incorrect payload will not necessarily cause an error when posted to the
/api/register_sales
endpoint. Instead, an empty sale, with no products and payments will be created and the server will respond with200 OK
. - The sale should never be posted with
OPEN
status. That status is reserved for the client-side applications and should never make it to the server. - Promotions cannot currently be used when registering a sale.
Parked vs completed sales
To park a sale in the Lightspeed Retail (X-Series) system set the status
to SAVED
. For completed sales you would normally use CLOSED
. However, see below Handle Fulfillment for sales where the sale is complete but still needs to be delivered or collected.
6. Handle Fulfillment
For a tutorial on using our fulfillment statuses for omnichannel workflows, please review our tutorial.
Lightspeed Retail (X-Series) supports click and collect as well as sales that need to be dispatched to the customer. For click and collect the statuses are AWAITING_PICKUP
for when the sale is complete but not yet picked up and PICKED_UP_CLOSED
for when the customer does the actual pickup. For sales that need to be sent to the customer we have AWAITING_DISPATCH
which can be changed to DISPATCHED_CLOSED
once the sale has been sent. This leads us nicely on to our next topic of editing a sale.
7. Editing a Sale
To edit a sale, a POST request with the appropriate payload should be posted to the API 0.9
/api/register_sales
endpoint.
WARNING: The current sales endpoint (
/api/register_sales
) behaves a bit differently than other POST endpoints. Where in the majority of other endpoints it is possible to submit partial payloads, the sale has to be submitted with all the attributes of an existing sale.\nE.g. if a sale is submitted without the line items (register_sale_products) which existed on it previously, they will be deleted and replaced with whatever is posted.
WARNING: It's worth noticing that there are a few important differences in payload attribute names between API 0.9 and API 2.0. So, if you get the sale data from API 2.0 and using it compose a payload to post back to API 0.9 you will have to, for example, rename
line_items
toregister_sale_products
.
Changing the status
This is done, by changing the value of the status
attribute of the sale. As mentioned above in the warning, you need to provide a full sale payload for this request.
The 2 most common use-cases for changing the status is completing or voiding a sale. They are worth mentioning separately as they usually require a differently prepared payload.
More about sale statuses here.
Completing a sale
When this is done, there are usually 2 things that should happen in the payload:
- A payment should be added to pay off the outstanding balance of the sale. This can be done as described above.
- The
status
of the sale should be changed to one of:CLOSED
,LAYBY_CLOSED
,ONACCOUNT_CLOSED
,PICKUP_CLOSED
orDISPATCHED_CLOSED
.
So our minimal payload would become:
{
"source_id": "Your-Source-ID",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"user_id": "0a6f6e36-8bba-11ea-f3d6-b41420a1a15f",
"status": "CLOSED",
"register_sale_products": [
{
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"quantity": 1.0,
"price": 10.00,
"tax": 1.15,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
}
],
"register_sale_payments": [
{
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
"payment_date": "2016-05-05 23:35:34",
"amount": 11.15
}
]
}
Voiding a sale
This simply requires changing the status of the sale to VOIDED
.
WARNING: Once the status of a sale is set to
VOIDED
it shouldn't be changed to anything else. Doing so may result in corrupting inventory and payment data.
Adding line items
To add a new line item to the sale a new item should be added to the register_sale_products
array in the sale payload. Below is an example of the minimal set of attributes that should be included:
{
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"quantity": 1.0,
"price": 22,
"tax": 3.3,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
}
So, in the case of a parked (SAVED
) sale with no previous payments on it, the payload should look like this:
{
"id": "a604d16b-a999-a82b-11e7-2ddc0a37c22b",
"source": "USER",
"source_id": "",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"market_id": "1",
"customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
"user_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a5806",
"user_name": "[email protected]",
"sale_date": "2017-04-30 22:29:00",
"created_at": "2017-04-30 22:29:00",
"updated_at": "2017-04-30 22:29:00",
"total_price": 12,
"total_cost": 8.73,
"total_tax": 1.8,
"tax_name": "GST",
"note": "",
"status": "SAVED",
"short_code": "aidgvq",
"invoice_number": "MR-1704-NZ",
"accounts_transaction_id": "",
"return_for": "",
"register_sale_products": [{
"id": "a604d16b-a999-963a-11e7-2df456273584",
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"sequence": "0",
"handle": "tshirt",
"sku": "tshirt-white",
"name": "T-shirt",
"quantity": 1.0,
"price": 12,
"cost": 8.73,
"price_set": 0,
"discount": 0,
"loyalty_value": 1.38,
"tax": 1.8,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
"tax_name": "GST",
"tax_rate": 0.15,
"tax_total": 1.8,
"price_total": 12,
"display_retail_price_tax_inclusive": "1",
"status": "SAVED",
"attributes": [{
"name": "line_note",
"value": ""
}]
},
{
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"quantity": 1,
"price": 12,
"tax": 1.8,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598"
}
]
}
Adding payments
To add a payment to a sale the following object should be added to the register_sale_payments
array in the payload:
{
"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
"payment_date": "2016-09-19T20:28:03Z",
"amount": -230.12
}
NOTE: _The
payment_date
attribute is optional. If not supplied the current date/time will be used.
- The
retailer_payment_type_id
attribute is theid
of a valid payment type that can be retrieved from/api/payment_types
or/api/2.0/payment_types
endpoints._
In the case of a sale where some payments have been added previously, the sale payload to be posted should look like that:
{
"id": "a604d16b-a999-9212-11e7-2ae82f545149",
"source": "USER",
"source_id": "",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"market_id": "1",
"customer_id": "02dcd191-ae2b-11e9-f336-cd2e6d848f93",
"user_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a5806",
"user_name": "[email protected]",
"sale_date": "2017-04-27 04:14:33",
"created_at": "2017-04-27 04:14:39",
"updated_at": "2017-04-27 04:14:39",
"total_price": 1200,
"total_cost": 205.52,
"total_tax": 180,
"tax_name": "GST",
"note": "",
"status": "ONACCOUNT",
"short_code": "nydtpj",
"invoice_number": "MR-1701-NZ",
"accounts_transaction_id": "",
"return_for": "",
"register_sale_products": [{
"id": "a604d16b-a999-9212-11e7-2affc316bc8c",
"product_id": "02dcd191-aeba-11e9-f336-cd2e6da4ef2e",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"sequence": "0",
"handle": "MoustachePotion1",
"sku": "10012",
"name": "Moustache Potion",
"quantity": 1.0,
"price": 1200,
"cost": 205.52,
"price_set": 0,
"discount": -115,
"loyalty_value": 138,
"tax": 180,
"tax_id": "02dcd191-ae2b-11e9-f336-cd2e6d864598",
"tax_name": "GST",
"tax_rate": 0.15,
"tax_total": 180,
"price_total": 1200,
"display_retail_price_tax_inclusive": "1",
"status": "CONFIRMED",
"attributes": [{
"name": "line_note",
"value": ""
}]
}],
"register_sale_payments": [{
"id": "a604d16b-a999-a251-11e7-2afff8a8a128",
"payment_type_id": "1",
"register_id": "02dcd191-ae2b-11e9-f336-cd2e6d8a318a",
"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
"name": "Cash",
"label": "Cash",
"payment_date": "2017-04-27 04:14:13",
"amount": 200
},
{
"retailer_payment_type_id": "5569147f-f4d3-4fe1-909d-175e2c59af06",
"payment_date": "2017-04-27 04:14:13",
"amount": 400
}],
}
Creating a return
Creating returns is described in a separate article here: Sale returns.
Notes
Avoiding Duplicates
The best way of avoiding duplicates is to store the Lightspeed Retail (X-Series) sale id that is returned when you create a sale in the Lightspeed Retail (X-Series) system in the external system so that you can always send the id
for the sale when updating a sale. This avoids unnecessary lookups and reduces the risk of duplicates significantly.
if that is not possible then the search API allows you to search for sales by date_from
, date_to
, status
, invoice_number
, customer_id
, user_id
, or outlet_id
.
What Next?
Now that you know how to access Lightspeed Retail (X-Series)'s API, please explore the documentation - we recommend visiting the API Introduction document, and the sales tutorials.
Updated 8 months ago