ブラウザだけでの決済がそろそろきそう

Blog Single

3月20日にW3Cにて「Candidate Recommendation(勧告候補)」となった「Payment Request API」がSafari最新バージョンで搭載。ApplePayトランザクションをWeb上で実行できるように。
ちなみにChromeでは前から搭載されております。

W3C | Payment Request API

Webkit.org | Payment Request API

それにしても参加企業がMicrosoft,Google,Mozilla,Facebookと錚々たる感じで標準化へ爆走している感w

■早速動きを見てみよう

Webkit.org | Payment Request API Demo

↑を対応しているバージョンのSafari(iPhoneかmac)でみると「Buy widh ApplePay」のボタンが表示。クリックするとiPhoneなら「Walletアプリ」が起動。
Chromeとかでみると「Apple Pay not supported」と出る。

■ソース確認

上記のボタン出し分けの部分を実現しているのが以下3点

HTML

<div id="live-button" class="hidden"></div>

JS

window.PaymentRequest
window.ApplePaySession
ApplePaySession.canMakePayments()

window.addEventListener("DOMContentLoaded", () => {
    const applePayButton = document.querySelector("#live-button");
    // ブラウザ対応チェック
    if (!window.PaymentRequest || !window.ApplePaySession || !ApplePaySession.canMakePayments())
        applePayButton.classList.add("apple-pay-not-supported");
    else {
        applePayButton.classList.add("apple-pay-button");
        applePayButton.addEventListener("click", applePayButtonClicked);   
    }
    applePayButton.classList.remove("hidden");
});

CSS

@supports なんてあるのは今回初めて知った。

@supports (-webkit-appearance: -apple-pay-button) {
    .apple-pay-button {
        display: inline-block;
        -webkit-appearance: -apple-pay-button;
        -apple-pay-button-type: buy;
        width: 80%;
    }
}
.apple-pay-not-supported::before {
    content: "Apple Pay not supported";
    color: grey;
}
.hidden {
    visibility: hidden;
}

続いて

■Payment Requestの初期化

JS


// メソッド設定 今後はsupportedMethodsを切り替える感じになる。 const applePayMethod = { supportedMethods: "https://apple.com/apple-pay", data: { version: 1, merchantIdentifier: "org.webkit.demo", merchantCapabilities: ["supports3DS", "supportsCredit", "supportsDebit"], supportedNetworks: ["amex", "discover", "masterCard", "visa"], countryCode: "US", }, }; // 支払い方法詳細 const detailsForShippingOption = selectedShippingOption => { const shippingOptions = [ { id: "ground", label: "Ground Shipping", amount: { value: "5.00", currency: "USD" }, overrideTotal: { value: "25.00", currency: "USD" }, }, { id: "express", label: "Express Shipping", amount: { value: "10.00", currency: "USD" }, overrideTotal: { value: "30.00", currency: "USD" }, }, ]; var shippingOptionIndex = null; for (var shippingOption in shippingOptions) { if (shippingOptions[shippingOption].id !== selectedShippingOption) continue; shippingOptionIndex = shippingOption; break; } if (!shippingOptionIndex) return { }; shippingOptions[shippingOptionIndex].selected = true; return { total: { label: "WebKit", amount: shippingOptions[shippingOptionIndex].overrideTotal, }, displayItems: [ { label: shippingOptions[shippingOptionIndex].label, amount: shippingOptions[shippingOptionIndex].amount, }, ], shippingOptions: shippingOptions, }; }; // 支払いオプション(電話番号,Email入力要求もここで設定できる) const options = { requestShipping: true, }; const paymentRequest = new PaymentRequest([applePayMethod], detailsForShippingOption("ground"), options);

■その他

JS


// Appleから返却させるレシート情報の整合性チェック // ここではAjaxで叩いてphp側で処理をしているイメージ paymentRequest.onmerchantvalidation = event => { fetch("merchant-validation.php", { body: JSON.stringify({ validationURL: event.validationURL }), method: "POST", }).then(response => event.complete(response.json())); }; // お届け先変更のイベント追加 paymentRequest.onshippingaddresschange = event => { const detailsUpdate = detailsForShippingOption(paymentRequest.shippingOption); event.updateWith(detailsUpdate); };

■まとめ

まだ正式リリースにあたる「勧告(Recommendation)」までに「勧告案(Proposed Recommendation)」もあるため、色々変わる部分もあるだろうけど大枠はこんな感じ。iOSアプリで課金系を触った人なら分かると思うけど、マーチャント検証など何気に面倒くさいので、そこまでは試せてません。
が、デモだけでも結構触りは確認できるんじゃなかろうか。

