About PushCart

Push-Cart is a collection of services and APIs provided by Locally. These services allow retailers to receive real-time transactions from the Locally platform as they occur, effectively “pushing” the shopper’s cart to an external system such as a Point of Sale, Sales Order Management, or ERP for fulfillment.

Prerequisites

Inventory Integration or Inventory Feeds

Locally offers several ways to sync store inventory. If your system is already sending on-hand inventory to Locally, no action is required. Here is an example inventory feed format. The format is simple:

upcqtyprice
727602259178110.99
605284574539299.95
736745040314100147.50

In order to use PushCart your system must be updating inventory feeds at least once every 24 hours. If Locally detects feed staleness beyond 72 hours, PushCart will be temporarily disabled. Inventory setup is outside of the scope of this document. Please review our documentation here or contact your Locally support representative about getting started.

Participation in “Buy It Locally” (BIL)

Locally also requires the activation of “Buy It Locally” for all retailers in order to participate in the PushCart transaction workflow. PushCart supports Reserve Online Pickup in Store (ROPIS) and Buy Online Pickup In Store (BOPIS) transactions. To be eligible, stores may accept ROPIS, BOPIS, or both.

PushCart does not currently support Same-Day Delivery or Ship to Store orders. Retailers utilizing Pushcart that wish to offer those transaction types can still fulfill those orders from the Locally order management console in their Locally dashboard.

Before proceeding with your PushCart integration, ensure you’ve completed the setup steps.

Configuring Your PushCart Profile

If you don’t already have a Locally company account, please contact your Locally representative about getting an account set up.

Once your account is set up, the following basic settings can be adjusted by contacting Locally.

Webhook URL for Order Updates

You will need to provide Locally with a webhook URL endpoint. This URL can be changed at any time via the configuration panel. Locally will send a payload containing the cart object (see below) to this endpoint whenever a new order is created or has been updated. It will be your company’s responsibility to consume this payload and act accordingly in order to fulfill the request in a timely manner. See the “Buy it Locally: Real-time Carts API” section below for more information on this topic.

Optionally, you may instead choose to poll our API at a high frequency. Please contact Locally to discuss if this option is feasible for your needs.

Manage Participating Stores

You can define which stores will have the PushCart capability. Any stores that are not enabled for PushCart fulfillment will default to the standard Buy it Locally customer order workflow (if any).

Buy It Locally: Real-time Carts API

The Real-time Carts API is your way to request up-to-the-minute information on all orders you have access to, and how to update an existing order’s status in the Locally system.

Requesting your API key

You will need an API Key to access your CartAPI endpoint. See Generate An API Key for more info.

GET /carts/{page} endpoint

Now that you have your API key, you will be able to access our Cart API endpoints.

A request is built by adding “Locally-API-Token” to the header of your request and the value of that will be the API key you generated on our platform.

To access the list of all the carts, use:

GET https://www.locally.com/api/v2/carts

You will need to pass along your API key in the header of the request. For example, in JSON:

{  
    "url": "<https://www.locally.com/api/v2/carts">,  
    "raw_url": "<https://www.locally.com/api/v2/carts">,  
    "method": "get",  
    "headers": {  
        "Locally-API-Token": "ea432b89353efxxxe46f147b3uae37fgcae2337"  
    }  
}

This will return the list of all carts your account has access to, ordered by most recent first, and broken up via 50 per page. The first page can be accessed via just /api/v2/carts or /api/v2/carts/0.

Sample payload response:

{  
    "status": true,  
    "properties": {  
        "date_time": "2022-02-03 10:52:40",  
        "current_page": 0,  
        "total_pages": 3  
    },  
    "carts": [  
        {  
            "hash": "E3GKY8",  
            "type": "HOLD",  
            "n_items": 1,  
            "source": "From locally.com",  
            "ordered_at": "2022-01-28 10:28:22",  
            "updated_at": "2022-01-28 15:34:30",  
            "delivery_status": "",  
            "pickup_window": "2022-01-29 15:00:00",
            "items": [  
                {  
                    "currency": "EUR",  
                    "total": "100.00",  
                    "status": "Canceled",  
                    "sts_status": "N/A",  
                    "qty": 1,  
                    "product_id": 967586,  
                    "order_resolution": "auto-cancelled",  
                    "upc": "601842495575",
                    "alias":[
                      "D1447020122-00003"
                      ]
                }  
            ]  
        },  
        {  
            "hash": "OK38N3",  
            "type": "ORDER",  
            "n_items": 1,  
            "source": "From locally.com",  
            "ordered_at": "2021-11-18 15:08:35",  
            "updated_at": "2021-11-18 15:09:59",  
            "delivery_status": "",
            "pickup_window": "2022-11-19 15:00:00",
            "items": [  
                {  
                    "currency": "JPY",  
                    "total": "990.00",  
                    "status": "Confirmed",  
                    "sts_status": "N/A",  
                    "qty": 1,  
                    "product_id": 936676,  
                    "order_resolution": "",  
                    "upc": "745889071559",
                     "alias": []
                }  
            ]  
        }  
    ]  
}

Definition of the response payload:

Field

Description

status

Boolean. true if request was a success, false if not.

message

Only sent if status was false, gives the reason for the false status.

properties.date_time

The datetime of this request.

properties.current_page

The current page you are on. Pagination starts at page 0 (zero).

properties.total_pages

Total number of pages. If total pages is 5, then pages 0, 1, 2, 3, 4 exist.

carts

Array of carts.

carts[].hash

The unique hash for the order. For example E3GKY8.

carts[].type

HOLD for a reservation for in-store payment.
ORDER for a payment for in-store pickup.
SHIP for a Ship-to-Store order (This is a special case for select brands only. This transaction type is NOT available for POS vendor or retailer integrations).

carts[].n_items

Number of items in the order.

carts[].source

Domain where the order originated from. “From Locally.com”, “From brand-site.com”, etc.

carts[].ordered_at

The datetime of when the order was placed.

carts[].updated_at

The datetime of when the order has changed last.

carts[].delivery_status

If the cart is a Same-Day Delivery, this is its current status. PushCart does not support Same-Day Delivery at this time, so this will be blank.

carts[].pickup_window

The datetime string of the selected pickup window slot time or empty string.

carts[].items

Breakdown of items in carts statuses and basic information. Please note that there can be more than one item per cart.

carts[].items[].currency

The currency the order is in. Adheres to ISO 4217

carts[].items[].total

The total amount for the item. Includes tax if this is a "Buy Online, Pickup in Store (BOPIS)" order.

carts[].items[].status

The status of the item:

Ordered : The BOPIS or ROPIS request has been placed and has not been resolved by the store.
Confirmed : The store has confirmed that the item is in stock and is being held for the shopper.
Canceled : The store has rejected the BOPIS or ROPIS request.
Charge_Failed : There was an issue charging the shopper’s credit card on a BOPIS order and payment was not captured. This is rare but should be accounted for. The store should treat these like reservation requests and collect payment when the shopper arrives.

carts[].items[].sts_status

This is for Ship-to-Store orders only. If the store is not participating in Ship-to-Store, this will always read “N/A”.

carts[].items[].qty

Quantity of this item.

carts[].items[].product_id

The Locally Product ID of the item.

carts[].items[].order_resolution

The order resolution, SUCCESS, DECLINED, etc. This gives more detail as to why/how the item status is what it is. The full list of order resolution statuses can be found later in this document under Other API Functionality: Order Resolution.

carts[].items[].upc

The UPC/EAN of the item.

carts[].items[].alias

Array of UPC/EAN aliases or empty array in case of no aliases

Reference Doc: GET /carts

GET /cart/{cart_hash} endpoint

This will give you all the data for this specific cart. Your company must have access to this cart in order to retrieve its data.

To access this single cart’s information, use this endpoint:

GET https://www.locally.com/api/v2/cart/{cart_hash}

You will need to pass along your API Key in the header of the request. For example, in JSON:

{  
    "url": "<https://www.locally.com/api/v2/cart/QE3QL6">,  
    "raw_url": "<https://www.locally.com/api/v2/cart/PDQE89">,  
    "method": "get",  
    "headers": {  
        "Locally-API-Token": "ea432b8935xxx459be4gf147b33ae3uf0cae2337"  
    }  
}

Sample payload response:

{  
    "status": true,  
    "properties": {  
        "date_time": "2022-02-03 11:11:18",  
        "type": "HOLD",  
        "hash": "QE3QL6",
        "pickup_window": "2022-02-04 11:00:00",
        "ordered_at": "2022-02-03 11:07:42",  
        "updated_at": "2022-02-03 11:09:43",  
        "delivery_status": "",  
        "invoice": "https://www.locally.com/order/QE3QL6/S615W9CDQK7M?print=1"  
    },  
    "customer": {  
        "first_name": "John",  
        "last_name": "Doe",  
        "address_1": "509 N Carrollton Ave",  
        "address_2": "",  
        "city": "New Orleans",  
        "state": "LA",  
        "zip": "21541",  
        "country": "US",  
        "email": "[email protected]",  
        "phone": "",  
        "marketing_active_consent": "answer_yes"  
    },  
    "store": {  
        "name": "Locally Test Store Netherlands",  
        "phone": "",  
        "address": "1012 KN",  
        "address_2": "",  
        "city": "Amsterdam",  
        "state": "",  
        "zip": "9V9R+RV",  
        "country": "NL",  
        "store_id": 245728,  
        "vendor_id": 0  
    },  
    "items": [  
        {  
            "status": "Ordered",  
            "sts_status": false,  
            "product_id": 967586,  
            "qty": 1,  
            "brand": "Trek",  
            "upc": "601842495575",  
             "alias": [
                  "D1447020122-00003"
                  ],
            "name": "Bontrager Circuit Road Cycling Shoe",  
            "attrib_1": "US Men 4/Women 5.5/EU 37",  
            "attrib_2": "",  
            "currency": "EUR",  
            "msrp": "100.00",  
            "product_price": "100.00",  
            "product_price_less_vat": "100.00",  
            "subtotal": "100.00",  
            "tax": "0.00",  
            "tax_rate": "0.00",			  
            "tax_is_vat": true,  
            "tax_remitted": false,  
            "commission": "3.50",  
            "stripe_fee": "3.20",  
            "total": "100.00",  
            "disbursement": "96.80",  
            "order_resolution": "",  
            "resolution_notes": "",  
            "messages": [  
                {  
                    "sender": "retailer",  
                    "description": "test message to customer",  
                    "created_at": "2022-02-03 11:09:41"  
                },  
                 {  
                    "sender": "customer",  
                    "description": "test message to store",  
                    "created_at": "2022-02-03 11:58:21"  
                }

        		]
    		}
		]
}

Definition of the response payload:

Field

Description

status

Boolean. true if request was a success, false if not.

message

Only set if status was false, gives the reason for the false status.

properties.date_time

The datetime of this request.

properties.type

HOLD for a reservation for in-store payment.
ORDER for a payment for in-store pickup.
SHIP for a Ship-to-Store order (This is a special case for select brands only, opt-in by the retailer is required).

properties.hash

The hash for the order. Ex. QE3QL6

properties.pickup_window

The datetime string of the selected pickup window slot time or empty string.

properties.ordered_at

The datetime of when the order was placed.

properties.updated_at

The datetime of when the order has changed last.

properties.delivery_status

If the cart is a Same-Day Delivery, this is its current status. PushCart does not support Same-Day Delivery at this time, so this will be blank.

properties.invoice

URL to a minimal PDF of the invoice.

customer

Customer information

customer.first_name

The customer’s first name

customer.last_name

The customer’s last name

customer.address_1

If this is a BOPIS order or if the customer has an address on file, their billing street address. Otherwise blank.

customer.address_2

If this is a BOPIS order or if the customer has an address on file, their secondary billing address. Otherwise blank.

customer.city

If this is a BOPIS order or if the customer has an address on file, their billing city. Otherwise blank.

customer.state

If this is a BOPIS order or if the customer has an address on file, their billing state. Otherwise blank.

customer.zip

If this is a BOPIS order or if the customer has an address on file, their billing postal code. Otherwise blank.

customer.country

If this is a BOPIS order or if the customer has an address on file, their billing country. Otherwise blank.

customer.email

The customers email address

customer.phone

The customers phone number

customer.marketing_active_consent

