PAY.JP の Checkout 用の React.js コンポーネントをつくってみた


PAY.JP の Checkout は、script タグ1行で、 デザインされた決済フォーム、カード情報のバリデーション、カード情報のトークン化を行うフォームを生成するライブラリです。これを React.js で使うためのワークアラウンドをまとめた React コンポーネントを作成しました。代案は、payjp.js です。この記事では、両者の優劣を論じません。判断の資料を提供するのが目的です。

コード

工夫が必要だったところ

  • React.js のコンポーネントでは script タグを render できないとのことなので、appendChild しました。window.document に appendChild すると、payjp の生成するボタンが SPA のあらゆる画面に表示されます。ref="payjpCheckoutRef" してその中に appendChild することで、回避できました。
  • data-on-createddata-on-failed で、コールバック関数の文字列を設定できます。しかしこれは window 直下にある関数にしか対応していません。そこで、componentWillMount で window.reactPayjpCheckoutOnCreated = this.onCreated; として、componentWillUnmount で window.reactPayjpCheckoutOnCreated = null; することにしました。それから、ここで呼び出す関数を prop で受け取る場合、実行時に this も意図したものにしておく必要があります。これも window.reactPayjpCheckoutContext = this; といったように退避させておき、いつもなら this.props.onCreatedHandler(); と呼び出す処理を window.reactPayjpCheckoutContext.props.onCreatedHandler(); で呼び出せるようにしました。
  • payjp の checkout では、エラーメッセージ(「カードの有効期限がきれています」)の表示を window.alert で行うことを強制されます。このコンポーネントがマウントされている間は、 window.alert = () => {}; とすることで、この表示を消しました。
  • エラーメッセージは、data-on-failed で自分たちのアプリのモーダルダイアログを表示するアクションを呼び出すことで表示できます。material-ui の Dialog であれば、z-index の値を大きくしておく必要があります。テーマ で以下のように設定しました。
    zIndex: {
        dialog: 100000,
    },

  • componentWillUnmount で window.PayjpCheckout = null; しているのは次の理由です。PAY.JP のボタンが表示されるには、script タグで読み込む https://checkout.pay.jp/ の JS が iframe.html というのを呼び出し、その iframe.html が server.css と iframe.js を呼び出すことで実現しています。この過程で window.PayjpCheckout オブジェクトが作成されるのですが、これがある状態で https://checkout.pay.jp/ の JS を実行しても、iframe.html が呼び出されません。画面遷移して、再びPAY.JP のボタンが表示されるべき画面に戻ってきたときに、ボタンを表示するために、window.PayjpCheckout を falsy にしておく必要がありました。
  • shouldComponentUpdate でつねに false を返すようにしているのは、このコンポーネントは一度 render されれば十分だからです。