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.
Storing a sale captured using an external process that you wish to store. This shows the mimimum amount of information required
The "sale" object defines the sale itself. The "control" object defines additional options you request, such as emailing receipts or registering webhooks for just this sale
In broad terms, the control object applies instructions on how the sale is to be processed, but isn't strictly part of the sale definition itself.
This object defines the sale, who bought what where sort of thing.
Sidenote. You can replace the object name "sale" with "testsale" or "saletest". In this case, the server will process the sale identically, but will not
actually save it. Caution is still advised however if using a live server; loading sales is complex and ensuring test sales dont update state can be difficult.
These fields define the sale header, id numbers, dates, stores, etc.
Delivery information can be specified at the sale level, using sale.delivery or on individual salelines using sale.line[N].delivery
Providing delivery at the sale level provides the values for all items unless an item overrides it. Try and avoid per line delivery if possible
as it can make fulfillment more complex - but if you need it, use it.
This field contains the output from whichever NZ Post API you used in raw form. Basically just copy the NZ Post response into this field.
Tip. The above shows you sending the complete response, you can do that, or just send the "address" object if you prefer - we should work it out.
Not released for general use. Contact Fieldpine before use
The parse object allows you to load a sale from alternative sources without completing individual fields. A common example is to cut/paste from eCommerce "you have a new sale"
emails to preload the sale.
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 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
Customer Pricing
| 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 |
Technical Pricing
| 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 |
Customer Pricing with Non discountable items
| 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
}
In this customer pricing example, the Gift card is excluded from discounts. These products are normally marked NoDiscount, but to remove all doubt you should load the "Discount1" field
for each product you included in your discount calculation.
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.
See also:
Payment Completion
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"
}
}
Sid
This is the allocated Sale Number for the current database. Sids are integers, excluding zero. A sid is not a permanent key to the sale and the Point of Sale may
renumber them. If you send a sale to fieldpine.com, we will alllocate a Sid, but when the sale is transferred to the primary database it may need renumbering at that time.
Status & StatusText
Status is a coded number (integer) indicating how the server handled this request, and StatusText is the english description of that value. Possible values are:
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.
|
Physkey
The unique identifier for this sale. This is the value you should record as proof or linkage to the sale. Physkey is a string value up to 44 ascii characters long.
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