Processing In-App Payments
with the Mozilla Web Payment API

Assumptions

Yes, but…

OK? Then, contact, ignition.

Understanding the Data Flow

The icon represents your server.

Building a JSON Web Token

[adapted from http://openid.net/specs/draft-jones-json-web-token-07.html 3.1 Examples JWT]

  1. The following example JWT Header declares that the encoded object is a JSON Web Token (JWT) and the JWT is signed using the HMAC SHA-256 algorithm:
    {"typ":"JWT","alg":"HS256"}
    Base64url encoding the bytes of the UTF-8 representation of the JWT Header yields this Encoded JWS Header value:
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
  2. The following is an example of a JWT Claims Set (used for Moz Payment):
    {
    "iss": "11111111-2222-3333-9999-deadbeef4321",
    "aud": "marketplace.firefox.com",
    "typ": "mozilla/payments/pay/v1",
    "iat": 1366388341,
    "exp": 1682612341,
    "request": {
      "id": "aaaaaaaa-1111-bbbb-2222-cccccccccccc",
      "pricePoint":1,
      "name": "MagicMystic",
      "description": "Adventure In the Middle of Nowhere",
      "productData": "abracadabra-12345",
      "postbackURL": "https://www.jaxo.com/magicmystic&agree=YES",
      "chargebackURL": "https://www.jaxo.com/magicmystic&agree=NO"
      }
    }
    
    Base64url encoding the bytes of the UTF-8 representation of the JSON Claims Set yields this Encoded JWS Payload value
    ewoiaXNzIjogIjExMTExMTExLTIyMjItMzMzMy05OTk5LWRlYWRiZWVmNDMyMSIsCiJhdWQiOiAibWFya2V0cGxhY2UuZmlyZW
    ZveC5jb20iLAoidHlwIjogIm1vemlsbGEvcGF5bWVudHMvcGF5L3YxIiwKImlhdCI6IDEzNjYzODgzNDEsCiJleHAiOiAxNjgy
    NjEyMzQxLAoicmVxdWVzdCI6IHsKICAiaWQiOiAiYWFhYWFhYWEtMTExMS1iYmJiLTIyMjItY2NjY2NjY2NjY2NjIiwKICAicH
    JpY2VQb2ludCI6MSwKICAibmFtZSI6ICJNYWdpY015c3RpYyIsCiAgImRlc2NyaXB0aW9uIjogIkFkdmVudHVyZSBJbiB0aGUg
    TWlkZGxlIG9mIE5vd2hlcmUiLAogICJwcm9kdWN0RGF0YSI6ICJhYnJhY2FkYWJyYS0xMjM0NSIsCiAgInBvc3RiYWNrVVJMIj
    ogImh0dHBzOi8vd3d3LmpheG8uY29tL21hZ2ljbXlzdGljJmFncmVlPVlFUyIsCiAgImNoYXJnZWJhY2tVUkwiOiAiaHR0cHM6
    Ly93d3cuamF4by5jb20vbWFnaWNteXN0aWMmYWdyZWU9Tk8iCiAgfQp9
    
  3. Concatenating the Encoded JWS Header, one period character, then the Encoded JWS Payload yields
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    .
    ewoiaXNzIjogIjExMTExMTExLTIyMjItMzMzMy05OTk5LWRlYWRiZWVmNDMyMSIsCiJhdWQiOiAibWFya2V0cGxhY2UuZmlyZW
    ZveC5jb20iLAoidHlwIjogIm1vemlsbGEvcGF5bWVudHMvcGF5L3YxIiwKImlhdCI6IDEzNjYzODgzNDEsCiJleHAiOiAxNjgy
    NjEyMzQxLAoicmVxdWVzdCI6IHsKICAiaWQiOiAiYWFhYWFhYWEtMTExMS1iYmJiLTIyMjItY2NjY2NjY2NjY2NjIiwKICAicH
    JpY2VQb2ludCI6MSwKICAibmFtZSI6ICJNYWdpY015c3RpYyIsCiAgImRlc2NyaXB0aW9uIjogIkFkdmVudHVyZSBJbiB0aGUg
    TWlkZGxlIG9mIE5vd2hlcmUiLAogICJwcm9kdWN0RGF0YSI6ICJhYnJhY2FkYWJyYS0xMjM0NSIsCiAgInBvc3RiYWNrVVJMIj
    ogImh0dHBzOi8vd3d3LmpheG8uY29tL21hZ2ljbXlzdGljJmFncmVlPVlFUyIsCiAgImNoYXJnZWJhY2tVUkwiOiAiaHR0cHM6
    Ly93d3cuamF4by5jb20vbWFnaWNteXN0aWMmYWdyZWU9Tk8iCiAgfQp9
    
  4. Signing the above value — referred later as the Base — with the HMAC SHA-256 algorithm and your pay secret:
    123456789abcdef123456789abcdef123456789abcdef123456789abcdef123456789abcdef123456789abcdef123456
    
    The pay secret is ... a secret! You should no one but you knows about its value
    and Base64url encoding the resulting signature yields this Encoded JWS Signature:
    gnYt9nrAfn_XkTtTtBCMQ2XoMI7dEeHv8tpjUx8jWb8
    
  5. Concatenating the Encoded JWS Header, one period character, the Encoded JWS Payload, one period character, then the Encoded JWS Signature yields to the final JSON Web Token:
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    .
    ewoiaXNzIjogIjExMTExMTExLTIyMjItMzMzMy05OTk5LWRlYWRiZWVmNDMyMSIsCiJhdWQiOiAibWFya2V0cGxhY2UuZmlyZW
    ZveC5jb20iLAoidHlwIjogIm1vemlsbGEvcGF5bWVudHMvcGF5L3YxIiwKImlhdCI6IDEzNjYzODgzNDEsCiJleHAiOiAxNjgy
    NjEyMzQxLAoicmVxdWVzdCI6IHsKICAiaWQiOiAiYWFhYWFhYWEtMTExMS1iYmJiLTIyMjItY2NjY2NjY2NjY2NjIiwKICAicH
    JpY2VQb2ludCI6MSwKICAibmFtZSI6ICJNYWdpY015c3RpYyIsCiAgImRlc2NyaXB0aW9uIjogIkFkdmVudHVyZSBJbiB0aGUg
    TWlkZGxlIG9mIE5vd2hlcmUiLAogICJwcm9kdWN0RGF0YSI6ICJhYnJhY2FkYWJyYS0xMjM0NSIsCiAgInBvc3RiYWNrVVJMIj
    ogImh0dHBzOi8vd3d3LmpheG8uY29tL21hZ2ljbXlzdGljJmFncmVlPVlFUyIsCiAgImNoYXJnZWJhY2tVUkwiOiAiaHR0cHM6
    Ly93d3cuamF4by5jb20vbWFnaWNteXN0aWMmYWdyZWU9Tk8iCiAgfQp9
    .
    gnYt9nrAfn_XkTtTtBCMQ2XoMI7dEeHv8tpjUx8jWb8
    

Base64Url encoding? What's that?

A trap in which I fall, and you have some chances to do as well.

As stated by RFC 4648, Base64Url is not Base64:

When it comes onto the JavaScript side, it becomes even worst.  Reason is that JavaScript knows "glyphs" (character such as this one: ), while Base64/Base64URL just want to hear of "octets" ( is ONE character weighting SIX UTF octets).  UTF-8 is what you must use for JWT.

Don't think, even a second, you could use atob() or btoa().  You have been warned.

The solution for me was to write the proper Base64.Url encoder/decoder for both JavaScript and Java.
Cut and Paste, here they are.

Base64.js
Base64.java

and HMAC SHA-256?

If curious, take a look at the Wikipedia, but the only thing that matters for you is the equation:
Secret Key
+
Base
an arbitrary Block of Data
=>
Signature
a fixed-length array of bytes,
aka Hash

Fortunately, Java provides us with most well-known hash engines, so that the code drastically reduces to just:

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

class Foo {
   private static final String ENC = "UTF-8";
   private static final String ALGO = "HmacSHA256";

   static byte[] getSignature(String secretKey, String base) throws Exception {  
      Mac mac = Mac.getInstance(ALGO);
      mac.init(new SecretKeySpec(secretKey.getBytes(ENC), ALGO));
      return mac.doFinal(base.getBytes(ENC));
   }
}

JSON (JavaScript Object Notation)

It is required to understand JSON on the server side, because that is the mean the Mozilla MarketPlace informs about the result of the payment transaction.

The "JWT Claim" is also formulated in JSON, but it is just easy in Java to assemble the appropriate String perhaps with the help of StringBuilder, StringBuffer.

"To read JSON" means a JSON Parser, and "Server side" means — in my case — that it is written in Java.   JSON (RFC 4627) is not a complex format.   JSON is well implemented in JavaScript, for which it was designed.  What about Java?

At the beginning, I thought it should be easy to find a couple of hundred lines of robust and elementary Java code to do the work, a monolithic and small Java program, no jars, no library dependencies, no useless rings and bells: my goal is just to extract the value of the productData field as returned by the MarketPlace. Why would I need a steam-machine for this job?  These lines, for instance:

public static String getProductData(String notice, String name) {
   String key = "\"productData\"";
   int start = notice.indexOf(key);
   if (start >= 0) {
      start = notice.indexOf('"', start+key.length()+1) + 1;
      return notice.substring(start, notice.indexOf('"', start));
   }else {
      return null;
   }
}
I found nothing that truly satisfied my needs.  Parsing is one of my hobby, and I decided to spend a day implementing RFC 4627 from scratch.

Json.java is (yet another) JSON decoder for Java. It is strictly conforming to RFC 4627, does not reinvent the wheel, reusing most known, standard Java classes (List, Object, String, Boolean.) Although that wasn't absolutely required, it also has a JSON generator.

Dealing with Mozilla MarketPlace on the Server Side

Jwt.java encapsulates what is needed for the encoding of the data during the exchanges with the Mozilla MarketPlace:

Note that makePurchaseOrder takes only one URL, used for both granted or denied payment (postback and chargeback). To ease the coding, a query parameter simply differentiates the granted callback URL from the denied one (agree=YES or agree=NO.)

We now are almost ready to enter the JWT dance.

a) A device (client) sends to our HttpServlet a purchase request

