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).

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)

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 whose data.[x].is_primary_user === true
    • For specific users, check the username.

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. The data.[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 or https://<<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

FieldSourceNotes
register_id/api/2.0/registersTypically the same register as the sale itself.
product_id/api/2.0/searchSearch or create a product.
tax_id/api/2.0/taxesSee '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.
statusFor CLOSED sales, prefer CONFIRMED.
price_setUse 1 to avoid recalculating totals.
costUnit cost of the item.
discountDiscount value of the line item.
tax/api/2.0/taxesThe unit tax value associated with this line item.
loyalty_valueThe value of loyalty that will be incurred by the customer for this line item.
sequenceOrder number of the line item.
quantityQuantity 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

AttributeSample ValueReq/OptDescription
register_id"02dcd191-ae2b-11e9-f336-cd2e6d8a318a"requiredValid id of register to assign the sale to.
user_id"0a6f6e36-8bba-11ea-f3d6-b41420a1a15f"requiredValid id of a Lightspeed Retail (X-Series) user associated with the sale.
status"CONFIRMED"requiredFor sale statuses see here.
register_sale_products[]requiredAn 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

AttributeSample ValueReq/OptDescription
product_id"02dcd191-aeba-11e9-f336-cd2e6da4ef2e"requiredLightspeed Retail (X-Series) product id.
quantity1requiredProduct quantity.
price10.00requiredUnit price, tax exclusive.
tax1.15requiredTax value.
tax_id"02dcd191-ae2b-11e9-f336-cd2e6d864598"requiredTax 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

AttributeSample ValueReq/OptDescription
source_id"Your-Source-ID"optionalThe id in your source system
register_id"b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4"requiredValid id of register to assign the sale to.
customer_id"06e35f89-3783-11e6-ec7e-13193f7bd2ed"optionalValid id of the customer associated with the sale.
user_id"b1ed6158-f019-11e3-a0f5-b8ca3a64f8f4"requiredValid id of a Lightspeed Retail (X-Series) user associated with the sale.
sale_date"2016-05-05 23:35:34"optionalBy default current time will be assigned
note""optionalA note to be attached to the sale
status"CLOSED"requiredFor sale statuses see here.
short_code"mlzs94"optionalUsed for loyalty claiming, can be overwritten but must be unique.
invoice_number"MR-1484-NZ"optionalInvoice number, if omitted one will be assigned by Lightspeed Retail (X-Series).
invoice_sequence1484optionalNumeric part of the invoice number.
"accounts_transaction_id"""optionalXero reference invoice ID. Only editable for ONACCOUNT sales.
register_sale_products[]requiredAn 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[]optionalAn array of payments - definition of attributes in a table below.

The register sale product object

AttributeSample ValueReq/OptDescription
product_id"b1d87b58-f019-11e3-a0f5-b8ca3a64f8f4"requiredLightspeed Retail (X-Series) product id.
register_id"b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4"optionalA line item can be added on a different register than the sale was initially created.
sequence0optionalOrder of the line item in the sale, safe to ignore.
quantity1requiredProduct quantity.
price22requiredUnit price, tax exclusive.
cost20optionalCost to be used for margin calculations.
price_set0optionalBoolean value describing if sale was modified manually. Setting this to 1 will prevent price recalculation in the sell screen.
discount0optionalIf the price was set manually, discount can be declared here for reporting.
loyalty_value2.0optionalThe value by which customer's loyalty balance should be increased.
tax3.3requiredTax value.
tax_id"b1d192bc-f019-11e3-a0f5-b8ca3a64f8f4"requiredTax id as retrieved from /api/2.0/taxes.
status"CONFIRMED"optionalCONFIRMED is the only meaningful, non-default option. Makes it impossible to remove product from the sale.

The register sale payment object

AttributeSample ValueReq/OptDescription
register_id"b1e198a9-f019-11e3-a0f5-b8ca3a64f8f4"optionalA payment can also be accepted in a sale different than the sale originate from.
retailer_payment_type_id"b1e1d70e-f019-11e3-a0f5-b8ca3a64f8f4"requiredPayment type id - that's the id of payment types retrieved from /api/2.0/payment_types.
payment_date"2016-05-05 23:35:34"optionalBy default current time will be assigned.
amount25.3requiredPayment 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 with 200 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 to register_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:

  1. A payment should be added to pay off the outstanding balance of the sale. This can be done as described above.
  2. The status of the sale should be changed to one of: CLOSED, LAYBY_CLOSED, ONACCOUNT_CLOSED, PICKUP_CLOSED or DISPATCHED_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 the id 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.