
因工作需要美國及德國電商網站,需要界接 Apple Pay。
Apple Pay / Google Pay 和第三方支付最大的不同是,第三方金流公司會協助處理和銀行帳務問題,但 Apple Pay / Google Pay 並不會。
在 Apple 官方的成功案例中 得知自己對接 Apple Pay 及銀行的公司規模都相當的大,其他大多數都是透過 Payment Provider,我猜可能大部分的銀行並沒有這麼標準及各國法規都不太一樣,各家銀行如果資料交換失敗,要處理的帳務問題就會很多,處理這段的問題是一般公司無法負擔的。
早期各家瀏覽器都是各自載入 JS Lib 去實作,後面 W3C 網站對瀏覽器的對支付訂義標準規格,現今Safari 及 Chrome 都己實作,PaymentRequest Api。
(相容性)
當使用者按下付款按鈕 > 前端 Call 後端 Api 去和蘋果驗證金流商戶,並建立交易的 session,取得用戶端 token > 此時 iphone 會請求使用者刷臉或指紋驗證 > Call 自己的後端 Api,向金流商請求建立訂單。








將下載下來的 cer 拖進 Keychain Access 的 login, 剛拖進來會發現憑證是 certificate is not trusted。

此時到 APPLE PKI 網站
安裝圖中紅框選取的憑證後,剛安裝憑證就會變成 This certificate vaild.

右鍵 > Export ( 密碼可隨便輸 )

輸入驗證域名 > 下載驗證檔案 > 放在該台網站伺服器 > 按下 Verify按鈕

參考蘋果官方的 Apple Pay Live Demo,可以從範例程式得知,Apple Pay 的前端主要的事件流程有:
<script src="https://applepay.cdn-apple.com/jsApi/v1/apple-pay-sdk.js"></script>
<style>
apple-pay-button {
--apple-pay-button-width: 150px;
--apple-pay-button-height: 30px;
--apple-pay-button-border-radius: 3px;
--apple-pay-button-padding: 0px 0px;
--apple-pay-button-box-sizing: border-box;
}
</style>
<h1>Apple Pay Welcome</h1>
<h2>Apple Pay button is only show in safari!!! </h2>
<apple-pay-button buttonstyle="black" type="plain" locale="en" onclick="onApplePayButtonClicked()">123</apple-pay-button>
<script>
function onApplePayButtonClicked() {
if (!ApplePaySession) {
return;
}
// Define ApplePayPaymentRequest
const request = {
"countryCode": "US",
"currencyCode": "USD",
"merchantCapabilities": [
"supports3DS"
],
"supportedNetworks": [
"visa",
"masterCard",
"amex",
"discover"
],
"total": {
"label": "付給 xxx 公司",
"type": "final",
"amount": "0.1"
}
};
// Create ApplePaySession
const session = new ApplePaySession(3, request);
const failObject = {
'status': ApplePaySession.STATUS_FAILURE
}
session.onvalidatemerchant = event => {
const validationURL = event.validationURL;
const failObject = {
'status': ApplePaySession.STATUS_FAILURE
}
getApplePaySession(validationURL).then(function (response) {
debugger
let result = JSON.parse(response)
session.completeMerchantValidation(result);
}).then(function (response) {
session.completeMerchantValidation(failObject)
}).catch(err => {
session.completeMerchantValidation(failObject)
})
};
session.onpaymentauthorized = event => {
/*alert('onpaymentauthorized' + JSON.stringify(event.payment.token.paymentData))*/
var paymentDataString =
JSON.stringify(event.payment.token.paymentData);
var paymentDataBase64 = btoa(paymentDataString);
debugger
let data = {
amount: request.total.amount,
paymentTokenObject: paymentDataBase64
}
paymentProcess(data).then(function (response) {
if (response === true) {
/*alert('true')*/
const result = {
"status": ApplePaySession.STATUS_SUCCESS
};
session.completePayment(result);
} else {
session.completePayment(failObject);
}
//let result = JSON.parse(response)
//session.completeMerchantValidation(result);
}).then(function (response) {
session.completeMerchantValidation(failObject)
}).catch(err => {
session.completeMerchantValidation(failObject)
})
// Define ApplePayPaymentAuthorizationResult
};
session.oncancel = event => {
alert('oncancel')
session.abort(); // maybe not*/
};
session.begin();
}
// 驗證商戶
function getApplePaySession(url) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/applepay/ValidateMerchant');
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(JSON.parse(xhr.response));
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify({ validationUrl: url }));
});
}
// 付款
function paymentProcess(data) {
return new Promise(function (resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/applepay/paymentProcess');
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
debugger
resolve(JSON.parse(xhr.response));
} else {
reject({
status: this.status,
statusText: xhr.statusText
});
}
};
xhr.onerror = function () {
reject({
status: this.status,
statusText: xhr.statusText
});
};
xhr.setRequestHeader("Content-Type", "application/json");
xhr.send(JSON.stringify(data));
});
}
</script>
/// <summary>
/// 商戶驗證
/// </summary>
[HttpPost]
public JsonResult ValidateMerchant(VerifyMerchantRequest request) {
string strResult = string.Empty;
try {
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
ServicePointManager.Expect100Continue = false;
System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12;
/* Merchant Identity憑證 */
string certPath = Request.MapPath(@"~/App_Data/ApplePay.p12"); //Merchant Identifier憑證路徑
string certPwd = "123"; //Merchant Identifier憑證密碼
X509Certificate2 cert = new X509Certificate2(certPath, certPwd, X509KeyStorageFlags.MachineKeySet);
/* 建立PayLoad */
var payload = new {
displayName = "letgo", // 名稱
initiative = "web", // 網頁
initiativeContext = "adm.letgo.com.tw", // 域名
merchantIdentifier = "merchant.letgo.com.tw.testPayment", // 商戶號
};
string strPayLoad = JsonConvert.SerializeObject(payload);
/* 將Payload以POST方式拋送至Apple提供的validationURL */
/* HTTP Request需以Merchant Identity憑證送出 */
/* 驗證成功後,Apple將會回傳Merchant Session物件*/
#region HTTP Web Result
HttpWebRequest httpRequest = (HttpWebRequest)HttpWebRequest.Create(request.ValidationUrl);
httpRequest.Method = WebRequestMethods.Http.Post;
httpRequest.ContentType = "application/json";
httpRequest.ContentLength = strPayLoad.Length;
httpRequest.ClientCertificates.Add(cert);
using (StreamWriter sw = new StreamWriter(httpRequest.GetRequestStream())) {
sw.Write(strPayLoad);
sw.Flush();
sw.Close();
}
HttpWebResponse response = httpRequest.GetResponse() as HttpWebResponse;
using (StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8)) {
strResult = sr.ReadToEnd();
sr.Close();
}
#endregion HTTP Web Result
}
catch (Exception ex) {
}
finally {
}
/* 將Merchant Session物件回應至Client端*/
return Json(strResult);
}
/// <summary>
/// 付款
/// </summary>
/// <param name="paymentProcessRequest"></param>
/// <returns></returns>
[HttpPost]
public async JsonResult PaymentProcess(PaymentProcessRequest)
{
// todo 和金流商串接,呼叫你的金流商付款 Api
}
}
錯誤的憑證蘋果的 Api gateway 不會回應你,請仔細檢查商戶或域名驗證、請求時帶的憑證,傳遞的 Payload 一定要正確。

主因 Cybersource沒有提供 Apple Pay 的測試環境,請直接用正式環境,進行開發。
npm install @types/applepayjs --save --dev
依據官方文件蘋果會通知憑證失效,但兩年請重新做一次 p12 及上傳金流商的 CSR
