At Mozilla, we have been working on bringing payments to the web. We think it is important that developers have an easy way to monetize their apps. It also gives us the chance to offer deeper platform integration, allowing things like carrier billing in addition to credit/debit cards.

We’ve talked about in-app payments before in navigator.mozPay() For Web Payments, but this is an overview from an app developer’s standpoint. This also includes a few updates, such as real payments being live in the Firefox Marketplace. Payments are different than simply being a paid app, which charges the user once on the initial purchase. In-app payments is a different workflow, and allows you to charge the customer for specific items within your app.

Architecture Overview

Payments are only enabled on Firefox OS devices for now. We are working on enabling them across other platforms, even desktop Firefox, but that is a ways off. For the rest of this article, we will assume the Firefox OS environment.

The MDN page for in-app payments has a good overview of the process, but here’s an even quicker overview. You need a server to generate the payments requests, because otherwise a hacker could easily change the prices to 0. The server creates JWT tokens, passes them to your app, and your app calls navigator.mozPay with the token. The user will be taken to a built-in payments workflow, and when finished, your server will be notified. The previous Hacks article on payments goes into the lower-level mechanics and reasons for payments more thoroughly.

It’s up to you to handle the rest of the workflow: updating the app when an item is successfully purchased, linking purchases to accounts, etc.

Getting a Simulation Key

In order to use payments, you’ll need use the app key and secret generated for you by the Marketplace. If you just want to simulate payments, you can easily grab a testing key and secret. The only thing you need to do is create an account if you haven’t done so already.

You will only be able to simulate payments with that key. When you are ready to use real money, you need to submit your app to the Marketplace, setup payments, and obtain a real key and secret. All of this is explained in more detail below.

Setting up the Server-Side Code

The first thing you need to do is set up the server-side code. A “mozpay” module exists in a few different languages which implements the payment workflow for you. If it does not exist in your programming language, you’ll need to use a JWT module and do some more legwork. Check the rather simple code from mozpay-js for an example of what you need to do.

I wrote a game for Firefox OS called Webfighter which uses node.js as the backend, so I used mozpay-js on the server-side. Webfighter is a shooter with a store where you can buy more weapons and ships, and it’s a good reference for developers to see actual code for working with payments. Examples below are taken from Webfighter. You can check out Webfighter’s source here.

First, require and configure the mozpay module:

var pay = require('mozpay'); var settings = require('settings'); pay.configure({ mozPayKey: settings.payKey, mozPaySecret: settings.paySecret, mozPayAudience: 'marketplace.firefox.com', mozPayType: 'mozilla/payments/pay/v1', mozPayRoutePrefix: '/mozpay' });

The settings module is just a file with JSON that specifies configuration of the app. It’s nice to separate the configuration so that you can easily swap it out in different environments, like development and production.

Use the key and secret that you got from the Marketplace. You should only change the audience and type options if you are using a different payment server than Mozilla’s. The routePrefix is optional and specifies the URL prefix for postbacks from the payment server.

Next you need to define a route that creates the data used for the payment transaction. The client is the one making the actual payments request (so that you don’t have to handle credit card details, and it’s possible to even be charged through carrier billing). You just need to create an object that represents a purchase. This example uses express.js as the http server.

