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

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