GET www.jaxo.com/foobar?OP=purchase
  1. Create a pay Entity with two properties: the state, initialized to pending, and a date, set to the actual UNIX time
    Entity pay = new Entity("Pay");
    pay.setProperty("state", "pending");
    pay.setProperty("created", new Date());
    
  2. Store the Entity in the application storage, and get the unique key to which it is associated
    DatastoreServiceFactory.getDatastoreService().put(pay);
    String paykey = KeyFactory.keyToString(pay.getKey());
    
    Here, for the sake of showing a working example, we make use of the Google App Engine Datastore Service, that is, com.google.appengine.api.datastore.Entity, Key, etc. For other kind of servers, you would use the most appropriate per-app-storage.
     
  3. Call Jwt.makePurchaseOrder.  The first argument (paykey) uniquely identifies our user.  The second argument (url) is where, in a later step, Mozilla MarketPlace will call us back when the transaction has ended.
    String jwt = Jwt.makePurchaseOrder(
       paykey,
       getBaseUrl(request) + "/foobar?OP=payment&agree="  // YES or NO
    );
    
  4. Return the signed JWT to the device. 
    response.getWriter().print(jwt);
    
  5. JavaScript, on the phone device calls navigator.mozPay passing the signed JWT as an argument…

b) The Mozilla MarketPlace calls our HttpServlet back to tell the result of the transaction

