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

ウェブベースの決済アプリをウェブ決済に適応させ、より優れたユーザー エクスペリエンスを提供する方法。

決済アプリが登録されると、販売者からの支払いリクエストを受け入れる準備が整います。この投稿では、実行中(ウィンドウが表示され、ユーザーがウィンドウを操作しているときなど)に Service Worker からの支払いトランザクションをオーケストレートする方法について説明します。

Service Worker を使用して支払いトランザクションをオーケストレートする
Service Worker を使用した支払いトランザクションのオーケストレーション

「ランタイム支払いパラメータの変更」とは、ユーザーが支払いハンドラとやり取りしている間に販売者と支払いハンドラがメッセージを交換できるようにする一連のイベントを指します。詳細については、Service Worker で省略可能な支払い情報を処理するをご覧ください。

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

顧客がウェブベースの支払いアプリでのお支払いを選択し、販売者が PaymentRequest.show() を呼び出すと、Service Worker は 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() を呼び出して支払いハンドラ ウィンドウを開くことができます。支払いハンドラ ウィンドウには支払いアプリのインターフェースが表示され、お客様はそこで認証、配送先住所とオプションの選択、支払いの承認を行うことができます。フロントエンド コードの記述方法については、支払いフロントエンドでの支払い処理(近日公開予定)で説明します。

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

将来の支払い結果で解決できるように、保持された Promise を 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 ポリフィルを使用すると、任意のタイミングで Promise を解決できます。

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

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

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

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

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

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

支払いハンドラ ウィンドウが開いたら、Service Worker は支払いアプリのフロントエンドからの準備状態のシグナルを待つ必要があります。Service Worker は、準備ができたら重要な情報をフロントエンドに渡すことができます。

[支払いハンドラ] 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;
…

[支払いハンドラ] 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;
…

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

顧客が支払いを承認すると、フロントエンドは Service Worker に POST メッセージを送信して続行できます。PaymentRequestEvent.respondWith() に渡された Promise を解決して、結果を販売者に送信できます。PaymentHandlerResponse オブジェクトを渡します。

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

[支払いハンドラ] 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;
      …

支払いトランザクションをキャンセルする

顧客が取引をキャンセルできるように、フロントエンドは Service Worker に POST メッセージを送信できます。次に Service Worker は、PaymentRequestEvent.respondWith() に渡された Promise を null で解決し、取引がキャンセルされたことを販売者に通知できます。

[支払いハンドラ] 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

[支払いハンドラ] Service Worker

[支払いハンドラ] frontend

試してみるには:

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

次のステップ

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