Marketplace feature removal
The functionality described on this page no longer works — Firefox Marketplace has discontinued support for Android, Desktop, Tablets, and payments (and other related functionality). For more information, read the Future of Marketplace FAQ.
The navigator.mozPay API enables web pages to take payment for digital goods. This article explains how to use the navigator.mozPay API and Web Payment Provider services to process in-app payments.
The fxPay library offers a complete abstraction around mozPay with a more concise, developer friendly API. Consider using fxPay before mozPay.
Overview
Here is an overview of how you add in-app payments to your app and process a transaction:
- Log in to the Firefox Marketplace Developer Hub.
- Upload an app, set it up as paid/in-app, and generate an Application Key and an Application Secret.
- From your app, initiate a payment by signing a JWT request with your secret and calling
navigator.mozPay(...).
- This starts a payment flow in a new window
- The buyer logs in with their email address.
- The buyer enters their PIN.
- The buyer charges the purchase to their phone bill or credit card.
- Your app receives a JavaScript callback when the buyer closes the window.
- Your app server receives a signed POST request with a Mozilla transaction ID indicating that the purchase was completed.
- If the transaction succeeded, you receive money directly deposited to your bank account
The navigator.mozPay
API is currently only available on Firefox OS. You can test it with the Firefox OS Simulator.
Process an in-app payment: step by step
This section explains how set up an in-app payment for testing and production.
Obtain a payment key for testing
When you log into the Firefox Marketplace Developer Hub you can visit the In-App Payment Keys page to generate an Application Key and Application Secret for testing. This key will only allow you to simulate in-app payments but this is suitable for testing. You should try out some simulations before you submit your app for review to the Marketplace. Read on for instructions on how to simulate a payment.
Obtain a real payment key
When you submit your working app to the Firefox Marketplace Developer Hub you will be prompted to configure payments. Select the cost of your app (typically free in this case) then mark the option to accept in-app payments. After setting up your bank account details, visit the In-App Payments page to obtain an Application Key and Application Secret for making real payments.
Store the Application Secret securely on your app server in a private settings file or something like that.
Set up an application
Let's say you are building an adventure game web app and you want to offer a Magical Unicorn for purchase so that players can excel in the game. You want to charge $1.99 or €1.89 or whatever the user's preferred currency is. In the following sections you'll see how to set up a backend server and write frontend code to use navigator.mozPay
to sell products.
Set up your server to sign JWTs
A payment is initiated with a JSON Web Token (JWT) and it must be created server side, not client side. This is because the secret key used for signing should never be publicly accessible. Continuing with the example of selling a Magical Unicorn for an adventure game, create a URL on your server like /sign-jwt
. This should create a JSON object that defines the product name, the price point, etc. Consult the Web Payment API spec for complete documentation on the JWT format. Here is an example:
{ "iss": APPLICATION_KEY, "aud": "marketplace.firefox.com", "typ": "mozilla/payments/pay/v1", "iat": 1337357297, "exp": 1337360897, "request": { "id": "915c07fc-87df-46e5-9513-45cb6e504e39", "pricePoint": 10, "name": "Magical Unicorn", "description": "Adventure Game item", "icons": { "64": "https://yourapp.com/img/icon-64.png", "128": "https://yourapp.com/img/icon-128.png" }, "productData": "user_id=1234&my_session_id=XYZ", "postbackURL": "https://yourapp.com/payments/postback", "chargebackURL": "https://yourapp.com/payments/chargeback", "defaultLocale": "en", "locales": { "de": { "name": "Magisches Einhorn", "description": "Adventure Game Artikel" } } } }
Here is an overview of each field:
JWT attribute | description |
---|---|
iss (issuer) |
Application Key that you obtained from the Firefox Marketplace Developer Hub. |
aud (audience) |
The payment provider you want to connect to. To use the production server, always set this to marketplace.firefox.com . |
iat (issued at time) |
A UTC Unix timestamp of when the JWT was issued. |
exp (expiration) |
A UTC Unix timestamp of when the JWT should expire. |
nbf (not before time) |
A UTC Unix timestamp of the earliest time the JWT can be processed. |
request.id |
A unique ID for the product you are selling. |
request.pricePoint |
A valid price point. This will be expanded to a price and currency at the time of purchase. |
request.productData |
Any optional string up to 255 characters. This allows you to reconcile application state when you receive a postback. |
request.postbackURL |
An absolute URL on your server that will receive postbacks. In a production environment, always use HTTPS URLs if possible. |
request.chargebackURL |
An absolute URL on your server that will receive chargebacks. In a production environment, always use HTTPS URLs if possible. |
request.icons |
An optional object describing icon URLs for the product you are selling. The keys are width/height pixel values (images must be square). Mozilla's Payment Provider will use your 64 pixel image on the payment confirmation page. If you don't specify a 64 pixel image, it will take the largest icon and resize it. The first time someone makes a payment they won't see the icon immediately because the Payment Provider is fetching it in the background; you should see it soon after. If you make changes to the icon, they could take up to 24 hours to appear. |
request.defaultLocale |
A language tag (RFC 4646) to describe the default localization of your in-app product name and description. It is optional unless locales is also defined, in which case it is required. |
request.locales |
An optional object containing one or more localized fields to describe your in-app product. Each locale entry is keyed on a language tag (RFC 4646) and contains the attributes you want to replace. You can only override name and description . If locales is defined then defaultLocale must also be defined. |
In Python code (using PyJWT), you could sign and encode the request dictionary shown above like this:
import jwt signed_request = jwt.encode(request_dict, application_secret, algorithm='HS256')
This code signs a JWT using the application secret and uses the HMAC SHA 256 algorithm. Currently, this is the only supported algorithm. When encoded, it will look something like this:
eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.IntcImF1ZFwiOiBcIm1hcmtldHBsYWNlLm1vemlsbGEub3JnXCIsIFwiaXNzXCI6IFwiQVBQLTEyM1wiLCBcInJlcXVlc3RcIjoge1wiY3VycmVuY3lcIjogXCJVU0RcIiwgXCJwcmljZVwiOiBcIjAuOTlcIiwgXCJuYW1lXCI6IFwiVmlydHVhbCAzRCBHbGFzc2VzXCIsIFwicHJvZHVjdGRhdGFcIjogXCJBQkMxMjNfREVGNDU2X0dISV83ODkuWFlaXCIsIFwiZGVzY3JpcHRpb25cIjogXCJWaXJ0dWFsIDNEIEdsYXNzZXNcIn0sIFwiZXhwXCI6IFwiMjAxMi0wMy0yMVQxMTowOTo1Ni43NTMxNDFcIiwgXCJpYXRcIjogXCIyMDEyLTAzLTIxVDEwOjA5OjU2LjgxMDQyMFwiLCBcInR5cFwiOiBcIm1vemlsbGEvcGF5bWVudHMvcGF5L3YxXCJ9Ig.vl4E31_5H3t5H_mM8XA69DqypCqdACVKFy3kXz9EmTI
The encoded/signed JWT can now be used by your app in its client code.
Set up a purchase button
Now that you have a backend to produce a JWT for your product, here's an example of writing frontend code with navigator.mozPay
. You would make a button somewhere in your app that lets players purchase the product. For example:
<button id="purchase">Purchase Magical Unicorn</button>
When the purchase button is clicked, your app should sign a JSON Web Token (JWT) and call navigator.mozPay
. Here is an example using jQuery:
$('#purchase button').click(function() { // The purchase is now pending... $.post('/sign-jwt', {}) .done(function(signedJWT) { var request = navigator.mozPay([signedJWT]); request.onsuccess = function() { waitForPostback(); }; request.onerror = function() { console.log('navigator.mozPay() error: ' + this.error.name); } }) .fail(function() { console.error('Ajax post to /sign-jwt failed'); }); }); function waitForPostback() { // Poll your server until you receive a postback with a JWT. // If the JWT signature is valid then you can dispurse the Magical Unicorn // product to your customer. // For bonus points, use Web Sockets :) }
This code would make an Ajax request to a /sign-jwt
URL on your own server. That URL would sign a JSON blob with product/price info and return a JWT in plain text. The Ajax handler would pass that JWT into navigator.mozPay
and then wait until the Payment Provider POSTs a purchase confirmation to your server. If the signature on the posted JWT is valid you can deliver the virtual goods to your customer.
Processing postbacks on the server
Before delivering your product, you need to wait for a purchase confirmation from the Marketplace; this is called a postback. The marketplace.firefox.com
site sends a POST confirmation notice (a JWT) to the request.postbackURL
specified in the original payment request. The postbackURL must be the URL for a server listening on either port 80 or port 443.
The POST has a Content-Type
of application/x-www-form-urlencoded
and the JWT can be found in the notice
parameter. In your server framework you'll probably access this with something like request.POST['notice'].
This JWT notice contains all the payment request fields plus a transaction ID, and is signed with your application secret that was obtained from the Firefox Marketplace Developer Hub. You can fulfill the purchase when you receive a postback and validate the signature. If you get a JWT whose signature you cannot verify you should ignore it since it probably wasn't sent by the marketplace.
The Web Payment API spec explains what postbacks look like in detail. The postback contains the original request and adds a new response parameter that contains a Mozilla specific transaction ID. Here is an example:
{ "iss": "marketplace.firefox.com", "aud": APPLICATION_KEY, "typ": "mozilla/payments/pay/postback/v1", "exp": 1337370900, "iat": 1337360900, "request": { "id": "915c07fc-87df-46e5-9513-45cb6e504e39", "pricePoint": 10, "name": "Magical Unicorn", "description": "Adventure Game item", "icons": { "64": "https://yourapp.com/img/icon-64.png", "128": "https://yourapp.com/img/icon-128.png" }, "productData": "user_id=1234&my_session_id=XYZ", "postbackURL": "https://yourapp.com/payments/postback", "chargebackURL": "https://yourapp.com/payments/chargeback", "defaultLocale": "en", "locales": { "de": { "name": "Magisches Einhorn", "description": "Adventure Game Artikel" } } }, "response": { "transactionID": "ABCD84294ec6-7352-4dc7-90fd-3d3dd36377e9", "price": {"amount": "0.99", "currency": "CAD"} } }
Description of the postback object:
JWT attribute | description |
---|---|
iss (issuer) |
Marketplace notices are issued by marketplace.firefox.com . |
aud (audience) |
Your application is the audience for the notice so this value will be your Application Key. |
iat (issued at time) |
A UTC Unix timestamp of when the JWT was issued. |
exp (expiration) |
A UTC Unix timestamp of when the JWT should expire. |
nbf (not before time) |
A UTC Unix timestamp of the earliest time the JWT can be processed. |
request |
The request object is exactly the same as the one you defined when creating the purchase JWT. |
response.transactionID |
A transaction identifier specific to Mozilla's Web Payment Provider. |
response.price |
An object describing the amount and currency that the customer actually used to make the purchase. |
Here are some important notes about the postback:
- The JWT is signed with your Application Secret
- Your postback/chargeback URLs must be located either on port 80 or port 443 of your web server. No other ports can be used. This is due to how Mozilla's firewall is configured.
Responding to postbacks
Your application must respond to the postback with a plain text HTTP response including just the transaction ID. For example:
HTTP/1.1 200 OK Content-Type: text/plain ABCD84294ec6-7352-4dc7-90fd-3d3dd36377e9
Processing chargebacks on the server
Marketplace will send you a chargeback notice (a POSTed JWT) if something goes wrong while processing the transaction, such as insufficient funds in the buyer's account. Chargebacks will be delivered to the app just like postbacks but they might arrive later on. The POST has a Content-Type
of application/x-www-form-urlencoded
and the JWT can be found in the notice
parameter. The chargeback must be the URL for a server listening on either port 80 or port 443.
Here is an example of what a decoded chargeback notice might look like:
{ "iss": "marketplace.firefox.com", "aud": APPLICATION_KEY, "typ": "mozilla/payments/pay/chargeback/v1", "exp": 1337370900, "iat": 1337360900, "request": { "id": "915c07fc-87df-46e5-9513-45cb6e504e39", "pricePoint": 10, "name": "Magical Unicorn", "description": "Adventure Game item", "icons": { "64": "https://yourapp.com/img/icon-64.png", "128": "https://yourapp.com/img/icon-128.png" }, "productData": "user_id=1234&my_session_id=XYZ", "postbackURL": "https://yourapp.com/payments/postback", "chargebackURL": "https://yourapp.com/payments/chargeback", "defaultLocale": "en", "locales": { "de": { "name": "Magisches Einhorn", "description": "Adventure Game Artikel" } } }, "response": { "transactionID": "ABCD84294ec6-7352-4dc7-90fd-3d3dd36377e9", "reason": "refund" } }
Description of the chargeback object:
JWT attribute | description |
---|---|
iss (issuer) |
Marketplace notices are issued by marketplace.firefox.com . |
aud (audience) |
Your application is the audience for the notice so this value will be your Application Key. |
iat (issued at time) |
A UTC Unix timestamp of when the JWT was issued. |
exp (expiration) |
A UTC Unix timestamp of when the JWT should expire. |
nbf (not before time) |
A UTC Unix timestamp of the earliest time the JWT can be processed. |
request |
The request object is exactly the same as the one you defined when creating the purchase JWT. |
response.transactionID |
A transaction identifier specific to Mozilla's Web Payment Provider. |
response.reason |
An object that describes the chargeback reason. This value will either be set to refund or reversal.
|
NOTE: Refunds are not yet supported for in-app payments.
Your application must respond to the chargeback with a plain text HTTP response containing just the transaction ID. For example:
HTTP/1.1 200 OK Content-Type: text/plain ABCD84294ec6-7352-4dc7-90fd-3d3dd36377e9
Postback/chargeback errors
If an application server responds to the HTTP request with a non-successful status code then Mozilla's Web Payment Provider will retry the URL several times. If it still doesn't receive a successful response, the app developer will be notified and the app may be temporarily disabled. If the application server does not respond to the postback or chargeback with the transaction ID then it is handled like an error and will be retried, etc. If you are not seeing requests to your chargeback or postback URL's, make sure that your serve is listening on port 80 or 443. Mozilla's IAP server will not contact you otherwise.
Use HTTPS postback/chargeback URLs
When running your app in a production environment try to use secure HTTPS URLs if you have the means to do so. This will protect the postback data from being read by a third party when it transits from a Mozilla server to your app server. Using HTTPS is not mandatory to protect the integrity of the payment request, a JWT signature accomplishes that.
Warning: If you do not use secure HTTPS postback URLs, ensure your payment request does not contain personally identifiable information in case it is intercepted by a third party. For example, make sure your productData value does not reveal any sensitive user data. Mozilla never includes personally identifiable information in a payment request by default.
Postback/chargeback IPs
If you are correctly checking the JWT signature as decribed above then there is no need to whitelist IPs of the Firefox Marketplace servers that will send you postback/chargeback notices. However, if you wish to add an extra layer of protection (e.g. against key theft), the Marketplace will send you postback/chargeback notices from the following IP address(es). Any changes to these IP addresses will be announced on the dev-marketplace mailing list.
63.245.216.100
Simulating payments
The intro mentioned how you can obtain a special application key and application secret from the Firefox Marketplace Developer Hub to simulate in-app payments while you are developing and testing your app. Use this secret to sign a custom JWT that looks like this:
{ "iss": APPLICATION_KEY, "aud": "marketplace.firefox.com", "typ": "mozilla/payments/pay/v1", "iat": 1337357297, "exp": 1337360897, "request": { "id": "915c07fc-87df-46e5-9513-45cb6e504e39", "pricePoint": 10, "name": "Magical Unicorn", "description": "Adventure Game item", "icons": { "64": "https://yourapp.com/img/icon-64.png", "128": "https://yourapp.com/img/icon-128.png" }, "productData": "user_id=1234&my_session_id=XYZ", "postbackURL": "https://yourapp.com/payments/postback", "chargebackURL": "https://yourapp.com/payments/chargeback", "simulate": { "result": "postback" } } }
JWT attribute | description |
---|---|
request.simulate |
An object to describe what to simulate. Possible results:
|
The additional request.simulate
attribute tells the payment provider to simulate some result without charging any money. The user interface will not ask for a login or a PIN number either. You can use this while developing your app to make sure your buy button is hooked up correctly to navigator.mozPay
and your server postback and chargeback URLs are functioning correctly.
Here is an example that will simulate a successful purchase and send a signed notification to your postback URL:
{ ... "request": { ... "simulate": { "result": "postback" } } }
Here is how to simulate a chargeback refund:
{ ... "request": { ... "simulate": { "result": "chargeback", "reason": "refund" } } }
A JWT notice is posted to your handler just like for a real purchase except you'll receive a randomly generated transactionID. It is okay to use non-HTTPS URLs for simulations.
When you simulate a chargeback, keep in mind that the mozPay onsuccess()
callback will be invoked since a chargeback is not an operational error.
Note: Simulated payment JWTs should not be used in production because your customers would be able to obtain free products.
Debugging errors
If you don't use the in-app payment API correctly, the payment screen will show an error that aims to help the user figure out what to do. The payment screen will also include an error code which aims to help you as the developer figure out what to do. You can use Mozilla's Error Legend API to decipher error codes in your own language. For example, the error code INVALID_JWT
means the JWT signature is invalid or the JWT is malformed.
Protect the application secret
Warning: Ensure that no one else can read your Application Secret. Never expose it to the client.
Revoking a compromised application secret
In the rare chance that your application secret leaks out or becomes compromised, you need to revoke it as soon as possible. Here's how:
- Log in to the Firefox Marketplace.
- Navigate to My Submissions and locate your app.
- Navigate to the Manage In-App Payments page, which is the same place where you generated your credentials.
- Click the Reset Credentials button.
After resetting your credentials, no one will be able to process payments with the old credentials. You will see a new application key and application secret that you can begin using immediately to continue processing payments in your app.
If you need to report any other security issues, please file a bug in the Payments/Refunds component.
Code libraries
Here are libraries specific to Mozilla's navigator.mozPay
:
To find a secure, up to date JSON Web Token (JWT) library for your favorite language, see jwt.io.
Sample code
- Here is the source to Web Fighter, a game that implements in-app payments in NodeJS. You can install it from the Marketplace here.
- Here is a diagnostics and testing app that shows how to sign JWT requests and write postback and chargeback verifier code in Python: In-app Payment Tester
Getting help
- You can discuss in-app payment related issues on the dev-webapps mailing list or in the #payments channel on irc.mozilla.org.
- You can file a Marketplace bug in the Payments/Refunds component if you find a bug