GET www.jaxo.com/foobar?OP=payment&agree=YES      if the buyer agreed to pay
GET www.jaxo.com/foobar?OP=payment&agree=NO      if the buyer changed her/his mind
  1. Call Jwt.getPaymentNotice to insure that the callback is really coming from the Mozilla MarketPlace, and to get the notice in readable format.
    String notice = Jwt.getPaymentNotice(request.getParameter("notice"));
    
  2. Using our Json class, extract the field productData from the notice field: this is the key to our Datastore pay Entity; retrieve the relevant pay Entity.  Also, extract the transactionID from the response field: we need it to answer to the MarketPlace request.
    Entity pay = null;
    String transactionID = null;
    for (Json.Member m1 : ((Json.Object)Json.parse(notice)).members) {
       if (m1.getKey().equals("request")) {
          for (Json.Member m2 : ((Json.Object)m1).members) {
              if (m2.getKey().equals("productData")) {
                 Key paykey = KeyFactory.stringToKey((String)m2.getValue());
                 pay = DatastoreServiceFactory.getDatastoreService().get(paykey);
                 break;
             }
          }
       }else if (m1.getKey().equals("response")) {
          for (Json.Member m2 : ((Json.Object)m1).members) {
             if (m2.getKey().equals("transactionID")) {
                transactionID = (String)m2.getValue();
                break;
             }
          }
       }
    }
    
  3. Store the MarketPlace answer into the server application storage, acknowledging the user's choice.  We assume that pay is not null.
    pay.setProperty(
       "notice",
       new com.google.appengine.api.datastore.Text(notice)
    );
    if (req.getParameter("agree").equals("YES")) {
       pay.setProperty("state", "granted");
    }else {
       pay.setProperty("state", "denied");
       // should we store the reason ???
    }
    store.put(pay);
    
  4. Finally, the Mozilla MarketPlace must know that we were aware of its response, whatever granted or denied. We assume that transactionID is not null.
    response.getWriter().print(transactionID);
    

