因工作需要美國及德國電商網站,需要界接 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