mozPay を使用したアプリ内課金

navigator.mozPay API は、デジタルアイテムの料金を Web コンテンツ内で決済するとともに、購入の確認情報を決済プロバイダから受け取るための API です。またこの API には、対応する Web Payment Provider が用意されており、それを通じて、アプリの提供者は支払われた料金を受け取ることができます。この記事では、navigator.mozPay API と Web Payment Provider サービスを使用して、アプリ内課金を設定する方法について説明します。

Mozilla では、課金メカニズムとして fxPay を推奨しています。fxPay は、購入確認のためのサーバをホストする必要がないなどの利点があり、アプリ内課金を便利に処理することができます。ただし、fxPay API はライブではあるものの、いまだ開発途上の実験的な API として位置付けられています。実験段階の API を使用することに問題がある場合は、引き続き mozPay を使用してください。

アプリ内課金の概要

以下では、アプリ内課金の機能をアプリケーションに追加する方法と、追加した機能がどのように動作するかについて簡単に説明します。

  • Firefox Marketplace Developer Hub へログインします
  • アプリをアップロード して、課金情報を設定し、アプリケーションキーと秘密鍵を生成します
  • あなたのアプリから、JWT リクエストを秘密鍵で署名し、navigator.mozPay(...) を呼び出すことで支払を開始します
  • これにより、特別なウィンドウ内で支払フローが開始されます
    • 購入者が Mozilla Persona でログインします (ログインしていない場合)
    • 購入者が PIN を入力します
    • 購入者がその支払を携帯電話料金もしくはクレジットカードに請求します
  • 購入者がウィンドウを閉じると、あなたのアプリは JavaScript コールバックを受け取ります
  • あなたのアプリサーバは、支払が正常に完了したことを示す Mozilla トランザクション ID で署名された POST リクエストを受け取ります
  • 料金があなたの銀行口座へ直接振り込まれます

navigator.mozPay API は今のところ Firefox OS 上でのみ使用可能です。この API は Firefox OS Simulator でテスト可能です。

注: Firefox OS 1.2 がインストールされたデバイスでテストを実施する場合、デバイスに SIM カードが搭載されていなければ、mozPay 呼び出しが失敗することに注意してください。このことが、一般のユーザにとって問題となることはありません。ほとんどのデバイスには SIM カードが搭載されており、また提供される予定の Firefox のバージョンは、Firefox 1.2 を使用しないからです。詳細については、バグ 989022 を参照してください。

アプリ内課金の設定手順

このセクションでは、アプリ内課金の設定方法を、テストバージョンと、アプリの登録準備が整った時点の、実際のバージョンの両方について詳細に説明します。


テスト用支払キーの取得

Firefox Marketplace Developer Hub へログインしたら、In-App Payment Keys ページを開いて、テスト用のアプリケーションキーと秘密鍵を生成することができます。この鍵はアプリ内課金の「シミュレーション」を可能にするだけですが、テストには最適です。アプリを Marketplace へ登録してレビューを申請する前に、あらかじめいくつかのシミュレーションを試しておくべきでしょう。課金をシミュレートする方法については、このページの続きを読んでください。

実際の支払キーの取得

実際のアプリケーションを Firefox Marketplace Developer Hub へ登録すると、課金設定を行うかどうか尋ねられます。アプリの料金を選択し (おそらく無料にしたいと思うでしょう) アプリ内課金を受け入れるオプションを選択します。銀行の口座情報を入力したら、Manage In-App Payments ページを開いて、実際の支払を行うためのアプリケーションキーと秘密鍵を取得できます。

アプリケーションの秘密鍵を、個人設定ファイルのような形で、サーバ上へ安全に保存します。

重要: 他人にアプリケーションの秘密鍵を見られないよう注意してください。絶対に外部に対して公開してはいけません。

アプリケーションの設定

例えば、あなたが「Magical Unicorn」というアドベンチャーゲームをOpen Web Appsとして開発しており、プレーヤーに対して使用料金を課金したいとします。1.99 ドル、1.89 ユーロ、その他購入者の選択した通貨での請求を行うことにします。これ以降の各セクションでは、バックエンドサーバを設定し、navigator.mozPay を使用して製品を販売するフロントエンドコードを作成する方法について説明します。