c) Then, what?

Recalling the Data Flow Diagram, the current situation is pictured as follows:

where the blue area represents the delay required for the buyer and the payment provider to terminate the transaction.  There can be no clues on how long it will take: 10 seconds, an hour, or ... never ending!

The device being a "Client", there are no easy means to wake it up when the payment has been received.  So it has to be the other way: the device queries regularly the Server about the state or the transaction. This is named polling: the device issues several HTTP requests getPayment to the Server, until the Server finishes by answering "granted" or "denied".

GET www.jaxo.com/foobar?OP=getPayment&PYK=agpz… The query parameter PYK is the pay key, created above at a) 2..  We will examine later how the Client knows about it.
  1. Read the paykey from the request, initialize the server-side polling loop.
    DatastoreService store = DatastoreServiceFactory.getDatastoreService();
    Key paykey = KeyFactory.stringToKey(req.getParameter("PYK"));
    Entity pay = null;
    long created = 0;
    String state = "unknown";
    
  2. Do the server-side polling loop.  The logic is to retrieve each second and 40 times at most the pay entity from the DataStore, until the state property changes from pending [see a) 1.] to either denied or granted [see b) 3.] 
    for (int i=0; i < 40; ++i) {
       pay = store.get(paykey);
       state = pay.getProperty("state").toString();
       if (state.equals("granted")) {
          break;
       }else if (state.equals("denied")) {
          store.delete(paykey);
          break;
       }else {
          try { Thread.sleep(1000); }catch (InterruptedException e1) {}
       }
    }
    
    Why 'only' 40 times?  Because Servers do not like request that appear not responsive — what sleep does.  For example, the Google App Engine raises a DeadlineExceededException exception after about 60 seconds spent in the request.  But that does NOT mean that the game is over after 40 seconds! After returning, if the device (client) finds that the transaction is not concluded (state is neither granted, nor denied), it can later re-issue another getPayment, or have a more elaborate strategy.
     
  3. Return with the result as a JSON object
    if (pay != null) { // defense!
       created = ((Date)pay.getProperty("created")).getTime();
    }
    String payment = "{\"state\":\"" + state + "\",\"date\":\"" + created + "\"}"
    response.getWriter().print(payment);
    