Whether a shopper has opted in to receive marketing emails from the store

store

Store information

store.name

The store’s name

store.phone

The store’s phone number

store.address

The store’s street address

store.address_2

If available, the store’s address 2 information

store.city

The store’s city

store.state

The store’s state

store.zip

The store’s postal code

store.country

The store’s country

store.store_id

The unique Locally Store ID

store.vendor_id

The brand's unique ID for the store, if applicable

items

Array of items in the cart

items[].status

The status of the item:

Ordered : The BOPIS or ROPIS request has been placed and has not been resolved by the store.
Confirmed : The store has confirmed that the item is in stock and is being held for the shopper.
Canceled : The store has rejected the BOPIS or ROPIS request.
Charge_Failed : There was an issue charging the shopper’s credit card on a BOPIS order and payment was not captured. This is rare but should be accounted for. The store should treat these like reservation requests and collect payment when the shopper arrives.
sts_status: This is for Ship-to-Store orders only. If the store is not participating in Ship-to-Store, this will always read false.

items[].product_id

The Locally product ID of the item

items[].qty

Quantity of this item in the cart

items[].brand

The brand / manufacturer name of the product

items[].upc

The UPC/EAN of the item

items[].alias

Array of UPC/EAN aliases or empty array in case of no aliases

items[].name

The product name

items[].attrib_1

The product’s first attribute: color, size, etc.

items[].attrib_2

The product's second attribute: color, size, etc.

items[].currency

The currency the order is in

items[].msrp

The MSRP for the item

items[].product_price

The item price for which the shopper paid or reserved the item. This can vary from MSRP if the store uses in-store pricing

items[].product_price_less_vat

If the price includes VAT, the product price less VAT. Please note that this is separate from US and Canadian taxes and applies to VAT countries only.

items[].subtotal

The subtotal of the item.

items[].tax

The calculated tax for the item if this is a BOPIS order.

items[].tax_rate

The tax rate for the item if this is a BOPIS order. For ex., a 6.25% tax rate would appear as 6.25.

items[].tax_is_vat

Boolean. Whether the tax amount is VAT or not.

items[].tax_remitted

Whether or not we remitted sales tax on behalf of the store. Locally remits sales tax on behalf of stores in the US and Canada for BOPIS orders only.

items[].commission

The commission Locally is owed for the item.

items[].stripe_fee

If this is a BOPIS order, the fee that Stripe charged for the transaction.

items[].total

The total amount for the item. If this is a BOPIS order, this includes tax.

items[].disbursement_brand

  • For Ship-to-store orders only,_ the revenue split amount payable to the brand on this order (will display as $0 until sts_status = ‘shipped’)

items[].disbursement_retail

  • For Ship-to-store orders only,_ the revenue split amount payable to the retailer on this order (will display as $0 until sts_status = ‘picked up’)

items[].split_brand

  • For Ship-to-store orders only_, the brand’s revenue split amount on the order

items[].split_retailer

  • For Ship-to-store orders only_, the retailer’s revenue split amount on the order

items[].order_resolution

The order resolution, SUCCESS, DECLINED, etc. This gives more detail as to why/how the item status is what it is. The full list of order resolution statuses can be found later in this document under Other API Functionality: Order Resolution.

items[].resolution_notes

Notes regarding the order resolution if the store has included them

items[].messages

Array of messages for the item, if any exist

items[].messages[].sender

Whether the message was sent by the retailer or the customer

items[].messages[].description

The contents of the message

items[].messages[].created_at

When the message was sent

Reference Doc: /cart/{cart_hash}

POST /cart/{cart_hash}/{upc} endpoint

Use this endpoint to make updates to existing orders. See “Lifecycle of a PushCart Transaction” for details on the timing of these requests.

You will need to pass along your API Key in the header of the request. For example, in JSON:

{  
    "url": "<https://www.locally.com/api/v2/cart/QE3QL6/190340561726">,  
    "raw_url": "<https://www.locally.com/api/v2/cart/QE3QL6/190340561726">,  
    "method": "post",  
    "headers": {  
        "Locally-API-Token": "ea432b8axxxef459be46f147577e37fgcae2u37"  
    },  
    "data": {  
        {
          "status" : "confirm"
        }  
    }  
}