サーバ上での JWT 署名の準備

決済は、 JSON Web Tokens (JWT) によって開始されます。JWTは、クライアントサイドではなくサーバサイドで行う必要があります。これは、署名に使われる秘密鍵を絶対に公開してはいけないからです。引き続き「Magical Unicorn」というアドベンチャーゲームを販売する例を挙げます。サーバ上で製品名、価格などを定義した JSON オブジェクトを生成し、/sign-jwt のような URL でアクセス可能にします。JWT 形式についての完全な情報は Web Payment API の仕様 を参照してください。以下に例を挙げます。

{
  "iss": APPLICATION_KEY,
  "aud": "marketplace.firefox.com",
  "typ": "mozilla/payments/pay/v1",
  "iat": 1337357297,
  "exp": 1337360897,
  "request": {
    "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
    "pricePoint": 1,
    "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"
  }
}

Web Payment API の仕様 に、形式についての詳しい説明が載っています。重要な点は次の通りです。

  • iss (issuer) は、Firefox Marketplace Developer Hub から取得したアプリケーションキーに相当します。
  • aud (audience) は常に、marketplace.firefox.com に設定します。
  • request.id は、製品ごとに固有の ID を指定します。
  • request.pricePoint は、購入時の価格と通貨に展開されます。これは有効な価格帯に設定する必要があります。
  • request.productData には、255 文字までの任意の文字列を設定できます。これは、ポストバックの受信時にアプリケーション状態を復元するのに使用できます。
  • request.postbackURLrequest.chargebackURL には、サーバ上の有効な絶対 URL を指定します。実環境では、可能な限り URL に HTTPS を使用する必要があります。
  • request.icons オブジェクト (オプション) は、販売する製品のアイコン URL のマップです。キーは幅と高さのピクセル値です (画像は正方形でなければなりません)。Mozilla が提供する決済プロバイダの支払確認ページでは、提供者が指定した 64 ピクセル四方の画像が使用されます。64 ピクセル四方の画像を指定しない場合、最大のアイコンが決済プロバイダによって取得され、サイズ調整されます。初めて支払いが行われるときは、決済プロバ イダがバックグラウンドでアイコンを取得するため、アイコンの表示に少し時間がかかりますが、2回目以降は即座に表示されます。アイコンを変更した場合、 反映されるまでに最大 24 時間かかります。
  • request.defaultLocale は、アプリ内課金製品で使用されている言語を記述します。これは locales を定義していなければ省略可能ですが、定義した場合は必須です。
  • request.locales オブジェクト (オプション) は、アプリ内課金製品に含まれているデータを 1 つ以上のロケール固有のデータに上書きするためのマップです。これにより、アクセスするデバイスのロケールに基き、地域化された UI が表示されます。例えば、イタリアのユーザがアプリをインストールした場合には、UI テキストをイタリア語で表示することができます。各ロケールのエントリでは、言語タグ (RFC 4646) をキー入力し、置き換えるキーを含めます。上書きできるキーは、namedescription のみです。locales を定義した場合は、defaultLocale も定義する必要があります。

(PyJWT を使った) Python コードでは、上記のリクエストディクショナリを以下のようにして署名、エンコードできます。

import jwt
signed_request = jwt.encode(request_dict, application_secret, algorithm='HS256')

このコードでは、アプリケーション秘密鍵を使って JWT を署名し、HMAC SHA 256 アルゴリズムを使用しています。それをエンコードすると次のようになります。

eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.IntcImF1ZFwiOiBcIm1hcmtldHBsYWNlLm1vemlsbGEub3JnXCIsIFwiaXNzXCI6IFwiQVBQLTEyM1wiLCBcInJlcXVlc3RcIjoge1wiY3VycmVuY3lcIjogXCJVU0RcIiwgXCJwcmljZVwiOiBcIjAuOTlcIiwgXCJuYW1lXCI6IFwiVmlydHVhbCAzRCBHbGFzc2VzXCIsIFwicHJvZHVjdGRhdGFcIjogXCJBQkMxMjNfREVGNDU2X0dISV83ODkuWFlaXCIsIFwiZGVzY3JpcHRpb25cIjogXCJWaXJ0dWFsIDNEIEdsYXNzZXNcIn0sIFwiZXhwXCI6IFwiMjAxMi0wMy0yMVQxMTowOTo1Ni43NTMxNDFcIiwgXCJpYXRcIjogXCIyMDEyLTAzLTIxVDEwOjA5OjU2LjgxMDQyMFwiLCBcInR5cFwiOiBcIm1vemlsbGEvcGF5bWVudHMvcGF5L3YxXCJ9Ig.vl4E31_5H3t5H_mM8XA69DqypCqdACVKFy3kXz9EmTI

これで、エンコードおよび署名された JWT は、前項で説明したように、クライアント側コードでアプリによる使用が可能となります。

購入ボタンの設置

これで、製品の JWT を作成するためのバックエンドができました。次に、navigator.mozPay を使用してフロントエンドコードを作成する例を示します。アプリの中にボタンを設置して、プレーヤーがゲームを購入できるようにする場合を考えてみましょう。以下に例を示します。

<button id="purchase">Magical Unicorn を購入する</button>

購入ボタンがクリックされると、アプリによってJSON Web Token (JWT) が署名され、navigator.mozPay が呼び出されます。以下は、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 :)
}

このコードは、サーバー上の /sign-jwt URL に対して Ajax リクエストを行います。この URL は、製品・価格情報とともに JSON コードへの署名を行い、JWT をプレーンテキストで返します。Ajax ハンドラはその JWT を navigator.mozPay へ渡し、決済プロバイダが購入確認をサーバへ POST するまで待ちます。POST された JWT 上の署名が正しい場合は、その仮想アイテムを顧客へ提供できます。

 

サーバ上でのポストバックの処理

開発者は、Marketplace から購入の確認が届くのを待って、購入者に製品を提供する必要があります。この購入確認をポストバックと呼びます。 marketplace.firefox.com サイトでは、POST 確認通知 (JWT) が、元の決済リクエストで指定された request.postbackURL に送信されます。ポストバック URL には、ポート 80 またはポート 443 でリスンしているサーバの URL を指定する必要があります。

この POST の Content-Typeapplication/x-www-form-urlencoded で、JWT は notice パラメータに含まれています。サーバフレームワークでは、通常、request.POST['notice'] のような指定によってアクセスします。

この JWT 通知には、すべての決済リクエストフィールドとトランザクション ID が含まれ、Firefox Marketplace Developer Hub から取得したアプリケーション秘密鍵を使用して署名されます。ポストバックを受け取り、署名を検証したら、購入者に製品を提供することができます。受け 取った JWT の署名が検証できない場合は、Marketplace から送信されたものでない可能性が高いので無視してください。

ポストバックの形式については、Web Payment API の仕様 で詳細な説明があります。ポストバックには、元のリクエストが含まれ、Mozilla 固有のトランザクション ID が入った新しいレスポンスパラメータが追加されます。以下に例を示します。

{
  "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": 1,
    "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"
  },
  "response": {
    "transactionID": "webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9"
  }
}

ポストバックについての重要な点は次の通りです。

  • 元の決済リクエストが常に含まれます。
  • iss (issuer) は、常に marketplace.firefox.com です。
  • aud (audience) は、アプリケーションキーに設定されます。
  • JWT は、アプリケーション秘密鍵を使用して署名されます。
  • response.transactionID は、Mozilla の Web Payment Provider に固有の ID です。
  • response.price は、金額とユーザが実際に使用した貨幣です。
  • ポストバックとチャージバックの URL は、Web サーバのポート 80 またはポート 443 を使用する必要があります。それ以外のポートは使用できません。

ポストバックへの応答

