In-app Payments with fxPay

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.

fxPay is a JavaScript library for web applications to process in-app payments and restore purchased products from receipts. It's a wrapper around the services offered by the Firefox Marketplace API and is an alternative to mozPay. Unlike mozPay, fxPay is a complete solution for working with in-app products.

Note: To accept in-app payments on as many platforms as possible, use fxPay instead of using mozPay directly.

fxpay support

The fxpay library aims to eventually work in all web runtimes but the following platforms are officially supported at the moment:

Packaged App Restrictions

If you want to accept payments with fxpay in a packaged app, there are a few restrictions:

  • Your manifest must declare an origin for receipt validation.
  • Your app must be privileged so that it can define an origin.
  • Your app can only run on Firefox OS 1.1 or greater because of origin support.

None of this applies to hosted apps.

Usage Guide

This section provides a full usage guide for fxpay.

Installation

To use fxpay, include a copy of the JavaScript library locally in your web application. Using the Bower package manager, this command would install the library into your app:

bower install fxpay

You could then link to the library in your app's HTML using a script tag:

<script src="bower_components/fxpay/lib/fxpay.js" type="text/javascript"></script>

Alternatively you can use Bower tools to load and/or compress your managed scripts.

Source Distribution

You can download the latest stable source via fxpay's Git repository

Using UMD or RequireJS

The fxpay JavaScript distribution is exposed using the Universal Module Definition (UMD) format and is also RequireJS compatible.

Set Up Your Products

Log into the Firefox Marketplace Developer Hub and upload your app following the submission process. On the Compatability & Payments page under Prices & Countries set in-app payments to Yes and save the change. Now open the In-App Payments page and click Configure In-App Products, which opens the In-App Products page where you can create and edit your in-app products. For more information on using this page, see In-App Products.

If you want to test out fxpay before setting up your products, skip to the Working with Fake Products section.

Fetching Products

Call fxpay.getProducts() to retrieve all of the active products you set up in the Developer Hub:

fxpay.getProducts()
  .then(function(products) {
    products.forEach(function(product) {
      console.log('product ID:', product.productId);
      console.log('product name:', product.name);
    });
  })
  .catch(function(error) {
    console.error('Error getting products: ' + error);
  });

The getProducts() promise is resolved with an array of product info objects and rejected with an error object. You can call this method to show a list of products so the user can purchase them.

Working with Fake Products

To test in-app purchases without first configuring products on the Firefox Marketplace Developer Hub, you can work with fake products. Set this somewhere in your app's initialization:

fxpay.configure({fakeProducts: true});

This changes fxpay.getProducts() to return pre-defined products that can only be purchased in simulation mode. The products will have fixed ID strings, names, and price points but this should help you set up your fulfillment code.

When you have submitted your finished app and fully configured your products, set fakeProducts to false and the same call to fxpay.getProducts() will retrieve your app's real products.

Purchasing A Product

You can call fxpay.purchase() to start the buy flow for an item. First, you'll probably want to make a screen where you offer some products for purchase using fxpay.getProducts(). Here's how to create a buy button that calls fxpay.purchase() when tapped:

fxpay.getProducts()
  .then(function(products) {
    products.forEach(function(product) {
      var button = document.createElement('button');
      button.textContent = 'Buy ' + product.name;
      button.addEventListener('click', function () {
        fxpay.purchase(product.productId)
          .then(function(purchasedProduct) {
            console.log('product purchased! ', 
                        purchasedProduct.productId);
            // It is now safe to deliver the product.
          })
          .catch(function(error) {
            console.error('error purchasing: ' + error);
          });
      });
      document.body.appendChild(button);
    });
  });

The purchase() promise resolves with a product info object and is rejected with an error object. The promise resolves only after the user completes the buy flow and the Marketplace server has verified the receipt so at this time it is safe to deliver the item.

How does this work? The fxpay.purchase() function automates the process of opening a payment window, completing the transaction, then waiting for and verifying an incoming JWT. If you want to know the specifics, see the mozPay in-app payments guide but that's not mandatory for using the fxpay library.

Restoring Products From Receipt

