このエントリは、Magento2の決済エクステンション開発のクライアントサイド実装について解説します。
Magento2決済エクステンション開発ガイド その1」と「Magento2決済エクステンション開発ガイド その2」を先に読んでおいて頂くと、理解がスムーズに進むと思います。

レイアウトXML

Magentoのフロントエンドカスタマイズでは避けて通れないもの、それがレイアウトXMLです。
決済エクステンションでもレイアウトXMLは必須ですが、基本的に書き方は決まっています。

checkout_index_index.xmlを定義する

決済エクステンションを実装する場合にはcheckout_index_index.xmlを定義します。これは通常のチェックアウト画面用なので、複数配送の場合にはまた違う定義が必要です。
Braintree連携の場合、以下のように定義されています。

<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="after.body.start">
            <referenceBlock remove="true" name="braintree.paypal.component"/>
        </referenceContainer>
        <referenceBlock name="checkout.root">
            <arguments>
                <argument name="jsLayout" xsi:type="array">
                    <item name="components" xsi:type="array">
                        <item name="checkout" xsi:type="array">
                            <item name="children" xsi:type="array">
                                <item name="steps" xsi:type="array">
                                    <item name="children" xsi:type="array">
                                        <item name="billing-step" xsi:type="array">
                                            <item name="component" xsi:type="string">uiComponent</item>
                                            <item name="children" xsi:type="array">
                                                <item name="payment" xsi:type="array">
                                                    <item name="children" xsi:type="array">
                                                        <item name="renders" xsi:type="array">
                                                            <!-- merge payment method renders here -->
                                                            <item name="children" xsi:type="array">
                                                                <item name="braintree" xsi:type="array">
                                                                    <item name="component" xsi:type="string">Magento_Braintree/js/view/payment/braintree</item>
                                                                    <item name="methods" xsi:type="array">
                                                                        <item name="braintree" xsi:type="array">
                                                                            <item name="isBillingAddressRequired" xsi:type="boolean">true</item>
                                                                        </item>
                                                                        <item name="braintree_paypal" xsi:type="array">
                                                                            <item name="isBillingAddressRequired" xsi:type="boolean">false</item>
                                                                        </item>
                                                                    </item>
                                                                </item>
                                                            </item>
                                                        </item>
                                                    </item>
                                                </item>
                                            </item>
                                        </item>
                                    </item>
                                </item>
                            </item>
                        </item>
                    </item>
                </argument>
            </arguments>
        </referenceBlock>
    </body>
</page>

非常に階層の深いXML定義です。この中で重要なポイントは、

<item name="renders" xsi:type="array">
    <!-- merge payment method renders here -->
    <item name="children" xsi:type="array">
        <item name="braintree" xsi:type="array">
            <item name="component" xsi:type="string">Magento_Braintree/js/view/payment/braintree</item>
            <item name="methods" xsi:type="array">
                <item name="braintree" xsi:type="array">
                    <item name="isBillingAddressRequired" xsi:type="boolean">true</item>
                </item>
                <item name="braintree_paypal" xsi:type="array">
                    <item name="isBillingAddressRequired" xsi:type="boolean">false</item>
                </item>
            </item>
        </item>
    </item>
</item>

という部分です。
ここでbraintreeモジュールがどのような決済方法を持っているかを定義しています。

isBillingAddressRequiredは、支払い方法の選択時に、住所入力あるいはアドレス帳からの選択が必要かどうかを指定しています。

独自の決済方法を定義する場合でも、基本的にここで紹介した書き方を真似れば動作するでしょう。

phtmlテンプレート

おもに管理画面用にはなりますが、phtmlテンプレートの作成も必要です。
ここはMagento1と比較的にていて、formとinfoの2種類のテンプレートを定義できます。

formでは決済方法固有の入力欄を定義でき、infoでは決済の結果などを表示するとができます。
残念ながら、フロントエンドの入力欄ではphtmlテンプレートは使われません。(infoはマイページなどで使います)

JavaScript

JavaScript部分はMagento2で新たに導入された機構です。
お作法どおり書かないと全く動作しないので、注意が必要です。

RequireJS