var purchaseQueue = {}; app.post('/sign-jwt', function(req, res) { var token = 'o' + Date.now(); var item = getItem(req.body.name); var jwt = pay.request({ id: item.name, name: item.name, description: item.description, icons: { '64': settings.url + item.icon }, pricePoint: item.price, productData: token, postbackURL: settings.url + 'mozpay/postback', chargebackURL: settings.url + 'mozpay/chargeback', simulate: { result: 'postback' } }); // Keep track of which JWT objects we are waiting on purchaseQueue[token] = 'processing'; res.send(JSON.stringify({ jwt: jwt, token: token }); });

Notice the settings object again; it’s just a dict that has some configuration properties about our app. settings.url is the base URL where our app is hosted, so that all of the passed URLs are absolute.

We call getItem to get the details about an item (which we don’t define here). I found it easiest to define a JSON file containing info about all the available items. You can see the JSON for available items in Webfighter on GitHub.

There are several fields for a purchase request. id is just a unique identifer, and we simply use the product name. Other fields describe the product, such as icons which lists icons of various sizes. The pricePoint is a number that maps to a specific price for each available region, and you should look at the price point table to find the one you need. For example, a price point of 10 indicates a price of $0.99 in the US.

The simulate parameter tells the payments system to just simulate the request. If it’s passed, no real charges are made and you will be taken through a simulated workflow. This is useful for development. Just remember to remove this flag in production.

You can store anything you want to in productData , which you can later use when handling events. Here I generated an arbitrary token to track requests, and I store it in the global object purchaseQueue . We use this when we handle events.

Next, subscribe to the postback event from the mozpay module:

pay.on('postback', function(data) { // the payment was successful var req = data.request; purchaseQueue[req.productData] = 'success'; });

You can also subscribe to the chargeback event. Chargebacks happen when a customer disputes a charge and gets the money back. This could happen a month later, and is difficult to process. Webfighter keeps track of chargebacks, but the app does not check for them and revoke items. You would need to check periodically or on startup if the user still has access to all purchased items.

pay.on('chargeback', function(data) { var req = data.request; purchaseQueue[req.productData] = 'chargeback'; });

That’s it for the server-side.

Using mozPay On the Client Side

On the client-side, you need to post to /sign-swt and use mozPay to initiate the purchase. In the following code, we check if mozPay is available and if it’s not we just give the user the item. Remember, it’s just a test app! Once a purchase is initiated, we start polling the server until we get a successful purchase.

function buy(name) { // Purchase an item by requesting a JWT object from the // server, and posting it to the mozPay API $.post('/sign-jwt', { name: name }, function(res) { if(navigator.mozPay) { var req = navigator.mozPay([res.jwt]); req.onerror = function() { console.log('mozPay error: ' + this.error.name); clearPolling(); }; // Poll to see when the payment is complete startPolling(); } else { alert('in-app payments unavailable, so giving it to you for free'); onPurchase(name); } }); }

Here are the polling functions which wait until the server responds with a successful purchase. Note that we only check for a successful purchase. The user will be notified of any errors within the native payment screen, and when they close the screen an error event is fired on the mozPay request object, which we handle and stop the polling. See the above code with req.onerror .

function pollQueue(token, name) { // Poll the server every second to see the status of our // payment request $.get('/purchaseQueue?token=' + token, function(res) { if(res == 'success') { onPurchase(name); clearPolling(); } }); } var pollTimer; function startPolling() { pollTimer = setInterval(function() { pollQueue(res.token, name); }, 1000); } function clearPolling() { if(pollTimer) { clearInterval(pollTimer); } pollTimer = null; }

We call onPurchase when an item is successfully purchased and should be enabled for the user. You should create that function and write your app-specific code.

Real payments: submitting app to the Firefox Marketplace

When you are ready to run real payments, remove the simulate field when the server creates the payment request, and submit your app to the Firefox Marketplace. It needs to be submitted so that you can hook up real payments.

Get paid: configuring bank account details and payment options

Once your app is submitted, edit your app and click on “Compatibility & Payments” on the left side and select “Paid / In-App”. Under “Payment Accounts”, and add your bank account. One thing that tripped me up was needing the SWIFT code for my bank. In the future, Marketplace will hopefully autofill it, but for now you can look it up on the theswiftcodes.com.

Under “Price & Countries” you can set the price for your app. You may just want “Free with in-app payments”, which is what Webfighter is. Additionally, you can selectively choose which region your app is available in. Apps with payments are only available in regions with them enabled.

Use a real key & secret

Now click the “In-App Payments” link in the left sidebar. You’ll see a screen where you can grab a new key & secret for real payments. Use these in the configure call on the server. Never share your secret.

At this time of writing, carrier billing is only available in specific countries. Check out the Payments Status page for where it’s available. When it is not available, the user will be presented with a credit card screen.

Go make some money with your app!

We want you to be successful in participating in the app ecosystem and economy. Let us know what your experience is like! We are happy to answer any questions over on the marketplace mailing list.