fxpay will discover any receipts already installed on the user's device. If a receipt is valid then the user has already purchased the product so you should make it available for use. You can check for receipts and validate them while fetching products like this:

fxpay.getProducts()
  .then(function(products) {
    products.forEach(function(product) {
      if (product.hasReceipt()) {
        product.validateReceipt()
          .then(function(restoredProduct) {
            console.log('restored product from receipt:',
                        restoredProduct.productId);
          })
          .catch(function(error) {
            console.error('Error validating receipt: ' + error);
          });
      } else {
        // Show a buy button for the product.
      }
    });
  });

Persisting Purchase Receipts

When fxpay.purchase() resolves, a receipt is installed on the user's device and backed up in the Firefox Marketplace database. FxPay does not offer a feature for restoring receipts on new devices yet but you could capture a receipt for later restoration like this:

fxpay.purchase(product.productId)
  .then(function(purchasedProduct) {
    console.log('purchase receipt:', 
                purchasedProduct.receiptInfo.receipt);
    // Ask the user to log in then save the base 64 
    // encoded receipt string to your server with 
    // their user ID.
  });

When fxpay supports receipt restoration, all historic purchase receipts will be made available.

Validating an app receipt

Although this is not directly involved with in-app purchases, it is worth knowing that fxpay provides a mechanism to check the receipt of a paid app when it is loaded on the user's device. This is done with the validateAppReceipt() method, as seen below:

fxpay.validateAppReceipt()
  .then(function(productInfo) {
    console.log('receipt is valid; app was purchased');
    console.log('product URL:', productInfo.productUrl);
  })
  .catch(function(reason) {
    console.log('receipt is INVALID; reason:',
                reason.error || reason);
  });

If a valid app receipt is found, the promise resolves with a productInfo object that allows you to retrieve further info about the installed app. If not the promise rejects with a rejection reason (an object or a DOMString).

To see the code above in context, look at our fxpay example on Github. If you want to run the example app, follow the installation instructions. Some further notes:

  • The allowTestReceipts option is a useful configuration option for using test receipts to test that your code is validating receipts correctly (see Test Receipts for more information.) You should make sure to turn this off in production, otherwise people can use your app for free with test receipts.

fxpay reference

This section provides a reference to important objects contained in the fxpay code, and other available features.

Product Info Object

Many different promises resolve with a product info object. Error objects also have a productInfo property which points to such an object. The product info object has the following properties:

product.productId
A unique string identifier for the product. This corresponds to the identifier you see in the Firefox Marketplace Developer Hub when managing your products.
product.name
The name of the product in the default locale.
product.productUrl
The URL of the product as declared in the receipt. This will most likely be a URL to the app, such as https://your-hosted-app or app://your-packaged-app.
product.smallImageUrl
A 64 pixel square image URL for the product.
product.pricePointId
A reference to the price point chosen during product configuration.
product.receiptInfo
If the product has been purchased, this object gives you more information about the receipt. If the product has not been purchased the property will be falsey.
product.receiptInfo.status
A string status of the receipt, as returned by the validation service. Possible values: "ok", "pending", "refunded", "expired" or "invalid."
product.receiptInfo.reason
For invalid receipts only, this string indicates the reason it was invalid.
product.receiptInfo.receipt
The original web application receipt string. You can cache this in your application to manage restoration of user purchases yourself.

Errors

Promises are rejected with class-like objects that are subclasses of Error.  The following is a guide to the hierarchy of errors so you can catch them and react accordingly. Most errors will also have a productInfo property which points to a relevant product info object.

Here's an example of ignoring cancelled payments and reacting to payment failures:

fxpay.getProducts()
  .then(function(products) {
    // ...
  })
  .catch(function(error) {
    console.error('Error: ' + error);
    if (error instanceof fxpay.errors.PayWindowClosedByUser) {
      console.info('user cancelled the payment');
    } else if (error instanceof fxpay.errors.PaymentFailed) {
      // Maybe show some info on how to purchase.
    } else {
      throw error;
    }
  });

General Errors

fxpay.errors.FxPayError
This is the root subclass for all errors thrown from FxPay. A check like if error instanceof FxPayError will always be true.