Payloads:

Confirm the BOPIS or ROPIS order:

{  
	"status" : "confirm"  
}

Reject the BOPIS or ROPIS order:

{  
	"status" : "reject"  
}

Send a message to the shopper via SMS and email:

{  
	"message" : "Message goes here that will be sent to the customer via SMS and email"  
}

Update the order status and include a message in the same payload:

{  
	"status" : "confirm",
	"message" : "Looking forward to seeing you!"  
}

Refund a previously confirmed BOPIS order

{  
	"refund":true  
}

Sample response:

{  
	"status": true,  
  "debug": "Inputs: array ('status' => 'reject')",  
  "code": 102  
}

Definition of the response payload:

FieldDescription
statusBoolean. true if request was a success, false if not.
messageOnly set if status was false, gives the reason for the false status.
debugList of inputs in case debugging is needed.
codeHTTP response code.

Lifecycle of a PushCart Transaction

What follows is a description of the workflow, starting with the initial customer engagement in the “Buy It Locally” system through to fulfillment (pickup) of the product from the local retailer:

Payment, Communications, and Order Handoff

This section requires the use of webhooks. Read more about Locally's webhook configuration here.

1. PENDING STORE ACTION. WINDOW FOR CONFIRMATION:

Customer submits the order. If this is for a BOPIS order, their credit card is authorized for the full MSRP amount but Locally does not charge their card yet.

Locally pushes information for the new order by sending a PUT request to the webhook address you provided. The payload is as follows:

{
  "upc":"601842495575",
  "order_id":"QE3QL6",
  "status":"ordered",
  "order_url":"https://www.locally.com/api/v2/cart/QE3QL6"
}

The retailer has a 4 hour window, during store operating hours, to respond to the order via the Cart API or via the Locally notification link. If the order is placed when the store is closed, the 4 hour window begins when the store next opens.

If the 4 hour window expires, the customer will be notified via email and SMS that it has expired and that the order is canceled. Locally will also PUT an API request to your webhook endpoint about this order, indicating that it was automatically rejected. The payload is as follows:

{
  "upc":"601842495575",
  "order_id":"E3GKY8",
  "status":"canceled",
  "order_url":"https://www.locally.com/api/v2/cart/QE3QL6"
}

2. RETAILER APPROVED OR REJECTED:

You must POST to Locally’s cart API with the status confirm in order to approve the customer order. To reject the order, you must submit the status reject. If the order was approved on the Locally platform, we will notify you via a PUT request to the webhook address you provided. The payload is as follows:

{
  "upc":"601842495575",
  "order_id":"QE3QL6",
  "status":"confirmed",
  "order_url":"https://www.locally.com/api/v2/cart/QE3QL6"
}

If you reject the customer’s request, we will notify the customer about the rejection.

After a BOPIS or ROPIS order has been confirmed or rejected, you can still communicate with the customer about this order through the messaging system.

If you approve a BOPIS request, the customer’s credit card will be charged immediately for the full amount and the funds will be transferred directly to your Stripe account.

Other API Functionality

SEND MESSAGE:

You can pass along any message through the POST API endpoint. For example:

{  
    "message":"Message goes here that will be sent to the customer via SMS and email"  
}

ORDER RESOLUTION:

If not supplied, it will be assumed that the order was successful. These can be set to provide information on what happened with the order. Order resolution statuses affect your billing, so it’s important for the store to be accurate in reporting what happened.

Here are the possible order resolutions you can change a line item to:

{  
    "1": {  
        "id": 1,  
        "name": "The shopper purchased this item (and this item only)",  
        "brief_name": "purchased"  
    },  
    "2": {  
        "id": 2,  
        "name": "The shopper purchased this item and additional products",  
        "brief_name": "purchased +"  
    },  
    "3": {  
        "id": 3,  
        "name": "The shopper showed up, but purchased a different product than this.",  
        "brief_name": "purchased other"  
    },  
    "4": {  
        "id": 4,  
        "name": "The shopper showed up but did not purchase anything",  
        "brief_name": "no purchase"  
    },  
    "5": {  
        "id": 5,  
        "name": "No sale/the shopper did not show up to the store",  
        "brief_name": "no show"  
    }  
}

