Aplicativos que tenham sido submetidos para o Marketplace da Mozilla poderão ser pagos pelo aplicativo Atualmente isso ainda é uma característica experimental (Junho 2012).
API de pagamentos através do aplicativo em background
O objetivo da API de pagamentos através de aplicativos é permitir um conteúdo web para inicializar o pagamento de um usuário para uma aplicação web, com as seguintes propriedades:
- O aplicativo não recebe quaisquer dados de cartões de pagamentos do usuário.
- O usuário só precisará inserir os dados de pagamento uma vez.
- Os dados do cartão de pagamento são armazenados de maneira que satisfaça as exigências do PCI DSS.
- O usuário recebe uma confirmação, clara e cancelável do que o pagamento será cobrado, a partir de uma fonte confiável.
- O aplicativo recebe de forma clara, a confirmação não repudiável quando um pagamento é efetuado, e tem uma trilha de auditoria para permitir respostas eficientes para estornos e tentativa de fraude.
- A API irá suportar somente pagamentos de boa procedência.
Outline of the in-app payment system
The Mozilla Marketplace in-app payment system is a quick web based flow where the buyer confirms the purchase. Here is a summary of a successful purchase:
- The app initiates a payment by signing a JWT request and calling a JavaScript function
- This starts the buyflow in a new window
- The buyer logs in with BrowserID (part of Mozilla Persona)
- The buyer completes the purchase
- The app receives a JavaScript callback when the buyer closes the window
- The app server receives a signed POST request with a Mozilla transaction ID indicating that the purchase was completed successfully
To use in-app payments, an app needs to load this script in its HTML:
<script src="https://marketplace-cdn.addons.mozilla.net/mozmarket.js" type="text/javascript"></script>
General flow of actions
-
The Mozilla Marketplace assigns you, as the app operator, an application key and an application secret; you provide Mozilla with postback and chargeback URLs. During the app submission process you need to indicate that you accept in-app payments and after finishing, you'll see a link to the management page where you can enter these callback URLs. It looks similar to the screen below.
- Save the application key and application secret securely to your app server.
Important: Ensure that no one else can read your application secret. Never expose it to the client.
- The app generates a payment request that contains all of the information about the item being purchased: price, currency, name, description, and so on. The app signs the payment request with its app secret and encodes the whole thing as a JSON Web Token (JWT). The JWT spec has more information about the standard claim fields. Below is an example of requesting a purchase such as a digital music download. You may only sell digital goods or services, not physical products.
{ iss: "ABGCWLQP8BGC4L1QP88", aud: "marketplace.mozilla.org", typ: "mozilla/payments/pay/v1", iat: 1337357297, exp: 1337360897, request: { priceTier: 1, name: "My band's latest album", description: "320kbps MP3 download, DRM free!", imageURL: "/media/product-image.jpg", productdata: "my_product_id=1234&my_session_id=XYZ" } }
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, the signed payment request looks something like this:
eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.IntcImF1ZFwiOiBcIm1hcmtldHBsYWNlLm1vemlsbGEub3JnXCIsIFwiaXNzXCI6IFwiQVBQLTEyM1wiLCBcInJlcXVlc3RcIjoge1wiY3VycmVuY3lcIjogXCJVU0RcIiwgXCJwcmljZVwiOiBcIjAuOTlcIiwgXCJuYW1lXCI6IFwiVmlydHVhbCAzRCBHbGFzc2VzXCIsIFwicHJvZHVjdGRhdGFcIjogXCJBQkMxMjNfREVGNDU2X0dISV83ODkuWFlaXCIsIFwiZGVzY3JpcHRpb25cIjogXCJWaXJ0dWFsIDNEIEdsYXNzZXNcIn0sIFwiZXhwXCI6IFwiMjAxMi0wMy0yMVQxMTowOTo1Ni43NTMxNDFcIiwgXCJpYXRcIjogXCIyMDEyLTAzLTIxVDEwOjA5OjU2LjgxMDQyMFwiLCBcInR5cFwiOiBcIm1vemlsbGEvcGF5bWVudHMvcGF5L3YxXCJ9Ig.vl4E31_5H3t5H_mM8XA69DqypCqdACVKFy3kXz9EmTI
The fields of the in-app payment JWT:
- iss: Issuer of the JWT. This is your assigned application key.
- aud: Audience. You must set this to the domain of the Mozilla Marketplace
, marketplace.mozilla.org
. - typ: JWT type.
- iat: Issued-at time. This is a UTC Unix timestamp of when the JWT was issued.
- exp: Expiration. A UTC Unix timestamp of when the JWT should expire.
- request: Request object.
- priceTier: Price tier (integer) that corresponds to an actual price in a supported currency. You can find all numeric price codes in the payments docs.
- name: Short description of your product, no longer than 100 characters.
- description: Longer description of your product, no longer than 255 characters.
- imageURL: Relative or absolute URL to the product image. This should be a large PNG or JPEG image. The marketplace will fetch it once the first time, resize it, then cache it on the server. It will re-fetch the image if the cached image is older than 5 days. The first time you make a payment (say, during development) you will not see the image immediately since it is in the process of being cached.
- productdata: Freeform string, no longer than 255 characters. This can be anything your application might need to identify the product with when a postback is sent back to your app.
- For a user to make a purchase, the app must execute the JavaScript method
mozmarket.buy()
with this signed payment request (this method is defined in themozmarket.js
file mentioned above). For example, you might make a buy button that triggers this method when clicked. Themozmarket.buy()
method takes the following parameters: a success callback, and a failure callback. This returns a request object so you can asynchronously generate a signed request and callrequest.sign(signedRequest)
when ready.
var request = mozmarket.buy(onPaySuccess, onPayFailure); request.sign(signedRequest);
The success and failure callbacks do not currently accept any arguments; they look like this:function onPaySuccess() { // The user completed the lightbox payment flow without errors. } function onPayFailure() { // The user canceled out of the lightbox or encountered an error and closed the lightbox. }
Important: The success callback is not proof that payment was sent to your app successfully. You must verify the signed postback response, documented below.In a realistic application you would probably show a buy button, attach a click handler, and make an Ajax call to your server to generate the signed request. Here is an example of that with jQuery. The HTML:
<html> <body> <button class="buy" data-product-id="1234">Buy</button> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script> <script src="https://marketplace-cdn.addons.mozilla.net/mozmarket.js" type="text/javascript"></script> <script src="buy_button.js" type="text/javascript"></script> </body> </html>
The JavaScript:
$('button.buy').click(function(event) { event.preventDefault(); var $button = $(this); var onSuccess = function() { alert('payment completed'); }; var onError = function() { alert('payment canceled'); }; // Start the payment flow for the buyer. var request = mozmarket.buy(onSuccess, onError); $.post('/sign-request', {productId: $button.data('product-id')}, function(data) { // Set the signed request (JWT) from a server POST that returns JSON. request.sign(data.signedRequest); }, 'json'); });
- The
mozmarket.
buy()
method opens a pop-up window pointed atmarketplace.mozilla.org
.- If the user is not authenticated to
marketplace.mozilla.org
, the user is prompted to authenticate using Mozilla Persona. - If the Mozilla Marketplace account does not have a preauthorized payment token, the user is prompted to begin the PayPal preauthorization flow in his browser (not in the app). At completion of this flow, the preauthorization token is saved so all future payments will complete immediately within the window.
- If the user is not authenticated to
- The purchase is presented to the user for confirmation, with name, description, and identifying information from Mozilla Marketplace about the seller. This seller information can include the seller's name, URL, contact information, and possible reputation data. For example, the reputation data might mention that the seller is a brand new business.
- The user confirms the purchase or cancels it.
- If the user cancels, the flow returns to the failure callback of the
mozmarket.buy()
method which is executed without any parameters, likeonPayFailure()
. - If the user confirms, the app's signed payment request is posted to the Marketplace. This completes the purchase with the payment provider (e.g. PayPal) and money is sent to the app's account. When the user clicks the close window button on the lightbox, the app's success callback is executed without any parameters like
onPaySuccess()
. - If the payment request was signed incorrectly, or an internal Marketplace error occurs, or an unexpected PayPal error occurs, the user will see a generic error message and a suggestion to try again or contact the app developer if it keeps happening. In the developer sandbox, a detailed exception message will be displayed.
- If the user cancels, the flow returns to the failure callback of the
marketplace.mozilla.org
sends a POST confirmation message (a JWT) to the postback URL for the seller. This confirmation message contains all the payment request fields plus a transaction ID, and is signed with the seller's application secret.{ iss: "marketplace.mozilla.org", aud: "ABGCWLQP8BGC4L1QP88", typ: "mozilla/payments/pay/postback/v1", iat: 1337357297, exp: 1337360897, request: { priceTier: 1, name: "My band's latest album", description: "320kbps MP3 download, DRM free!", productdata: "my_product_id=1234", } response: { transactionID: "123456123456123456" } }
The application must respond to the postback with the transaction ID. For example:
123456123456123456
In Python code (using PyJWT and the Django web framework), you could decode the request dictionary from the POST body, cryptographically verify the signature, and respond with Mozilla's transaction ID like this:
import jwt from django import http from django.conf import settings def postback_view(request): encoded_jwt = request.read() # get the raw POST body verified_request = jwt.decode(encoded_jwt, settings.APPLICATION_SECRET, verify=True) moz_transaction_id = verified_request['response']['transactionID'] return http.HttpResponse(moz_transaction_id)
Here is a detailed explanation of the postback JWT:
- iss: Issuer of the JWT.
- aud: Audience. This is your assigned application key.
- typ: JWT type.
- iat: Issued-at time. This is a UTC Unix timestamp of when the JWT was issued.
- exp: Expiration. A UTC Unix timestamp of when the JWT expires.
- request: Copy of the request object your application sent in the original payment request.
- response: Response object.
- transactionID: Unique ID of the transaction as it was recorded by Mozilla Marketplace.
- The seller can proceed with confidence that the payment will probably complete.
Chargebacks
Chargebacks will be delivered to the app just like postbacks (a POSTed JWT) but they might arrive later on. Here is an example of what a decoded chargeback will look like:
{ iss: "marketplace.mozilla.org", aud: "ABGCWLQP8BGC4L1QP88", typ: "mozilla/payments/pay/chargeback/v1", iat: 1337357297, exp: 1337360897, request: { priceTier: 1, name: "My band's latest album", description: "320kbps MP3 download, DRM free!", productdata: "my_product_id=1234", } response: { transactionID: "123456123456123456", reason: "refund" } }
The application must respond to the chargeback with the transaction ID. For example:
123456123456123456
The JWT is similar to the postback but has an additional reason field. Here are the details:
- iss: Issuer of the JWT.
- aud: Audience. This is your application key.
- typ: JWT type.
- iat: Issued-at time. UTC Unix timestamp of when the JWT was issued.
- exp: Expiration. UTC Unix timestamp of when the JWT expires.
- request: Copy of the request object your application sent in the original payment request.
- response: Response object.
- transactionID: Unique ID of the transaction as it was recorded by Mozilla Marketplace.
- reason: Reason for the chargeback. Valid values are
refund
orreversal
.- A refund means the payment was refunded (possibly by you in the PayPal dashboard or by Mozilla for some reason).
- 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 PayPal or through the credit card company as part of a dispute.
Protect the application secret
Revoking a compromised app 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 Mozilla 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
Working with Mozilla Marketplace in-app purchases:
- Python: moz_inapp_pay module
JSON Web Token (JWT) encoding/decoding and signature verification:
- Python: PyJWT
- Ruby: ruby-jwt
- node.js: node-jwt-simple
- PHP: luciferous/jwt
- Java: jsontoken
- .NET: Json.NET
Sample apps
- 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