Create New Sale API
Stores a complete sale definition into the database. A complete sale is one that is broadly finished processing and not still having items or payments added to it at a checkout. An eCommerce sale is also stored via this API, even if it still has fulfullment processing to complete.
Simple Examples
Storing a sale captured using an external process that you wish to store. This shows the mimimum amount of information required
{ "ExternalId": "Sale.000001", "CompletedDt": "2019-07-01T10:35", "Phase": 1, "LINE": [{ "Pid": 12345, "Qty": 1, "TotalPrice": 9.00 }], "PAYM": [{ "Type": "cash", "Amount": 9.00 }] }
Quick Notes
This API is designed so that you can send the same information multiple times and the POS will only process it once. This can simplify sending applications as they can simply resend sales several times over a couple of days as a primitive retry mechanism. The restriction to this is that you must not change the sale, such as adding new items or comments, as once the POS has loaded the sale all further attempts simply return "OK" type response codes. If we have only returned error codes for each call, then you are permitted to change the sale details; as it has not been stored.
The ExternalId field is a unique identifier for a sale. You must ensure this is unique for all time, as if you repeat a new sale with the same ExternalId, the POS will immediately assume it is the same sale. This can be quite a long string and the internal structure is completely for you to decide. We suggest using a value of current date/time concatenated with a GUID which you store against your copy of the sale as well. ExternalId can work in conjunction with website field below.
You can upload different content-types to this API:
- Application/Json - which is the typical format used by OpenApi applications
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet - To upload an Excel spreadsheet containing a sale definition (More details)
- text/plain - To upload an english description of the sale (not currently documented for use)
Fieldpine is an extensible retail platform, so if you need to store industry specific information please contact us with details and we will allocate standard field names. Do not use a field for a reason other than its documented purpose, this simply creates headaches elsewhere and makes creating of reports less effective
Error Handling or Queuing Requests
The caller of this API is responsible for ensuring accuracy of the information and the API will reject invalid sales. For example the total price of items must exactly match payments. The system cannot allow invalid transactional data to be stored. As storing sales is critical and can lead to finanical loss if information is not processed, you can provide an indication of your level of error handling ability.
The CallerHandling parameter should preferably be sent in the HTTP Header X-Fieldpine-CallerHandling, rather than the request payload.
- "X-Fieldpine-CallerHandling": "full"
- Use this option where the sending system is fully able to handle all errors and retry operations after correcting errors. This is recommended during the development phase so as to not clog the POS with invalid attempts
- "X-Fieldpine-CallerHandling": "audit"
- This option is used when the sending system is capable of handling some errors and resending corrected data, but the POS should keep track of these and alert humans where possible as well the sending application. This is the default if no CallerHandling option is provided. An example might be an eCommerce website that is creating sales and has its own transmission queue. While the eCommerce site will have a report of failed sales, the POS will also maintain a list to ensure human notification as soon as possible
- "X-Fieldpine-CallerHandling": "none"
- Use this option if the sending application is unable to accept failure conditions. The error will still be returned to the calling application but it will also be stored for human review in the POS. An example might be sales being stored from an automatic vending machine.
- "X-Fieldpine-CallerHandling": "queue"
-
When "queue" is used the server may immediately return a HTTP 202/Accepted response code and store the request
data for later processing. You will not immediately receive error responses and need to either poll for the response or process a sale-load web hook.
Queue mode provides the highest reliability of request delivery for clients as the server only needs to store the request and not attempt to process it immediately, this means any backend issues or high traffic conditions
will not affect your API call.
With Queue mode it is explicitly permitted to queue the same request data multiple times.
- "X-Fieldpine-CallerHandling": "developer"
- When set to developer mode, the API returns an error response for every little problem, including warnings. This mode should ideally be coupled with "readonly"
- "X-Fieldpine-CallerHandling": "readonly"
- This API supports the "readonly" attribute.
Note. You need to provide error handling or retry logic for any HTTP status code other than the 2xx response codes. The above error handling does not absolve callers from all responsibility
Refer to CallerHandling.htm for more details on other features and uses of this header. It has some development features to help make your client application more robust
Specifying the Product
Each saleline supplied adds another line to the customers receipt. A saleline must reference a known product in the product list. There are several ways to specify which product you wish to use
Using the Product.Pid Each product has a 32 bit integer called "pid" that uniquely identifies the item for that retailer. This is a good choice for automated systems where each Pids are known, or where the application has uniquely identified a product from a search function. Pid is clear and unambiguous.
"LINE": [ { "Pid": 2301, ... } ]
Using the Product.Plu or SKU Shop staff typically work with PLUs or SKUs. You can specify the product using a Plu instead. A plu is not guaranteed to be unique to a single product in rare cases, so you might occassionally need to handle failures.
"LINE": [ { "Plu": "KITTEN-14", ... } ]
Calculating the Price
In circumstances where the price to be charged to the customer is unknown when you create the sale, you can request the API to price the item(s). As the price is variable, you also supply a single payment record indicating what type of payment is to be used. An example of where you may need this ability is where customers are swiping their id cards to enter an exhibition and the price depends on who the customer is. In general you should avoid asking this API to calculate prices as errors in calculation will not be stopped. A better approach is to call the sale pricing API which returns the price details
{ "ExternalId": "EntryScanner16.20190701134326.12345", "CompletedDt": "2019-07-01T11:43:26", "Phase": 1, "Customer": "978000124356", "CallerHandling": "none", "LINE": [ { "Pid": 2301, "Qty": 1, "TotalPrice": "calculate" } ], "PAYM": [ { "Type": "prepay", "Amount": "all" } ] }
Pricing Models
Pricing can be represented in two different ways, a "Customer Pricing" model which is what customers expect to see, and "Technical Pricing" which is more useful to accountants and stock controllers. You can send sales in either model but must use only one within a single sale.
"Customer Pricing" and "Technical Pricing" are different and can result in different results due to rounding. If you calculate discounts on the subtotal, as per customer pricing, then this may not equal the total of the discounts calculated on each line. You should decide how you want receipts to appear and ensure that the pricing model matches that; otherwise customers will notice and complain about rounding errors, even 1 cent
Price | |
(123) Carrots | 5.94 |
(456) Drink | 1.44 |
(882) Beans | 2.44 |
Sub total | 9.82 |
Less 10% discount | 0.98 |
Total | 8.84 |
"SALE": { "LINE": [ { "Pid": 123 "TotalPrice": 5.94 }, { "Pid": 456 "TotalPrice": 1.44 }, { "Pid": 882 "TotalPrice": 2.44 }], "Discount1": "10%", "Discount1Amount": 0.98, "Discount1Rate": 10.00 }Potential Receipt Layout
(123) Carrots | 5.94 |
(456) Drink | 1.44 |
(880) Beans | 2.44 |
Sub total | 9.82 |
Less 10% discount | 0.98 |
Total | 8.84 |
Price | Discount $ | Disc Type | Each | |
(123) Carrots | 5.35 | 0.59 | 10% | 5.94 |
(456) Drink | 1.30 | 0.14 | 10% | 1.44 |
(882) Beans | 2.20 | 0.24 | 10% | 2.44 |
0.97 | 9.82 | |||
Total | 8.85 |
"SALE": { "LINE": [ { "Pid": 123 "TotalPrice": 5.35, "Discount1": "10%", "Discount1Amount": 0.59, "Discount1Rate": 10.00 }, { "Pid": 456 "TotalPrice": 1.30, "Discount1": "10%", "Discount1Amount": 0.14, "Discount1Rate": 10.00 }, { "Pid": 880 "TotalPrice": 2.20, "Discount1": "10%", "Discount1Amount": 0.24, "Discount1Rate": 10.00 }], }Potential Receipt Layout
Item | Each | Disc | Price |
(123) Carrots | 5.94 | 0.59 | 5.35 |
(456) Drink | 1.44 | 0.14 | 1.30 |
(880) Beans | 2.44 | 0.24 | 2.20 |
Total | 8.85 |
Price | Disc Type | |
(890) Gift Card | 20.00 | |
(123) Carrots | 5.99 | 10% |
(456) Drink | 2.00 | 10% |
Sub total | 27.99 | |
Less 10% discount | 0.80 | |
Total | 27.19 |
"SALE": { "LINE": [ { "Pid": 890 "TotalPrice": 20.00 }, { "Pid": 123 "TotalPrice": 5.99, "Discount1": "10%" }, { "Pid": 456 "TotalPrice": 2.00, "Discount1": "10%" }], "Discount1": "10%", "Discount1Amount": 0.80, "Discount1Rate": 10.00 }
Adding Discounts
A discount is an adjustment made to the totalprice of the saleline. A discount is not a payment, financially it is considered to have reduced the earned revenue for this saleline. To add a discount to a saleline, include the "Discount1" field identifying the type of discount, and if required the "Discount1Amount" for the amount of the discount.
The following example applies a discount called "seniors" to the saleline. The final price is derived by calculating the price as normal and then applying the discount to that figure
{ ... "LINE": [ { "Pid": 2301, "Qty": 1, "TotalPrice": "calculate", "Discount1": "seniors" } ] }
When discounts and totalprices are specified the following table outlines the logic. The main rules in this table are
- If you specify a totalprice, then any discount is already included in that price
- If you specify a discount but not the totalprice, then the totalprice is calculated and the discount applied.
UnitPrice | Qty | Discount1/Amount | TotalPrice | Calculation Logic |
$5.00 | 1 | fixed $1.50 | $3.50 | Stored as provided, no server calculation |
N | fixed $1.50 | calculate | Total price calculated, discount then applied | |
N | senior | calculate | Total price calculated, then "senior" discount applied | |
1 | fixed $2.00 | $7.00 | Unitprice calculated as $9.00 Unitprice = (TotalPrice + Discount1) / Qty |
This next example shows how to request a 10% discount on a calculated price. The field Discount1Amount contains the actual amount of the discount if known, and Discount1Rate holds the 10%
{ ... "LINE": [ { "Pid": 2301, "Qty": 1, "TotalPrice": "calculate", "Discount1": "percent", "Discount1Rate": 10.00 } ] }
Charging the Sale to an Account
If you wish to charge the sale to an account then send the sale as normal with the following details
- The CustomerId field of the Customer not the account. Sales are recorded against customers not accounts directly. Fieldpine will look up the relevant account for the customer.
- A Payment line with the type "Account" and the amount. The amount can be the keyword "all" and the total of the sale, less any other payments, will be charged to the account.
- Optionally, an override password to allow the sale to be recorded even if the account is on stop credit. See below for further details about this
{ "ExternalId": "EntryScanner16.20190701134326.12345", "CompletedDt": "2019-07-01T11:43:26", "Phase": 1, "Customer": "124356", "CallerHandling": "none", "LINE": [ { "Pid": 2301, "Qty": 1, "TotalPrice": "calculate" } ], "PAYM": [ { "Type": "Account", "Amount": "all", "OverridePassword": "SitePasswordHere" } ] }
If the sale cannot be charged to the account for some reason then the following applies:
- If the account is over credit limit and you have supplied a valid override password, the sale will be recorded without issue. Essentially the password bypasses limit checking
- If your API request has specified CallerErrorHandling=full, then you will receive an error response and the sale will not be applied
-
If your request has a CallerErrorHanding that indicates it cannot handle errors, then:
- The Sale will be stored and placed in a "Picking" state. This state indicates that human action is required to complete or void this sale.
- You will receive the normal status expected.
There are several reasons account sales may block:
- The account would be over its credit limit if this sale was accepted, AND, credit limits are set to hard blocking
- The account is on stop credit
- The amount requested exceed the floor limit for this customer or account.
Account Payments
You can send details of a customer paying their account balance via this API. In normal operation the POS records an account payment made at a checkout by the operator selling the "Account Payment" product, which the POS knows how to handle, and the fact that this is not sales revenue.
This API supports the same logic, so you can automate things like recording direct debits seen in your bank account. An example sale sent might be:
{ "ExternalId": "AccountPayment.20190701.L45", "CompletedDt": "2019-07-01T10:35", "Phase": 1, "Customer": "Bobs building supplies", "CallerHandling": "none", "LINE": [ { "Pid": 2, // The productid of "Account payment" product on your system "Qty": 1, "TotalPrice": 200.00 } ], "PAYM": [ { "Type": "directdebit", "Amount": 200 } ] }
Retailer Specific Data
Retailers will frequently have specific data they need to collect and save as part of a sale. Every sale has the basic what product was sold and for how much, but there is often a need for serial#, Vehicle Registration Number, Repair instructions, hire return date and so on.
Where these are known fields, they can simply be included as part of the sale item they relate too
{ "ExternalId": "1512523526252", "CompletedDt": "2019-07-01T10:35", "CallerHandling": "none", "LINE": [ { "Pid": 1234, "Qty": 1, "TotalPrice": 19.95, "Regno": "ABC123", // Item Specific fields "SerialNo": "82737144", "Odometer": 20549, "Vin": "AUC12345678J89", "Comments", "Caution customer has peanut allergies" } ], "PAYM": [ { "Type": "cash", "Amount": 50 }, { "Type": "change", "Amount": 30.05 } ] }
Redeeming Vouchers and Prepay Cards
Customers may have serialised vouchers or prepay cards with balances they can spend. Card details can be validated with the /CardInquiry API. This API returns details about the card the customer wishes to redeem, as well as a UsageToken that can be sent as part of the payment records. The UsageToken confirms that a card was validated.
When sending a UsageToken you do not need to send the "Type" of payment identifier. The server can deduce this automatically from the card information. Payments made using a UsageToken reduce the cardholders balance immediately, even if the sale is in a picking state waiting to be dispatched. Any difference is adjusted when the sale is finally completed.
{ "ExternalId": "123414", "CompletedDt": "2019-10-06T21:35", "CallerHandling": "none", "LINE": [ { "Pid": 1234, "Qty": 1, "TotalPrice": 19.95 } ], "PAYM": [ { "UsageToken": "1|50|ABC|AAABCC|BBBAAA", // Value returned from Cardinquiry "Amount": 19.95 } ] }
Click and Collect Sales
A click and collect sale can indicate the collecting store by inserting this into the Delivery block. This causes Fieldpine to alert the store that they have a C+C sale waiting. The Sale Location field is not changed as the sale was still created via your website etc. The server has configurations options to ensure stock is reduced in the correct stocking location.
{ "ExternalId": "123414", ... "Delivery": { "CollectLocation": 301 } }
The CollectLocation field can be either the numeric "locationId" assigned in Fieldpine, or the "Location Name". We recommend using the LocationId over the Name, as the name can be changed by users easily which will cause API rejection if values no longer match.
When sales use the CollectLocation functionality, there are specialised functions to make this easier instore to manage their C+C sales
Providing the Source Website Domain
The field "website" records the source domain name when a sale is created from a website. The value should be the domain name (optional port) it is not a URL.
The website parameter MAY include a server identifier where a website is running instances AND each instance can generate overlapping ExternalId values. A server identifier is introduced using a hash sign (#) and any server specific value
The value of "website" is used in conjunction with external id, with the following logic.
-
If Website is not supplied, or is zero length, then externalid is checked alone.
Pseudo SQL: select ... from ... where externalid=? and (website is null or website=''){ "ExternalId": "abc" ... }
"abc" must be unique and externalid on historic sales must be null or empty -
If Website is supplied, and does not contain a hash sign (#), then externalid is checked alone.
Pseudo SQL: select ... from ... where externalid=? and (website is null or website=''){ "ExternalId": "abc", "Website": "red.example.com" ... }
"abc" must be unique and externalid on historic sales must be null or empty. As the website parameter does not contain a hash, it is stored for reporting purposes only -
If Website is supplied, and contains a hash sign (#), then externalid is checked in conjunction with website
Pseudo SQL: select ... from ... where externalid=? and website=?){ "ExternalId": "abc", "Website": "red.example.com#1" ... }
"abc" must be unique with the combination of both abc and red.example.com#1.{ "ExternalId": "abc", "Website": "red.example.com#" ... }
This sale is different to the previous sale.
The above rules can also be phrased "if the website field contains a hash character, the entire website field is included as part of the uniqueness check for externalid"
Setting Client App Details
When sending sales it is very helpful for support and reporting to specify which application created the original sale. The field "CreateApp" in the sales header is used to supply this information.
- If CreateApp is a number between 65,536 and 131,071 inclusive then this value is stored. This range is reserved for retailers to insert their own application ids. This is the fastest method as the Pos sees the value and trusts the client data
- If CreateApp is a string, the POS looks up a reference table for that exact key and uses that value. If it is not present in the table, it is dynamically added and a new internal number allocated.
- If CreateApp is not defined, the POS will use any User-Agent value present in the header
{ "CreateApp": "70123", ... }
{ "CreateApp": "iPad-Pos V1", ... }
CreateApp defines the name of the application, a second attribute PosVersion is used to store the current version of your application. This field is uncontrolled in terms of contents, but we highly recommend you store a value that changes on every minor release update. It is very helpful for support purposes.
{ "CreateApp": "iPad-Pos V1", "PosVersion": "Build 22 Jun 2011, 1.13872", ... }
Held Barcodes
Fieldpine allows customers to present vouchers and coupons to influence the sale in various ways. An example might be a promo sent to a customer offering "$5 off your next purchase of ABC this year" The promo typically includes a barcode. Client apps can scan these codes and provide them as a "HeldBarcode" when creating the sale. This permits Fieldpine to correctly process the special offer and apply pricing adjustments.
{ "ExternalId": "123414", "HeldBarcode": "VOUCHER1234", ... }
Delivery Information
If the sale being created is an eCommerce order and you wish to include delivery information then you can supply a Delivery or DELI block with this information. If you have specified a customer for the sale then a delivery block is only required if the details are different to the customers current information. If delivery information is not supplied then the shipping address is taken from the customers definition.
For more information on setting delivery address, see Delivery Addresses for details.
If you are implementing a click+collect sale, see Multiple Locations for details.
{ "ExternalId": "123414", "CompletedDt": "2019-10-06T21:35", "CallerHandling": "none", "LINE": [ { "Pid": 1234, "Qty": 1, "TotalPrice": 19.95 } ], "PAYM": [ { "type": "Account", "Amount": 19.95 } ], "Delivery": { "Instructions": "Leave beside BBQ if nobody home", "Address": "1a My street|Douglas|Taranaki", "Email": "sue@example.com" } }
When supplying a shipping address, you can either provide the address in a single field "address" or split out the component pieces into the Address1, 2 fields.
Multiple Locations
A "location" is the single store where the sale was made. If you are creating an eCommerce website, then typically that website is classifed as a seperate location to physical stores. All your sales refer to that location. Think of this as the primary "revenue reporting" location
A sale may also have a "stock location", which is the location that the stock will be drawn from. It is rare for an eCommerce website to directly specify the Stock Location to be used, generate this decision is a server side one.
If your sale is a click and collect sale, then the Delivey block will include a CollectLocation value, indicating the store the customer will collect this item from. This can influence stock location on the server.
See Multiple Locations for more indepth details.
Credit Card Preauth transactions
eCommerce sales are typically charged using a PreAuth method where an amount is reserved on the customers card and the final amount is charged after product or services have been delivered. To save a sale with a preauth, include a Payment record with the type set to eftpreauth and the amount being the reserved amount
Do not send credit card numbers, only include references to the payment acquirers transaction
{ "ExternalId": "123414", "CompletedDt": "2019-10-06T21:35", "Phase": 200, "StatusAdvise": "https://myserver/sale/changing", "LINE": [ { "Pid": 1234, "Qty": 1, "TotalPrice": 19.95 } ], "PAYM": [ { "type": "eftpreauth", "Ref": "AB19182", "Amount": 40.00 } ] }
In order for you to finalise the credit card charge you need to know when the sale has been completed and the amount to charge, several options:
- Polling. Your server can poll /Sales/{Id} Api to retrieve the current sale details. When the phase changes to 1 (complete) or 10002 (void) you can retrieve the final amount to be charged from the "eftpreauth" payment field
- StatusAdvise Webhook. If the original sale included a StatusAdvise field, Fieldpine will attempt to send the sale details each time a significant event occurs on the sale. You may receive multiple calls as the sale progresses. If using statusadvise, you should also poll or have an alternative method available in case the webhook fails to fire for any reason.
- Manually. Have the person fulfilling the order login to the payment system and manually complete the payment.
Date/Time Handling
The Field "CompletedDt" is used to store the completed date/time of the sale. Fieldpine prefer to receive the localtime of the sale, but this is not always possible.
- If you can, send the localtime in "CompletedDt" by sending a ISO 8601 format date/time and include the timezone information
- If your localtime is a server and you do not believe that servers date/time to be relevant to the shopper, then send an ISO 8601 date with UTC timezone
- Advanced. If you have both localtime and UTC, you can send the local time in "CompletedDt" including timezone information, and the UTC date/time in "CompletedDtUTC" also including timezone information.
Understanding the Response
If your sale request is saved or already known you will receive a response packet with the following contents.
{ "data": { "Sid": 12345, "Status": 0, "StatusText": "Saved", "Physkey": "KAPEJEJFJV87SBEFGGTFEF" } }
Value | StatusText | Explanation |
0 | Saved | This was a new sale and we have stored it to the database. |
1 | Sid Known | A sale was sent including the "Sid" field and we already know this sid. |
2 | ExternalId known | A sale was sent and the "ExternalId" field matches an existing sale. |
3 | Srcuidkey known | A sale was sent from a Fieldpine application and we already know it. The fields used by Fieldpine application are not documented for customer use, so you should not see this status |
4 | Physkey known | A sale was sent with a "Physkey" and a sale already exists with this value |
5 | Queued | We have accepted your sale request and will process it at a future time. This status is only returned if you request queuing and the server wishes to queue it. |
32 | Saved with Error |
Your sale was Saved, but had non fatal errors. This is a rare status and will not generally be returned without
some form of exception rule existing.
An example is where you send a sale for a Customer, but the customer is not currently known, AND we have a server side rule stating that your application MAY send customer information after a delay. |
The recommended way to verify a sale was stored is:
- Verify the HTTP response code was 2xx
- Verify the received JSON is valid
- Verify a "data" object exists in the response
- Verify a field called "Status" exists in the "data" object
- Verify the value of the Status field is acceptable to you