Payment Request API:統合ガイドを写経してみた

#はじめに
先月メルカリさん主催のイベントに参加し、実はその時に初めてPaymentRequestAPIなるものの存在を知りました。触ってみたいなと思いつつも結局はまだ触らず終いでした。

そんな中、昨日このようなツイートを拝見しました。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">まもなくリリースされる新しいSafariでは、Payment Request APIを介してApple Payを利用できるようになります。その仕様が公開されました<a href="https://t.co/GiRrywgrMv">https://t.co/GiRrywgrMv</a></p>&mdash; Eiji Kitamura / えーじ (@agektmr) <a href="https://twitter.com/agektmr/status/978513973698940928?ref_src=twsrc%5Etfw">2018年3月27日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

いいきっかけだと思って触ってみることにしました。


#PaymentRequestAPIとは
先ほどのツイートをされている方が書いた記事になります。ここに凝縮されています。
[ウェブでの新しいお金の払い方 - Web Payments と Payment Request API について: Tender Surrender](https://blog.agektmr.com/2017/07/conversion-api.html)

>
Payment Request API は、ウェブでの支払いを標準化しようという一連の仕様である Web Payments を構成する要素のひとつであり、中心を成すものです。Web Payments を構成する要素には下記のようなものがあります。
・Payment Request API
・Payment Handler API
・Payment Method Identifiers
・Basic Card Payment

→Payment Request APIというのはそれ単体で決済完了までできるものではなく、決済の仕組みの中の構成要素の一つなんですね。

>
Payment Request API を使ってできることは、基本的にフォームの置き換えになります。それ自体は支払い処理を行わない、という点に注意してください。つまり、この API を使ってできるのは支払情報を取得するところまでであり、その先の支払い処理は別途開発者が行わなければなりません。

→あくまでも入力フォームの置き換えで、決済処理自体は自分たちでやってね、とのこと。
専用の入力フォームの表示と支払情報の取得までがこのAPIの役割みたいです。

#デモサイト
https://polykart-credential-payment.appspot.com/
先ほどの記事内にリンクがあったので貼っておきます。
>(実際に商品を購入することはできません。また、クレジットカードなどの情報がサーバーに渡されることはありません)。

とのことです。

#統合ガイドの写経
[Payment Request API: 統合ガイド](https://developers.google.com/web/fundamentals/payments/?hl=ja#introducing)
いろいろ書いてありますが、一番下にまとめのコードがあります。このscriptを<body>タグの最後に書きました。

```
function onBuyClicked(event) {
  if (!window.PaymentRequest) {
    return;
  }
  // Payment Request API is available.
  // Stop the default anchor redirect.
  event.preventDefault();

  var supportedInstruments = [{
    supportedMethods: [
      'visa', 'mastercard', 'amex', 'discover', 'maestro',
      'diners', 'jcb', 'unionpay', 'bitcoin'
    ]
  }];

  var details = {
    displayItems: [{
      label: 'Original donation amount',
      amount: { currency: 'USD', value: '65.00' }
    }, {
      label: 'Friends and family discount',
      amount: { currency: 'USD', value: '-10.00' }
    }],
    total: {
      label: 'Total due',
      amount: { currency: 'USD', value : '55.00' }
    }
  };

  var options = {
    requestShipping: true,
    requestPayerEmail: true,
    requestPayerPhone: true,
    requestPayerName: true
  };

  // Initialization
  var request = new PaymentRequest(supportedInstruments, details, options);

  // When user selects a shipping address
  request.addEventListener('shippingaddresschange', e => {
    e.updateWith(((details, addr) => {
      var shippingOption = {
        id: '',
        label: '',
        amount: { currency: 'USD', value: '0.00' },
        selected: true
      };
      // Shipping to US is supported
      if (addr.country === 'US') {
        shippingOption.id = 'us';
        shippingOption.label = 'Standard shipping in US';
        shippingOption.amount.value = '0.00';
        details.total.amount.value = '55.00';
      // Shipping to JP is supported
      } else if (addr.country === 'JP') {
        shippingOption.id = 'jp';
        shippingOption.label = 'International shipping';
        shippingOption.amount.value = '10.00';
        details.total.amount.value = '65.00';
      // Shipping to elsewhere is unsupported
      } else {
        // Empty array indicates rejection of the address
        details.shippingOptions = [];
        return Promise.resolve(details);
      }
      // Hardcode for simplicity
      if (details.displayItems.length === 2) {
        details.displayItems[2] = shippingOption;
      } else {
        details.displayItems.push(shippingOption);
      }
      details.shippingOptions = [shippingOption];

      return Promise.resolve(details);
    })(details, request.shippingAddress));
  });

  // When user selects a shipping option
  request.addEventListener('shippingoptionchange', e => {
    e.updateWith(((details) => {
      // There should be only one option. Do nothing.
      return Promise.resolve(details);
    })(details));
  });

  // Show UI then continue with user payment info
  request.show().then(result => {
    // POST the result to the server
    return fetch('/pay', {
      method: 'POST',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(result.toJSON())
    }).then(res => {
      // Only if successful
      if (res.status === 200) {
        return res.json();
      } else {
        throw 'Failure';
      }
    }).then(response => {
      // You should have received a JSON object
      if (response.success == true) {
        return result.complete('success');
      } else {
        return result.complete('fail');
      }
    }).then(() => {
      console.log('Thank you!',
          result.shippingAddress.toJSON(),
          result.methodName,
          result.details.toJSON());
    }).catch(() => {
      return result.complete('fail');
    });
  }).catch(function(err) {
    console.error('Uh oh, something bad happened: ' + err.message);
  });
}

// Assuming an anchor is the target for the event listener.
document.querySelector('#start').addEventListener('click', onBuyClicked);
```

また、

>Payment Request API shim の読み込み
この開発中の標準 API に随時対応する手間を省くために、この shim をコードの <head> セクションに追加することを強くお勧めします。

とのことなのでこれを`<head>`内に書きました。ちなみに書かなくても動きました。

```
<script src="https://storage.googleapis.com/prshim/v1/payment-shim.js">
```

#結果
![スクリーンショット 2018-03-28 12.45.25.png](https://qiita-image-store.s3.amazonaws.com/0/112614/f2d3d629-4611-ddba-67bf-1e00a6e448a0.png)


ほんとクライアントサイドは簡単できちゃいました。


なお、PAY.JPとの連携をしている記事があったので参考に貼らせてもらいます。
[PAY.JP で Google Chrome の Payment Request API を使って決済する](http://payjp.hatenablog.com/entry/2017/06/09/175451)

#まとめ
このAPIを介してApplePayが使えるようになる、というのが最初のツイートの内容ですね。今でもすでにそれができるようになるJSがあるみたいですけど、もっとシームレスに簡単に連携できるということなのでしょうか。わかりません。

また、最初のリンクに記載がありますが、モバイルでの購入処理の取りやめはパソコンでのそれの2倍らしいです。やっぱりスマホは入力しづらいですもんね。アマゾンなんかは1クリックで買えちゃうのでほんと便利だなと思います。

アマゾンのような会員を抱えているサービスであれば別にわざわざこのAPIを使う必要はなさそうですが、顧客情報・支払情報を持っていない新規サービスには有用だなと思いました。

今後決済のあるサービスを作る場合は検討してみようと思います。