Payload needed to push to us is:

{  
    "order_resolution":1  
}

REFUND ITEM:

This can be done through the same POST API endpoint. Our system will do a full refund for the line item you are altering. Refunds will be processed immediately, though the shopper’s financial institution will generally take 5-10 business days to process the returned funds. Payload example:

{  
    "refund":true  
}

Testing endpoints and integrations

Our current process is to flag a company as being in “test mode.” What that does is make it so if an order is placed that would normally collect money, it would use our test payment credentials. This means orders can be placed on production with a test CC (acct: 4242 4242 4242 4242 exp: 02/26, csv: 111) and it will work the same as a live order. We could create a test store that could be used as well. Please reach out to us when ready for testing.

Important Considerations

SALES TAX COLLECTION IN THE US AND CANADA:

As a Marketplace Facilitator, Locally is required to collect and remit sales tax on BOPIS orders in the US and Canada. The sales tax amount on BOPIS sales is not remitted to retailers; Locally collects the tax amount during the transaction and remits it to state and local jurisdictions. A log of the sales tax Locally has collected and remitted is available in a retailer’s Locally dashboard.

Locally uses Avalara/Avatax to calculate sales tax amount based on the item being purchased and the location of the store.

The opposite is true outside of the US and Canada. In Asia and Europe, Locally passes the tax (VAT) amount to the retailer, and it is the retailer’s responsibility to remit.

CHARGE FAILED:

While very rare, there can be cases where a BOPIS order is able to be placed, but the credit card charge fails when the retailer confirms the item. This is more common in Europe where varying authentication requirements by different banks can lead to complications, however users in the US and Canada should take this into account as well.

In this case, the item status changes to Charge_Failed. At this point, both the shopper and the retailer are emailed informing them that the order was confirmed but the credit card charge failed and the order must be paid in-store, and the Locally order screen UI is also updated with this messaging. Pushcart users must take this into account; our recommendation is to change the type to a ROPIS order to indicate payment was not captured.

Here is a sample payload response for a failed charge:

{  
    "status": true,  
    "properties": {  
        "date_time": "2022-04-29 11:46:25",  
        "type": "ORDER",  
        "hash": "00001",
        "pickup_window": "2022-04-29 18:00:00",
        "ordered_at": "2022-04-28 11:20:48",  
        "updated_at": "2022-04-28 11:20:49",  
        "delivery_status": "",  
        "invoice": "https://www.locally.com/order/00001/SGZ48JH1WNOK?print=1"  
    },  
    "customer": {  
        "first_name": "John",  
        "last_name": "Smith",  
        "address_1": "123 Fake St",  
        "address_2": "",  
        "city": "New Orleans",  
        "state": "LA",  
        "zip": "70179",  
        "country": "US",  
        "email": "[email protected]",  
        "phone": "+11234567890",  
        "marketing_active_consent": "answer_yes"  
    },  
    "store": {  
        "name": "Sample Store",  
        "phone": "+11234567890",  
        "address": "789 Fake St",  
        "address_2": "",  
        "city": "New Orleans",  
        "state": "LA",  
        "zip": "70471",  
        "country": "US",  
        "store_id": 12345,  
        "vendor_id": 0  
    },  
    "items": [  
        {  
            "status": "Charge_Failed",  
            "sts_status": false,  
            "product_id": 653104,  
            "qty": 1,  
            "brand": "Arc'teryx",  
            "upc": "686487512122",  
            "name": "Mantis 26 Backpack",  
            "attrib_1": "",  
            "attrib_2": "",  
            "currency": "GBP",  
            "msrp": "90.00",  
            "product_price": "90.00",  
            "product_price_less_vat": "75.00",  
            "subtotal": "90.00",  
            "tax": "15.00",  
            "tax_rate": "20.00",  
            "tax_is_vat": true,  
            "tax_remitted": false,  
            "shipping_cost": "0.00",  
            "commission": "2.63",  
            "stripe_fee": "0.00",  
            "total": "90.00",  
            "order_resolution": "",  
            "resolution_notes": "",  
            "messages": \[],  
            "disbursement": "90.00"  
        }  
    ]  
}