Subclasses of FxPayError:

fxpay.errors.ConfigurationError
A problem with how FxPay was configued.
fxpay.errors.FailedWindowMessage
A failure while communicating with another window as part of the payment.
fxpay.errors.IncorrectUsage
An interface in FxPay was used incorrectly.
fxpay.errors.InvalidApp
The app involved in the purchased is somehow invalid.
fxpay.errors.InvalidJwt
The JWT (JSON Web Token) used to begin payment is invalid.
fxpay.errors.NotImplementedError
The feature you are trying to use isn't fully implemented yet.
fxpay.errors.PayWindowClosedByUser
The payment window was closed by the user, perhaps by pressing a Cancel button. This will additionally have a code attribute of DIALOG_CLOSED_BY_USER. You will probably want to ignore this error.
fxpay.errors.UnknownMessageOrigin
FxPay received a window message from an unknown origin.

Failed Payments

fxpay.errors.PaymentFailed
This is the root subclass for all errors relating to payment failures. You can check for this error if you want to show the user an informative message about how to complete or retry the payment.

Subclasses of PaymentFailed:

fxpay.errors.AppReceiptMissing
No receipt could be found to prove ownership of the application.
fxpay.errors.InvalidReceipt
The app receipt or in-app product receipt was invalid.
fxpay.errors.PurchaseTimeout
The API request or process to complete the purchased timed out and likely was not completed.
fxpay.errors.TestReceiptNotAllowed
A test receipt was presented but FxPay was not configured to allow test receipts.

Platform Errors

fxpay.errors.PlatformError
This is the root subclass for all errors that originate from the underlying payment platform instead of from FxPay.

Subclasses of PlatformError:

fxpay.errors.AddReceiptError
An error was returned while working with the mozApps.addReceipt API.
fxpay.errors.PayPlatformError
A general error returned while working with a mozApps APIs.
fxpay.errors.PayPlatformUnavailable
The FxPay feature you're using requires a payments platform, such as mozApps, but none was found.

API Errors

fxpay.errors.ApiError
This is the root subclass for errors that can occurr while working with external APIs such as the Firefox Marketplace.

Subclasses of ApiError:

fxpay.errors.ApiRequestAborted
A request to the API was aborted.
fxpay.errors.ApiRequestError
A general error was returned while working with the API.
fxpay.errors.ApiRequestTimeout
A request to the API timed out.
fxpay.errors.BadApiResponse
An unexpected response was returned from the API.
fxpay.errors.BadJsonResponse
The JSON data from an API response could not be parsed.

Logging

By default, fxpay logs everything using window.console. If you want to replace console with your own logger, pass in an object as log that implements the same window.console methods:

fxpay.configure({log: myConsole});

Configuration

You can call fxpay.configure(overrides) to set some internal variables. If you call this repeatedly, the old keys will be preserved unless overidden.

fxpay.configure({apiTimeoutMs: 3000});

Possible overrides:

apiUrlBase
The base URL of the internal fxpay API. Default: https://marketplace.firefox.com.
apiTimeoutMs
A length of time in milleseconds until any API request will time out. Default: 10000.
apiVersionPrefix
A Path that gets appended to apiUrlBase to access the right API version. Default: /api/v1.
fakeProducts
If true, fxpay.getProducts() will return fake products that can be used for testing. See fake products for details. Default: false.
log
A log object compatible with window.console to use internally. Default: window.console.
receiptCheckSites
Array of sites allowed to verify purchase receipts. These values are top level URLs to verifier services; they don't need to include URL paths. You would only need to adjust this if you want to work with something other than the production version of Firefox Marketplace. Default: ['https://receiptcheck.marketplace.firefox.com'].

Migrating from old fxpay versions

The following release notes will help you update your code for major changes:

  • Version 0.0.15
    • Callback style was deprecated in favor of promises.
    • fxpay.init() was deprecated.
    • Errors codes changed from strings to objects.

See also

Document Tags and Contributors

 Contributors to this page: chrisdavidmills, kumar303, muffinresearch, andymckay, rebloor
 Last updated by: chrisdavidmills,