In-app payments Redirect 1

The navigator.mozPay API allows web content to initiate a payment for digital goods and receive a confirmation from a payment provider of the purchase. Mozilla implements a Web Payment Provider for this API that developers can use to collect payment for their goods. This article explains how to use these services to create in-app payments.

In-app payments overview

Here is a quick overview of how in-app payments work:

  • Log in to the Firefox Marketplace Developer Hub.
  • Upload an app, set up payments, 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 special window
    • The buyer logs in with Mozilla Persona (if not logged in.)
    • The buyer enters their PIN.
    • The buyer charges the purchase to her 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 successfully
  • 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 App Manager.

Setting up an in-app payment: step by step

This section explains in detail to how set up an in-app payment, both a test version, and the real thing when you are ready to submit your app.

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 secret key for testing. This key will only allow you to simulate in-app payments but this is perfect 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 (most apps with in-app payments are free, but not all) then mark the option to accept in-app payments. After setting up your bank account details, visit the Manage 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.

Important: Ensure that no one else can read your application secret. Never expose it to the client.

Set up an application

Let's say you are building an adventure game as an open 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"
      }
    }
  }
}

The Web Payment API spec explains the format in detail. Here are a few important notes:

  • The iss (issuer) corresponds to the application key that you obtained from the Firefox Marketplace Developer Hub.
  • The aud (audience) should always be set to marketplace.firefox.com.
  • The request.id should always be unique for each one of your products.
  • The request.pricePoint will be expanded to a price and currency at the time of purchase. You must set this to a valid price point.
  • You can put any string up to 255 characters in request.productData. This allows you to reconcile application state when you receive a postback.
  • The request.postbackURL and request.chargebackURL must point to valid, absolute URLs on your server. In a production environment, you should always use HTTPS URLs if possible.
  • The request.icons object (optional) is a map of 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.
  • The request.defaultLocale describes what language the in-app product is in. It is optional unless locales is also defined, in which case it is required.
  • The request.locales object (optional) is an map of one or more locale-specific overrides of the data contained in the in-app product, which UIs use to provide localized views based on the accessing device's locale. For example, if you have Italian readers installing your app, you probably want to give them Italian UI text. Each locale entry is keyed on a language tag (RFC 4646) and contains the keys 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. 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 POST has a Content-Type of application/x-www-form-urlencoded and the JWT can be found in the notice parameter. 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": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9",
    "price": {"amount": "0.99", "currency": "CAD"}
  }
}

Here are some important notes about the postback:

  • Your original payment request will always be included
  • The iss (issuer) will always be marketplace.firefox.com
  • The aud (audience) is set to your application key
  • The JWT is signed with your application secret
  • The response.transactionID is specific to Mozilla's Web Payment Provider
  • The response.price is the amount and currency that the customer actually used

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

webpay:84294ec6-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. 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": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9",
    "reason": "refund"
  }
}

The JWT is similar to the postback and is defined in detail at the Web Payment API spec. Here are some important notes:

  • Your original payment request will always be included
  • The iss (issuer) will always be marketplace.firefox.com
  • The aud (audience) is set to your application key
  • The JWT is signed with your application secret
  • The response.transactionID is specific to Mozilla's Web Payment Provider
  • The response.reason will either be set to refund or reversal
    • A refund means the payment was refunded either upon request of the customer or by an administrator.
    • A reversal is when a buyer asks the credit card issuer to reverse a transaction after it has been completed. The buyer might do this through the credit card company as part of a dispute.

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

webpay:84294ec6-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.

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"
    }
  }
}

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.

Note: Simulated payment JWTs should not be used in production. Your customers will 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:

  1. Log in to the Firefox Marketplace.
  2. Navigate to My Submissions and locate your app.
  3. Navigate to the Manage In-App Payments page, which is the same place where you generated your credentials.
  4. 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:

Here are some generic JSON Web Token (JWT) libraries for encoding/decoding and signature verification:

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

Similar payment systems

Document Tags and Contributors

Contributors to this page: Sheppy
Last updated by: Sheppy,