Magento2ではRequireJSによるJavaScriptのモジュール化を行っています。

決済方法の定義

決済方法を定義したい場合は、「エクステンションディレクトリ/web/js/view/payment」にレイアウトXMLのcomponentで指定したファイル名のJavaScriptファイルを作成します。
先程の例で言うと、「braintree.js」というファイル名です。

このファイルには以下のような内容を記述します。

define(
    [
        'uiComponent',
        'Magento_Checkout/js/model/payment/renderer-list'
    ],
    function (
        Component,
        rendererList
    ) {
        'use strict';

        var config = window.checkoutConfig.payment,
            braintreeType = 'braintree',
            payPalType = 'braintree_paypal';

        if (config[braintreeType].isActive) {
            rendererList.push(
                {
                    type: braintreeType,
                    component: 'Magento_Braintree/js/view/payment/method-renderer/hosted-fields'
                }
            );
        }

        if (config[payPalType].isActive) {
            rendererList.push(
                {
                    type: payPalType,
                    component: 'Magento_Braintree/js/view/payment/method-renderer/paypal'
                }
            );
        }

        /** Add view logic here if needed */
        return Component.extend({});
    }
);

この記述は大枠書式が決まっていて、

define(
    [
        'uiComponent',
        'Magento_Checkout/js/model/payment/renderer-list'
    ],
    function (
        Component,
        rendererList
    ) {
        'use strict';

まではほぼ固定です。
その後に続く、

rendererList.push(
{
    type: braintreeType,
    component: 'Magento_Braintree/js/view/payment/method-renderer/hosted-fields'
}
);

という部分で決済方法を追加しています。

method-rendererの定義

次に、決済方法ごとの詳細な実装を行います。
先程の決済方法を追加していた場所で、componentとして定義した名称のJavaScriptを定義することで、決済方法固有の実装を定義できます。
hosted-fieldsの場合は以下のようになっています。

define([
    'jquery',
    'Magento_Braintree/js/view/payment/method-renderer/cc-form',
    'Magento_Braintree/js/validator',
    'Magento_Vault/js/view/payment/vault-enabler',
    'mage/translate'
], function ($, Component, validator, VaultEnabler, $t) {
    'use strict';

    return Component.extend({

        defaults: {
            template: 'Magento_Braintree/payment/form',
            clientConfig: {

                /**
                 * {String}
                 */
                id: 'co-transparent-form-braintree'
            },
            isValidCardNumber: false
        },

===中略===        

        /**
         * Trigger order placing
         */
        placeOrderClick: function () {
            if (this.validateCardType()) {
                $(this.getSelector('submit')).trigger('click');
            }
        },

        /**
         * @returns {String}
         */
        getVaultCode: function () {
            return window.checkoutConfig.payment[this.getCode()].ccVaultCode;
        }
    });
});

基本的にはComponentオブジェクトを拡張し、必要な処理を追加します。
ここで定義したメソッドは、次に紹介するknockout.jsが処理するテンプレートで呼び出すことができます。

knockout.jsのテンプレート

Magento2ではしばしばknockout.jsによるクライアントサイドでの描画が行われています。
チェックアウト画面でも多用されていますが、決済エクステンションを開発する場合には必須です。
このテンプレートと、先程のJavaScriptモジュールの関連性を理解しておかないと、うまく実装することができません。

テンプレートの定義

テンプレートを定義する場合、「エクステンションディレクトリ/web/template/payment」以下にファイルを作成します。ファイル名はRequireJSのモジュールで以下のように定義したものに「.html」をつけます。

template: 'Magento_Braintree/payment/form',

ファイルの中身については、少し長いので、実際にMagento2のソースコードを見てください。

気をつけたいポイントとしては、

<label data-bind="attr: {for: getCode() + '_cc_number'}" class="label">
    <span><!-- ko i18n: 'Credit Card Number'--><!-- /ko --></span>
</label>

<!-- ko foreach: {data: getCcAvailableTypes(), as: 'item'} -->
<li class="item" data-bind="css: {
 _active: $parent.selectedCardType() == item,
 _inactive: $parent.selectedCardType() != null && $parent.selectedCardType() != item
} ">
  <!--ko if: $parent.getIcons(item) -->
  <img data-bind="attr: {
    'src': $parent.getIcons(item).url,
    'width': $parent.getIcons(item).width,
    'height': $parent.getIcons(item).height
  }">
  <!--/ko-->
