This is a guide for programmers who want to interface their point-of-sale systems to Product, to allow them to accept payment from Product members.
The Product API is implemented over HTTPS, either in XML or in JSON. The result is the same, and the requests are analogous between XML and JSON. The POS software you write is called the client in this document; our HTTPS destination is called the server in this document.
The sequence of events takes place as follows:
This document is intended for programmers wishing to add the ability to take Product as a payment method on your point-of-sale equipment.
You should be proficient in your chosen programming language, and you should have some experience with either JSON or XML. You should be able to read JavaScript for the Reference Client, but in-depth JavaScript knowledge is not required. (JavaScript is similar to Java, PHP or C++ syntax; the complicated parts in the Reference Client don't deal with the protocol much.)
Your POS must be a programmable computer, with enough ram to, for instance, run a browser with fairly complex pages. You can use any programming language you want, but it must be able to generate ASCII text for XML or JSON. Most computers that can do web surfing are powerful enough.
This API operates over the internet to access the Product server. Your POS must have some sort of internet access and the ability to do HTTPS POST requests.
The POS screen must be able to display a photo of the customer to the cashier. This is a crucial part of the Product system and is part of what makes it so secure. We can supply five different photo sizes from 40 pixels to 640 pixels, although the lower resolutions lose a lot of detail.
We document the API in its raw XML or JSON format; we do not provide a library beyond the Reference Client, which shows you how it is done in JavaScript. You should Save the HTML and examine the JavaScript code in it. Feel free to modify it for your own experimentation.
Whichever format you choose, it is very helpful to have a parser library that can read the format and either produce a tree, or generate callbacks (such as the SAX xml library). The Reference Client uses native JSON and XML parsers built-in to modern browsers; the difficulty of parsing data depends on the tools available in your particular programming envrionment.
The JSON or XML that you must generate can be done with simple Print statements, or you can use a similar library that is designed to generate JSON or XML.
Choose whether you want to see the JSON or XML in the rest of this document.
Each request is an HTTP POST, whose body is either XML or JSON. For best results, the MIME type of the body should ideally be application/json or application/xml. You send the requests to one of the URLs:
| JSON Sign In URL https://api.product.com/signin/json.php | XML Sign In URL https://api.product.com/signin/xml.php |
| JSON Order URL https://api.product.com/orders/json.php | XML Order URL https://api.product.com/orders/xml.php |
Your POSTs must be raw data posts, not as with a form. For instance, if you had an HTML form like this:
<form method="POST"> <input name="area" VALUE="Sonoma"> <input name="city" VALUE="Atkinson"> <input type="submit" name="submit" VALUE="submit"> </form>then the raw data posted would probably be a query string and look like this:
area=Sonoma&city=Atkinson&submit=submitYou want the raw data to look like an XML or JSON string for Product instead:
{ "action": "signOut", "apitoken": "tDPKWDh0I1S9mCAtEu9hxvKuOSG4ALtT" }
There's usually a way to do it in your HTTP library. For the cURL library, try passing the data string to CURLOPT_POSTFIELDS.
Each request will return a response that is the same type as the request (XML/JSON). The MIME type will be application/json or application/xml. Every response has a property errorCode that will tell you if the request failed or succeeded. Success is 'Success', and failure is some other code, either numerical or alphanumerical.
The Sign In Token is a 32 character string that you send with almost every request. You must send this with each request, although the server will set a cookie for you named apitoken. If your client HTTP interface can pick up and send back cookies (eg Curl) then the cookie can take care of this for you; otherwise, just send it as an argument with each request that needs it. Do a Sign Out request when you are done with it, for security.
This token is your context identifier. Orders, or other sequences of requests, collect details that are attached to the sign-in token, back in our servers. If you try to interleave individual requests for different orders on the same token, the server will get confused, leading to unexpected results (probably, orders being lost instead of processed). Instead, sign in a second time to get another sign-in token, and process orders on that sign-in token independently and concurrently with the original token.
Your implementation can either present a Product email and password dialog for the cashier to directly log in, or you can rely on some other authentication (or even none at all) for your cashier, while your implementation keeps the Product credentials internally. Product does have a way to distinguish between cashiers in a company by giving each their own cashier account and signon info; if you want, you could set up parallel cashier accounts on FC for each of your own cashier accounts. This way, we can keep track of which cashier was responsible for what transactions on your behalf.
A token is guaranteed to last 20 minutes or more from the time of the most recent request. Your client should manage this timeout for best results. Here are some suggested approaches:
JSON is a popular format, easier than XML. It is based on JavaScript, but the rules are more strict.
A brief recap of the rules, and the way we use them:
For more information about JSON, see the official JSON specifications or the JavaScript Kit.
The XML we use is a proprietary dialect, so it does not follow a protocol like SOAP or XML-RPC.
A brief recap of the rules, and the way we use them:
For more information about XML, see the official W3C spec or the O'Reilly XML site.
The Send Data request will return a URL for a photo of the customer, for comparison by the cashier. You should display this file (always a .jpg) on the cashier's display; this is a crucial part of the system. You can choose among five sizes to fit your display hardware. The URL will be entity-encoded in XML so ampersands will be &.
The URL for this photo includes your sign-in token. Therefore, it is only good as long as your sign-in token is good. You can, however, display it under a different sign-in token, although you'll have to splice in your new token in place of the old.
A Product ID is the numeric account number that a consumer uses to buy items. This is the number that appears below the customer's Product barcode, and which the cashier scans to initiate payment. Fundamentally, it is an 11-digit number. As encoded in the barcode, however, it has '000' (zero zero zero) prefixed onto it for a total of 14 digits.
Typically a barcode reader emulates a keyboard through USB or a similar interface. To the computer, it's just another keyboard. When it scans a Product ID, you will get three digit zeros, eleven digits for the number, and then a Return character, like this:
0 0 0 4 5 4 9 0 4 4 7 4 9 4 Return
You can send the number with or without the leading 000, it doesn't matter.
Every response contains an errorCode property. 'Success' means that it succeeded (case sensitive; will always be spelled that way). If it is something else, there should also be another property, errorMessage. This will be a character string indicating what happened, in english. The error messages are meant for human consumption, and we reserve the right to change the wording as we see fit. The error code, however, will never change and you can test for specific codes if you want to handle specific error situations.
| JSON error reply {"errorCode": "InsufficientFunds", "errorMessage": "Insufficient Funds for a $343.00 purchase."} | XML error reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>InsufficientFunds</errorCode> <errorMessage>Insufficient Funds for a $343.00 purchase.</errorMessage> </reply> |
| JSON success reply { "errorCode": "Success", "orderid":2606, "paymentid":6151, "numProductItems":7, "totalPrice":343, "orderConfirmed":1 } | XML success reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> <orderid>2605</orderid> <paymentid>6150</paymentid> <numProductItems>2</numProductItems> <totalPrice>290.93</totalPrice> <orderConfirmed>1</orderConfirmed> </reply> |
The term "entity" refers to something that spends and receives money, and pays taxes. This can either be a person, or a company that is incorporated. (Sole Proprietorships and certain other companies are the same entity as their owners.)
A Product account is actually multiple accounts. There is one account for each entity that the customer has authority to spend money for. The person is always the first entity, and any companies (usually zero or one) are each additional entities. These entities are set up as the user signs up, and upon subsequent changes that they make to their account. Each entity is assigned an integer that does not change, called the Entity ID.
One step the cashier must perform is to figure out which of the customer's entities is paying for the current purchase. That entity id forms part of the confirmation.
Large retailers often have multiple locations. If they are listed in their Product account, we can keep track of which location each transaction was conducted in. This is decided by the api username and password used in signin; each location has a unique set of credentials, and a person (manager) who is principally responsible for it.
Product offers a way to disable a merchant account for testing or training purposes. From your Home page, go to Settings > Locations, and choose the merchant location you want to test. Near the bottom should be Test Mode which you can turn off or on. With Test Mode on, every order you send to Product will be responded to in the normal way, but instead of actually charging the customer and crediting your account on our servers, the sale is ignored.
Leaving Test Mode on accidentally can be a disaster, as a cashier could ring up real purchases that would ultimately be forgotten. Turning it off by accident would mean all your test transactions would become real. Although you could refund the extra charges or re-enter orders, it would be needless trouble. To make sure you know when it is on or off, we email you a receipt for every test transaction you perform. In addition, every reply from the orders group of requests (that is, not signin/out, ping) carries an extra attribute testMode whose value is 1. We suggest that you use it to make some striking change to the appearance of your POS screen, such as changing large areas of color and/or adding a large banner that wasn't there before, to avoid accidents.
When you send an order, you can optionally send along a Description, a Clerk ID, and a Reference Number. They are all for your own use and are not visible to customers. The Description and Reference Number show up in the Orders and Payments listing when you log into the Product web site.
Use the Description to specify categories of transactions, or their department, or whatever you want.
Use the Clerk ID to specify which clerk, waiter or salesperson registered the order. For restaurants, we can collect tips by clerk ID for disbursement later. The Clerk ID is sent down with the order confirmation rather than the original order.
Use the Reference Number to record your internal transaction ID to better match our records with yours.
The Description can be any character string using Latin-1 encoding, up to 100 characters long. The Clerk ID and Reference Number can also be anything from Latin-1, but only 25 characters long. Longer strings will be truncated.
Many Product merchants are restaurants and other service businesses where customers might leave tips for the individuals who serve them. Product manages these tips and allows the merchant to apportion tip money to exactly the clerks who deserve them.
Central to this system is a setting for each of the merchants' locations: the tipping policy. A merchant changes this in the Settings > Locations area from the home page.
The customer enjoys a meal at a restaurant and then pays the bill with FC for the total amount, not including the tip. After leaving the restaurant, the customer uses their Product smartphone app, or the website, to choose a tip for the waiter. They have two hours, after which a default tip is applied so the transaction can be closed. As of this writing, Expected defaults to an 18% tip, and Encouraged defaults to a zero tip at the end of this period.
These are automatically provided if the merchant's setting is set to Expected or Encouraged.
Use this request to sign in and get a token to use on the other requests.
This request returns your API token in apitoken. You must pass this in all of your other requests using this api, in an attribute called apitoken, or in an HTTP cookie named apitoken. Your HTTP interface may or may not do cookies; often the simplest is to just pass it with each request.
In addition, it sends you back the timeout for your session. If you go without a request for that many seconds, the API will automatically sign you out. See the Ping request for more info.
| JSON request { "action": "signIn", "username": "eh3nh9SsrlOp0V" , "password": "cuAnEP79"} | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <signIn> <username>eh3nh9SsrlOp0V</username> <password>cuAnEP79</password> </signIn> |
| JSON reply {"errorCode": "Success", "apitoken": "tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP", "signintimeout": 3570} | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> <token>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</token> <signintimeout>3570</signintimeout> </reply> |
Your apitoken lasts for the signintimeout (currently 60 minutes) from the time of last use. Renew it by sending any request before it times out; a Ping request is good for this because it does nothing else. (The current duration is returned to you from the Sign In request.
| JSON request { "action": "ping" , "apitoken": "tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP"} | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <ping> <apitoken>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</apitoken> </ping> |
| JSON reply {"errorCode": "Success"} | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> </reply> |
Very simple; just pass your token back.
| JSON request { "action": "signOut" , "apitoken": "tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP"} | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <signOut> <token>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</token> </signOut> |
| JSON reply {"errorCode": "Success"} | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> </reply> |
This initiates an order. You will follow this with a Confirm Order, Deny Order, Cancel Order or First Purchase Verify request. You can send a split subtotal and tax, or just the total alone if you already track your sales tax yourself. The Product ID is the code that the cashier gets from scanning the customer's barcode, either on their smartphone or on a separate card. Although the Product ID is all digits, you should enclose it in quotes for json because it can get longer than certain implementations can work with.
The total and productid attributes are mandatory. Some additional attributes you can send with Send Order:
You get a large chunk of data back called the buyer tree. It has enough information so that you can display the customer's photo and option buttons for each possible entity the customer wants to charge it to. If the status is 'Inactive', you must then proceed on to the First Purchase Verify request, otherwise just confirm or deny the order. This buyer tree has three legs:
| JSON request { "action": "sendOrder", "subtotal": 6.95, "tax": 0.31, "total": 7.26, "photoSize": "m", "productid": "00079648439951"} | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <sendOrder> <token>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</token> <subtotal>6.95</subtotal> <tax>0.31</tax> <simulatefirstpurchase>1</simulatefirstpurchase> <total>7.26</total> <description>API Order</description> <photoSize>m</photoSize> <productid>00079648439951</productid> </sendOrder> |
| JSON reply { "errorCode": "Success", "buyertree": {"contact": {"contactid":"4956", "photoid":"116", "status":"Active", "salutation":"Mr.", "name":"Dana Sabina", "gender":"M", "driversLicenseNumbers": ["O3699433", "G2979882", "U1075056" ]}, "image":"http://product.com/shared/photo.html?id=116&s=m", "entities": [{"entityid":"5325", "organization":"Sabina, Dana", "type":"Individual"}, {"entityid":"1", "organization":"Skokie Auto Parts", "type":"C Corporation"} ]} } | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> <buyertree> <contact> <contactid>4956</contactid> <photoid>116</photoid> <status>Inactive</status> <salutation>Mr.</salutation> <name>Dana Sabina</name> <gender>M</gender> <driversLicenseNumbers> <driversLicenseNumber>E0579690</driversLicenseNumber> <driversLicenseNumber>U4103571</driversLicenseNumber> <driversLicenseNumber>U1075056</driversLicenseNumber> </driversLicenseNumbers> </contact> <image>http://product.com/shared/photo.html?id=116&s=m</image> <entities> <entity> <entityid>5325</entityid> <organization>Sabina, Dana</organization> <type>Individual</type> </entity> <entity> <entityid>1</entityid> <organization>Skokie Auto Parts</organization> <type>C Corporation</type> </entity> </entities> </buyertree> </reply> |
| JSON request {"action": "sendOrder", "photoSize": "m", "productid": "00079648439951", "items": [ {"sku":"1v3982324285867", "description":"Dog Sled", "total":290.93, "quantity":2, "rate":135.95, "tax":19.03, "subtotal":271.90}, {"sku":"5t0967968768401", "description":"Dog Food", "total":29.60, "quantity":4, "rate":7.40, "subtotal":29.60}, {"sku":"3k2291678648714", "description":"Dog House", "total":22.47, "tax":1.47, "subtotal":21}], "subtotal": 322.50, "tax": 20.50, "total":343.00, "apitoken": "tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP"} | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <sendOrder> <photoSize>m</photoSize> <productid>00079648439951</productid> <items> <item> <sku>1v3982324285867</sku> <description>Dog Sled</description> <total>290.93</total> <quantity>2</quantity> <rate>135.95</rate> <subtotal>271.90</subtotal> <tax>19.03</tax> </item> <item> <sku>5t0967968768401</sku> <description>Dog Food</description> <total>29.60</total> <quantity>4</quantity> <rate>7.40</rate> <subtotal>29.60</subtotal> </item> <item> <sku>3k2291678648714</sku> <description>Dog House</description> <total>22.47</total> <subtotal>21</subtotal> <tax>1.47</tax> </item> </items> <subtotal>322.50</subtotal> <tax>20.50</tax> <total>343.00</total> <apitoken>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</apitoken> <description>API Order</description> </sendOrder> |
You only need to do this if this purchase is the first for your customer on the Product system. You can tell because the status in the contact leg of the buyer tree says 'Inactive'. Really, anybody can upload any photo to our website; we need the cashier's help to make sure their face, their photo and their driver's license agree. You must write code to go through this procedure because a customer's first purchase could be anywhere.
The screen should show the person's face, from the image branch of the buyer tree. It should also show four option buttons that the cashier must press to continue:
Your cashier must do the following:
| JSON request { "action": "firstPurchaseVerify" , "contactid": "4956" , "driversLicenseNumber": "U1075056" } | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <firstPurchaseVerify> <token>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</token> <contactid>4956</contactid> <driversLicenseNumber>U1075056</driversLicenseNumber> </firstPurchaseVerify> |
| JSON reply { "errorCode": "Success", "cardStatus": "verified" } | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> <cardStatus>verified</cardStatus> </reply> |
If the face matches the photo, send the Confirm Order request. The transaction from the most recent Send Order will then be processed. You must pass the entityid from the entities branch of the buyer tree for the entity to be charged. If there is no Confirm Order request, or if a Send Order request goes through before then, the transaction is not stored, effectively denying and canceling the order. After this request, there is no transaction attached to this token, and so you must do a Send Order again to restart the process.
An argument not shown here is the clerkid, which is just a character string identifying which clerk or waiter finished the transaction. We can keep tips from customers collected by clerkid, so you can pay them later according to the exact tips each received. In addition, ths adds a layer of accountability to your clerks' work.
The reply contains the total price to be collected from the customer, and a count of all of the items ordered. It also contains the orderid and paymentid, which you can use to retrieve the information later (not implemented as of this writing).
| JSON request { "action": "confirmOrder" , "entityid": "5286" } | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <confirmOrder> <token>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</token> <entityid>5325</entityid> </confirmOrder> |
| JSON reply { "errorCode": "Success", "orderid":2606, "paymentid":6151, "numProductItems":7, "totalPrice":343, "orderConfirmed":1 } | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> <orderid>2605</orderid> <paymentid>6150</paymentid> <numProductItems>2</numProductItems> <totalPrice>290.93</totalPrice> <orderConfirmed>1</orderConfirmed> </reply> |
If the face doesn't match the photo, send the Deny Order request. After this request, there is no transaction attached to this token, and so a Send Order must be done again to restart the process. Denied orders are not kept on our servers and the information is lost forever. In addition, the customer is marked for a Product security review. This should only be used for situations with serious suspicion. The cashier can say whatever they want.
| JSON request { "action": "denyOrder" , "entityid": "5286" } | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <denyOrder> <token>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</token> <entityid>5325</entityid> </denyOrder> |
| JSON reply { "errorCode": "Success", "orderDenied": 1 } | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> <orderDenied>1</orderDenied> </reply> |
If, at the last minute, you need to abort an order, send the Cancel Order request. After this request, there is no transaction attached to this token, and so a Send Order must be done again to restart the process. Canceled orders are not kept on our servers and the information is lost forever. There is no harm in canceling an order other than the need to reenter data. To cancel an order with prejudice against the customer, use Deny Order.
| JSON request { "action": "cancelOrder" , "entityid": "5286" } | XML request <?xml version="1.0" encoding="ISO-8859-1"?> <cancelOrder> <token>tY5NdX7822lWu9F1EdXOaWf2ZD64eeZP</token> <entityid>5325</entityid> </cancelOrder> |
| JSON reply { "errorCode": "Success", "orderCanceled": 1 } | XML reply <?xml version="1.0" encoding="UTF-8" ?> <reply> <errorCode>Success</errorCode> <orderCanceled>1</orderCanceled> </reply> |
Here is PHP code that does a Product request:
$http = curl_init("https://api.product.com/orders/json.php");
curl_setopt_array($http, array(
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => '{"action": "confirmOrder",
"apitoken": "tUqdoOUEmV1loFtdIzqE5kfoTFbrHhuM", "entityid": 62201}',
CURLOPT_HTTPHEADER => array('Content-Type' => 'application/json'),
));
$result = curl_exec($http);
echo "<br>------ data returned: -----------<br>";
echo $result;
echo "<br>-------<br>";
curl_close($http);