On the Client side (Firefox OS Device)

The localStorage is a global object that maintains a persistent storage area on the device.  This is the mechanism used to store the state of a purchase request — if any — as well as its paykey after the purchase request has been sent.

Now, you need a "Buy" button:

It has been defined somewhere in your main index.html like:

<BUTTON id="btnBuy" style="display:none"><IMG src="coins.png"/></BUTTON>
The JavaScript window.onload() checks the pay state, and if a payment is required, the "Buy" button is displayed while its onclick event triggers the purchase function,
 
if ((navigator.mozPay !== undefined) && (getPayState() !== "granted")) {
   var elt = document.getElementById("btnBuy");
   elt.style.display = "";
   elt.onclick = purchase;
}
the purchase function being:
function purchase() {
   var elt = document.getElementById("btnBuy");
   elt.style.display = "none";
   if (getPayState() === "pending") {
      getPayment(elt, getPayKey());
   }else {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", http://www.jaxo.com/foobar?OP=purchase");
      xhr.onreadystatechange = function() {
         if (this.readyState === 4) {
            if ((this.status === 200) || (this.status === 0)) {
               var jwt = this.responseText;
               /*
               | extract the productData from the returned JWT.
               | It is the stringized key (keyToString)
               | of the "Pay" kind entity in Google App Datastore
               */
               var ix = 1 + jwt.indexOf('.');
               var paykey = JSON.parse(
                  Base64.Url.decode(
                     jwt.substring(ix, jwt.indexOf('.', ix))
                  )
               ).request.productData;
               var req = navigator.mozPay([jwt]);
               req.onsuccess = function() { getPayment(elt, paykey); };
               req.onerror = function() {  // mozPay failed
                  alert("Pay process" + this.error.name);
                  elt.style.display = "";
               }
            }else {
               alert("Payment request: server error\nRC:" + this.status);
               elt.style.display = "";
            }
         }
      };
      xhr.send();
   }
}

getPayment() — described later — is the function checking if the transaction has been concluded.  Its relevant argument is the paykey.

Polling

getPayment() is in charge to poll the Server, checking for a pending transaction being concluded.  The Javascript code is as follows:

function getPayment(elt, paykey) {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", http://www.jaxo.com/foobar?OP=getPayment&PYK=" + paykey);
   xhr.onreadystatechange = function() {
      if (this.readyState === 4) {
         if ((this.status === 200) || (this.status === 0)) {
            var pay = JSON.parse(this.responseText);
            var msg;
            if (pay.state === "granted") {
               msg = "granted payment";
            }else {
               elt.style.display = "";    // show the "buy" button
               if (pay.state == "denied") {
                  msg = "payment denied");
               }else {
                  // assume pay.state is "pending"
                  if (getPayState() === "pending") { // for the 2nd time
                     // TODO: inform the payment is still pending
                     //       propose a way to cancel it
                     return;
                  }else {
                     msg = "pending payment";
                  }
               }
            }
            writePayment(paykey, pay);
            alert("info", msg);
         }else {
            alert("Payment response: Server error\nRC:" + this.status);
            elt.style.display = "";
         }
      }
   );
   xhr.send();
}

Recall — as seen in c) 3. — that each getPayment request to the Server may take as long as 40 seconds.  Thanks to XMLHttpRequest, the main thread of the Browser's Javascript Engine will not be stuck while waiting.  In fact, XMLHttpRequest.open has been called with the "asynchronous" argument set to its default value: "asynchronous=true".  That means that a gremlin has been assigned with the duty of watching the Server response, freeing the browser from this boring task.  Firefox still responds to user actions, and so is the device.

What to do if, after 40 seconds, the Server has still no acknowledgments from the Payment Provider? Being dependent of the type of Application, it's up to you to define the right strategy…  One solution might be to issue an alert, after a given number of retries, and propose to the user to abandon the transaction.  If the user confirms, you clean the local storage (or create an abandon state) and tell the Server to dismiss. 

●     ●     ●     ●     ●