アプリケーションは、ポストバックに対して、トランザクション ID のみを含むプレーンテキスト HTTP レスポンスで応答する必要があります。以下に例を示します。

HTTP/1.1 200 OK
Content-Type: text/plain

webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9

サーバ上でのチャージバックの処理

トランザクション中に、購入者アカウントの残高不足などの問題が発生した場合、Marketplace によって送信者にチャージバック通知 (POST された JWT) が送信されます。チャージバックは、ポストバックと同様にアプリに対して配信されますが、ポストバックと異なり、遅れて配信されることがあります。この POST は、Content-Typeapplication/x-www-form-urlencoded であり、JWT は notice パラメータに含まれています。チャージバックの URL には、ポート 80 またはポート 443 でリスンしているサーバの URL を指定する必要があります。

以下に、デコードされたチャージバック通知の例を示します。

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

この JWT はポストバックと似ており、Web Payment API の仕様に詳細が定義されています。重要な点は、次のとおりです。

  • 元の決済リクエストが常に含まれます。
  • iss (issuer) は、常に marketplace.firefox.com です。
  • aud (audience) は、アプリケーションキーに設定されます。
  • JWT は、アプリケーション秘密鍵を使用して署名されます。
  • response.transactionID は、Mozilla の Web Payment Provider に固有の ID です。
  • response.reason は、refund または reversal のいずれかに設定されています。
    • refund は、購入者の要求に応じて、または管理者によって返金されたことを意味します。
    • reversal は、トランザクションの完了後に、購入者がクレジットカード発行者に対してその取り消しを要求したことを意味します。取り消しの要求は、クレジットカード会社を通じて、異議申し立ての一部として行われる場合もあります。

: アプリ内課金の返金はまだ実装されていません

アプリケーションは、チャージバックに対して、トランザクション ID のみを含むプレーンテキスト HTTP レスポンスで応答する必要があります。以下に例を示します。

HTTP/1.1 200 OK
Content-Type: text/plain

webpay:84294ec6-7352-4dc7-90fd-3d3dd36377e9

ポストバックとチャージバックのエラー

アプリケーションサーバが HTTP リクエストに対して非成功ステータスコードで応答した場合、Mozilla の Web Payment Provider は URL の読み込みを数回再試行します。それでも成功レスポンスを受け取れなかった場合、アプリ開発者に通知が行われるとともに、アプリが一時的に無効化される可能性があります。アプリケーションサーバがポストバックやチャージバックに対してトランザクション ID で応答しなかった場合もエラーと同様に扱われ、再試行などの処理が行われます。

HTTPS のポストバックとチャージバックの URL

アプリを本番環境で実行する際は、可能な限り、安全な HTTPS URL を使用してください。これにより、ポストバックデータが Mozilla サーバから開発者のアプリサーバに送信されている途中に、他人に読み取られることを防止できます。HTTPS の使用は、決済リクエストの完全性の保護という意味では必須ではありません。JWT の署名によって完全性が保護されるからです。

警告:安全な HTTPS ポストバック URL を使用できない場合は、他人に傍受された場合に備え、決済リクエストに個人を特定可能な情報が含まないように注意してください。例えば、productData の値に、機密性の高いユーザデータが含まれていないことを確認します。Mozilla の標準設定では、決済リクエストに個人を特定可能な情報は含まれていません。

ポストバックとチャージバックの IP

上で説明したように適切に JWT 署名を確認していれば、ポストバック通知やチャージバック通知を送信する Firefox Marketplace の IP をホワイトリスト化する必要はありません。しかし、別の側面で保護を追加する必要がある場合 (キー窃盗からの防御など)、Marketplace から次の IP アドレスにポストバックとチャージバックの通知を送信することができます。これらの IP アドレスに関する変更は、dev-marketplace メーリングリストでお知らせします。

63.245.216.100

課金のシミュレーション

この記事の冒頭で、アプリ内課金をシミュレートするために、Firefox Marketplace Developer Hub から特別なアプリケーションキーとアプリケーション秘密鍵を取得する方法を解説しました。この秘密鍵を使って、以下のように、独自の JWT に署名します。

{
  "iss": APPLICATION_KEY,
  "aud": "marketplace.firefox.com",
  "typ": "mozilla/payments/pay/v1",
  "iat": 1337357297,
  "exp": 1337360897,
  "request": {
    "id": "915c07fc-87df-46e5-9513-45cb6e504e39",
    "pricePoint": 1,
    "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"
    }
  }
}

追加の request.simulate 属性が、決済プロバイダに対して、課金を行うことなく一部の結果をシミュレートするよう伝えます。ユーザインタフェースはログインも PIN 番号も尋ねません。アプリの開発中にこの方法を使って、購入ボタンから navigator.mozPay が正しく呼び出されるか、サーバ上のポストバック、チャージバック URL が正しく動作するかどうかを確認できます。

成功した購入をシミュレートし、署名済み通知をポストバック URL へ送信する例を以下に示します。

{
  ...
  "request": {
    ...
    "simulate": {
      "result": "postback"
    }
  }
}

チャージバックの返金をシミュレートする方法を以下に示します。

{
  ...
  "request": {
    ...
    "simulate": {
      "result": "chargeback",
      "reason": "reason"
    }
  }
}

JWT 通知は、ランダムに生成されたトランザクション ID を受け取る点を除き、実際の購入のようにハンドラへ投げられます。シミュレーションでは HTTPS でない URL を使っても構いません。

重要: シミュレーションされた支払 JWT を実環境で使ってはいけません。顧客が製品を無料で取得できてしまいます。

エラーのデバッグ

アプリ内課金 API の使用方法が誤っている場合、決済画面にエラーが表示され、ユーザが必要な措置を取る助けとなります。決済画面にはまた、開発者が問題を解決する助けとなるエラーコードが含まれています。Error Legend API を使用すれば、エラーコードをもとに自国の言語による説明を参照することができます。例えば、INVALID_JWT は、JWT の署名が無効であるか、JWT の形式が誤っていることを意味します。

アプリケーション秘密鍵の保護

警告: 他人にアプリケーションの秘密鍵を見られないよう注意してください。絶対に外部に対して公開してはいけません

漏えいしたアプリ秘密鍵の無効化

万が一、アプリケーション秘密鍵が漏えいしたり信頼できなくなった場合、できる限り早くそれを無効化する必要があります。手順は以下の通りです。

  1. Firefox Marketplace へログインします。
  2. My Submissions ページを開き、自分のアプリを見つけます。
  3. Manage In-App Payments ページを開きます。これは認証情報を生成したのと同じところです。
  4. Reset Credentials ボタンをクリックします。

一度認証情報をリセットすると、古い認証情報による支払処理はできなくなります。すぐに使い始められる新しいアプリケーションキーと秘密鍵が生成されますので、それを使ってアプリ内での支払処理を続けられます。

その他何らかのセキュリティ問題を報告する必要がある場合は、Payments/Refunds コンポーネントにバグを登録してください

コードライブラリ

以下は Mozilla の navigator.mozPay 固有のライブラリです。

  • Python: mozpay モジュール
  • node.js: mozpay モジュール

以下は、エンコード・デコードと署名検証に使用できる、いくつかの一般的な JSON Web Token (JWT) ライブラリです。

サンプルコード

  • NodeJS でアプリ内課金を実装したゲーム、Web Fighter のソースコードを参照できます。また、ここをクリックすると、Marketplace からこのゲームをインストールできます。
  • Python で JWT リクエストに署名し、ポストバックとチャージバックの検証コードを記述する方法を示した、診断法とテストアプリです: In-app Payment Tester

サポート

  • アプリ内課金関連の問題は dev-webapps メーリングリストまたはirc.mozilla.org の #payments チャネルで議論できます。
  • バグを見つけた場合は Marketplace 関連バグ の Payments/Refunds コンポーネントに登録できます

類似の課金システム

ドキュメントのタグと貢献者

 このページの貢献者: dynamis, mantaroh, kohei.yoshino, ethertank
 最終更新者: dynamis,