</li>
<!--/ko-->

といったknockout.js特有の記法です。
JavaScriptの関数については、RequireJSモジュールで定義したものがそのまま使えます。
親クラスのメソッドについては$parentをつければ呼び出すことができます。

設定値の受け渡し

さて、Magentoの管理画面などで設定した値を受け渡しする場合は、ConfigProvierを介したやり取りが必要になります。
この場合、事前にサーバーサイドの実装で定義しておく必要があります。

ConfigProvider

ConfigProviderは、決済エクステンションに関連する設定値の取得を行うことを目的としたクラスです。
ConfigProviderInterfaceを実装したクラスであればなんでも構いませんが、下記のようにgetConfigメソッドで設定値を配列の形で返すように実装します。

    public function getConfig()
    {
        return [
            'payment' => [
                self::CODE => [
                    'isActive' => $this->config->isActive(),
                    'clientToken' => $this->getClientToken(),
                    'ccTypesMapper' => $this->config->getCctypesMapper(),
                    'sdkUrl' => $this->config->getSdkUrl(),
                    'countrySpecificCardTypes' => $this->config->getCountrySpecificCardTypeConfig(),
                    'availableCardTypes' => $this->config->getAvailableCardTypes(),
                    'useCvv' => $this->config->isCvvEnabled(),
                    'environment' => $this->config->getEnvironment(),
                    'kountMerchantId' => $this->config->getKountMerchantId(),
                    'hasFraudProtection' => $this->config->hasFraudProtection(),
                    'merchantId' => $this->config->getMerchantId(),
                    'ccVaultCode' => self::CC_VAULT_CODE
                ],
                Config::CODE_3DSECURE => [
                    'enabled' => $this->config->isVerify3DSecure(),
                    'thresholdAmount' => $this->config->getThresholdAmount(),
                    'specificCountries' => $this->config->get3DSecureSpecificCountries()
                ],
            ]
        ];
    }

di.xmlの定義

etc/frontend/di.xmlに以下のような記述をすると、値を受け渡しすることができます。

    <type name="Magento\Checkout\Model\CompositeConfigProvider">
        <arguments>
            <argument name="configProviders" xsi:type="array">
                <item name="braintree_config_provider" xsi:type="object">Magento\Braintree\Model\Ui\ConfigProvider</item>
                <item name="braintree_paypal_config_provider" xsi:type="object">Magento\Braintree\Model\Ui\PayPal\ConfigProvider</item>
            </argument>
        </arguments>
    </type>
    <type name="Magento\Payment\Model\CcGenericConfigProvider">
        <arguments>
            <argument name="methodCodes" xsi:type="array">
                <item name="braintree" xsi:type="const">Magento\Braintree\Model\Ui\ConfigProvider::CODE</item>
            </argument>
        </arguments>
    </type>

JavaScript側での値取得

JavaScript側では、

window.checkoutConfig.payment

に配列で格納されている設定値を取り出すことができます。配列の添字は、ConfigProviderのgetConfigが返す配列で使用したものと同じです。
決済方法のコードと揃えておくと、誤解が少なくて良いでしょう。

あとはJavaScript側で値を取得するメソッドを定義し、各種処理やテンプレートで利用します。

まとめ

3回に分けてお送りしてきたMagento2決済エクステンション開発ガイドもこれで終わりです。
本当は更に細かい実装の調整などあるのですが、個別ケースに踏み込むこともあるので、一旦ここで終わります。
今回のまとめは以下のとおりです。

  • 決済エクステンションの開発にはレイアウトXMLの定義が必要
  • phtmlテンプレートは管理画面とマイページなどで使用する
  • フロントエンドの殆どはJavaScriptでできている
  • RequireJSとknockout.jsを理解しよう
  • ConfigProviderを使えば設定値をJavaScriptに渡すことができる