Service Worker を使用して支払いトランザクションを調整する

ウェブベースの支払いアプリをウェブ支払いに対応させて、ユーザー エクスペリエンスを向上させる方法。

支払いアプリが登録されると、販売者からの支払いリクエストを受け付ける準備が整います。この記事では、実行時(ウィンドウが表示され、ユーザーが操作しているとき)にサービス ワーカーから支払いトランザクションをオーケストレートする方法について説明します。

Service Worker による支払いトランザクションのオケストレーション
サービス ワーカーによる支払い取引のオケストレーション

「実行時の支払いパラメータの変更」とは、ユーザーが支払いハンドラを操作している間に、販売者と支払いハンドラがメッセージを交換できるようにする一連のイベントを指します。詳しくは、サービス ワーカーでオプションのお支払い情報を処理するをご覧ください。

販売者から支払いリクエスト イベントを受信する

お客様がウェブベースの支払いアプリで支払うことを選択し、販売者が PaymentRequest.show() を呼び出すと、サービス ワーカーは paymentrequest イベントを受信します。イベント リスナーを Service Worker に追加して、イベントをキャプチャし、次のアクションの準備を行います。

[支払いハンドラ] service-worker.js:

…
let payment_request_event;
let resolver;
let client;

// `self` is the global object in service worker
self.addEventListener('paymentrequest', async e => {
  if (payment_request_event) {
    // If there's an ongoing payment transaction, reject it.
    resolver.reject();
  }
  // Preserve the event for future use
  payment_request_event = e;
…

保存された PaymentRequestEvent には、このトランザクションに関する重要な情報が含まれています。

プロパティ名 説明
topOrigin トップレベルのウェブページのオリジン(通常は支払い先の販売者)を示す文字列。販売者のオリジンを特定するために使用します。
paymentRequestOrigin 呼び出し元のオリジンを示す文字列。販売者が Payment Request API を直接呼び出す場合は topOrigin と同じですが、支払いゲートウェイなどのサードパーティが iframe 内から API を呼び出す場合は異なる場合があります。
paymentRequestId Payment Request API に提供される PaymentDetailsInitid プロパティ。販売者が省略した場合は、ブラウザが自動生成された ID を提供します。
methodData PaymentMethodData の一部として販売者から提供される、お支払い方法固有のデータ。支払いトランザクションの詳細を確認する場合に使用します。
total PaymentDetailsInit の一部として販売者が提供する合計金額。これを使用して UI を構築し、支払い合計額をお客様に知らせます。
instrumentKey ユーザーが選択した金融機関キー。これは、事前に指定した instrumentKey を反映しています。空の文字列は、ユーザーがインストルメントを指定していないことを示します。

決済ハンドラ ウィンドウを開いて、ウェブベースの決済アプリ フロントエンドを表示する

paymentrequest イベントを受信すると、支払いアプリは PaymentRequestEvent.openWindow() を呼び出して支払いハンドラ ウィンドウを開くことができます。支払いハンドラ ウィンドウには、お客様に支払いアプリのインターフェースが表示されます。ここで、お客様は認証、配送先住所とオプションの選択、支払いの承認を行うことができます。フロントエンド コードの記述方法については、支払いフロントエンドでの支払いの処理(近日公開予定)をご覧ください。

ウェブベースの支払いアプリを使用した購入手続きフロー。

保存したプロミスを PaymentRequestEvent.respondWith() に渡して、後で支払い結果で解決できるようにします。

[支払いハンドラ] service-worker.js:

…
self.addEventListener('paymentrequest', async e => {
…
  // Retain a promise for future resolution
  // Polyfill for PromiseResolver is provided below.
  resolver = new PromiseResolver();

  // Pass a promise that resolves when payment is done.
  e.respondWith(resolver.promise);
  // Open the checkout page.
  try {
    // Open the window and preserve the client
    client = await e.openWindow(checkoutURL);
    if (!client) {
      // Reject if the window fails to open
      throw 'Failed to open window';
    }
  } catch (err) {
    // Reject the promise on failure
    resolver.reject(err);
  };
});
…

便利な PromiseResolver ポリフィルを使用して、任意のタイミングでプロミスを解決できます。

class PromiseResolver {
  constructor() {
    this.promise_ = new Promise((resolve, reject) => {
      this.resolve_ = resolve;
      this.reject_ = reject;
    })
  }
  get promise() { return this.promise_ }
  get resolve() { return this.resolve_ }
  get reject() { return this.reject_ }
}

フロントエンドと情報を交換する

支払いアプリのサービス ワーカーは、ServiceWorkerController.postMessage() を介して支払いアプリのフロントエンドとメッセージを交換できます。フロントエンドからメッセージを受信するには、message イベントをリッスンします。

[支払いハンドラ] service-worker.js:

// Define a convenient `postMessage()` method
const postMessage = (type, contents = {}) => {
  if (client) client.postMessage({ type, ...contents });
}

フロントエンドから準備完了シグナルを受信する

支払いハンドラ ウィンドウが開いたら、Service Worker は支払いアプリのフロントエンドからの準備完了シグナルを待機する必要があります。サービス ワーカーは、準備ができたときに重要な情報をフロントエンドに渡すことができます。

[payment handler] frontend:

navigator.serviceWorker.controller.postMessage({
  type: 'WINDOW_IS_READY'
});

[支払いハンドラ] service-worker.js:

…
// Received a message from the frontend
self.addEventListener('message', async e => {
  let details;
  try {
    switch (e.data.type) {
      // `WINDOW_IS_READY` is a frontend's ready state signal
      case 'WINDOW_IS_READY':
        const { total } = payment_request_event;
…

トランザクションの詳細をフロントエンドに渡す

お支払いの詳細を返信してください。この場合、送信するのは支払いリクエストの合計額のみですが、必要に応じて詳細情報を渡すこともできます。

[支払いハンドラ] service-worker.js:

…
        // Pass the payment details to the frontend
        postMessage('PAYMENT_IS_READY', { total });
        break;
…

[payment handler] frontend:

let total;

navigator.serviceWorker.addEventListener('message', async e => {
  switch (e.data.type) {
      case 'PAYMENT_IS_READY':
        ({ total } = e.data);
        // Update the UI
        renderHTML(total);
        break;
…

お客様のお支払い認証情報を返す

お客様が支払いを承認すると、フロントエンドはサービス ワーカーに投稿メッセージを送信して処理を進めることができます。PaymentRequestEvent.respondWith() に渡された Promise を解決して、結果を販売者に返すことができます。PaymentHandlerResponse オブジェクトを渡します。

プロパティ名 説明
methodName お支払いに使用されたお支払い方法の識別子。
details 販売者が支払いを処理するために必要な情報を提供する、支払い方法固有のデータ。

[payment handler] frontend:

  const paymentMethod = …

  postMessage('PAYMENT_AUTHORIZED', {
    paymentMethod,              // Payment method identifier
  });

[支払いハンドラ] service-worker.js:

…
// Received a message from the frontend
self.addEventListener('message', async e => {
  let details;
  try {
    switch (e.data.type) {
      …
      case 'PAYMENT_AUTHORIZED':
        // Resolve the payment request event promise
        // with a payment response object
        const response = {
          methodName: e.data.paymentMethod,
          details: { id: 'put payment credential here' },
        }
        resolver.resolve(response);
        // Don't forget to initialize.
        payment_request_event = null;
        break;
      …

支払い取引をキャンセルする

お客様が取引をキャンセルできるようにするには、フロントエンドでサービス ワーカーに投稿メッセージを送信します。サービス ワーカーは、PaymentRequestEvent.respondWith() に渡されたプロミスを null で解決し、取引がキャンセルされたことを販売者に通知します。

[payment handler] frontend:

  postMessage('CANCEL_PAYMENT');

[支払いハンドラ] service-worker.js:

…
// Received a message from the frontend
self.addEventListener('message', async e => {
  let details;
  try {
    switch (e.data.type) {
      …
      case 'CANCEL_PAYMENT':
        // Resolve the payment request event promise
        // with null
        resolver.resolve(null);
        // Don't forget to initialize.
        payment_request_event = null;
        break;
      …

サンプルコード

このドキュメントで紹介したサンプルコードはすべて、次の動作するサンプルアプリからの抜粋です。

https://paymenthandler-demo.glitch.me

[payment handler] service worker

[payment handler] フロントエンド

試すには:

  1. https://paymentrequest-demo.glitch.me/ にアクセスします。
  2. ページの一番下までスクロールします。
  3. [お支払い方法を追加] ボタンを押します。
  4. [Payment Method Identifier] フィールドに「https://paymenthandler-demo.glitch.me」と入力します。
  5. フィールドの横にある [支払い] ボタンを押します。

次のステップ

この記事では、Service Worker から支払いトランザクションをオーケストレートする方法について説明しました。次のステップでは、Service Worker に高度な機能を追加する方法を学びます。