一応まとめたソース貼っときます。
Https環境じゃないと動かないけどネ!

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name=”robots” content=”noindex”>
    <title>Payment Request Demo</title>
    <style type="text/css">
        @supports (-webkit-appearance: -apple-pay-button) {
            .apple-pay-button {
                display: inline-block;
                -webkit-appearance: -apple-pay-button;
                -apple-pay-button-type: buy;
                width: 80%;
            }
        }

        .apple-pay-not-supported::before {
            content: "Apple Pay not supported";
            color: grey;
        }

        .hidden {
            visibility: hidden;
        }

        #black-buttons > .apple-pay-button {
            -apple-pay-button-style: black;
        }

        #white-buttons > .apple-pay-button {
            -apple-pay-button-style: white-outline;
        }

        #demo {
            border: 1px solid black;
            border-radius: 5px;
            text-align: center;
        }

        #description {
            font-family: -apple-system;
            font-style: italic;
            font-size: 25px;
        }
    </style>
    <script type="text/javascript">
        (() => {
            "use strict"
            async function applePayButtonClicked(event)
            {
                const applePayMethod = {
                    supportedMethods: "https://apple.com/apple-pay",
                    data: {
                        version: 1,
                        merchantIdentifier: "org.webkit.demo",
                        merchantCapabilities: ["supports3DS", "supportsCredit", "supportsDebit"],
                        supportedNetworks: ["amex", "discover", "masterCard", "visa"],
                        countryCode: "US",
                    },
                };

                const detailsForShippingOption = selectedShippingOption => {
                    const shippingOptions = [
                        {
                            id: "ground",
                            label: "Ground Shipping",
                            amount: { value: "5.00", currency: "USD" },
                            overrideTotal: { value: "25.00", currency: "USD" },
                        },
                        {
                            id: "express",
                            label: "Express Shipping",
                            amount: { value: "10.00", currency: "USD" },
                            overrideTotal: { value: "30.00", currency: "USD" },
                        },
                    ];

                    var shippingOptionIndex = null;
                    for (var shippingOption in shippingOptions) {
                        if (shippingOptions[shippingOption].id !== selectedShippingOption)
                            continue;
                        shippingOptionIndex = shippingOption;
                        break;
                    }

                    if (!shippingOptionIndex)
                        return { };

                    shippingOptions[shippingOptionIndex].selected = true;

                    return {
                        total: {
                            label: "WebKit",
                            amount: shippingOptions[shippingOptionIndex].overrideTotal,
                        },
                        displayItems: [
                            {
                                label: shippingOptions[shippingOptionIndex].label,
                                amount: shippingOptions[shippingOptionIndex].amount,
                            },
                        ],
                        shippingOptions: shippingOptions,
                    };
                };

                const options = {
                    requestShipping: true,
                };

                const paymentRequest = new PaymentRequest([applePayMethod], detailsForShippingOption("ground"), options);

                paymentRequest.onmerchantvalidation = event => {
                    fetch("merchant-validation.php", {
                        body: JSON.stringify({ validationURL: event.validationURL }),
                        method: "POST",
                    }).then(response => event.complete(response.json()));
                };

                paymentRequest.onshippingaddresschange = event => {
                    const detailsUpdate = detailsForShippingOption(paymentRequest.shippingOption);
                    event.updateWith(detailsUpdate);
                };

                paymentRequest.onshippingoptionchange = event => {
                    const detailsUpdate = detailsForShippingOption(paymentRequest.shippingOption);
                    event.updateWith(detailsUpdate);
                };

                const response = await paymentRequest.show();
                response.complete("success");
            }

            window.addEventListener("DOMContentLoaded", () => {
                const applePayButton = document.querySelector("#live-button");
                if (!window.PaymentRequest || !window.ApplePaySession || !ApplePaySession.canMakePayments())
                    applePayButton.classList.add("apple-pay-not-supported");
                else {
                    applePayButton.classList.add("apple-pay-button");
                    applePayButton.addEventListener("click", applePayButtonClicked);   
                }
                applePayButton.classList.remove("hidden");
            });
        })();
    </script>
</head>
<body>
    <div id="demo">
        <div id="description">Genuine Squirrelfish<br>$20.00 + shipping<br><br></div>
        <div id="live-button" class="hidden"></div>
    </div>
</body>
</html>
Posted by YamamotoDaiki
映画をこよなく愛するFOX HOUND古株エンジニア。 PHPから始まり今ではnodeJSやらSwift、Solidityなど色々な言語に触る機会が多し。

Other Posts: