# Apple Pay on Web 串接

最近要為網站加上 Apple Pay 付款,從申請 Apple 憑證開始,到網站串接 Apple Pay SDK ,最後再串接藍新、綠界的 API 完成整套的 Apple Pay 付款流程,這篇紀錄一下整個過程以及一些筆記,希望可以幫助到大家。

# 申請 Merchant ID

  1. 登入 Apple Developer

  2. 進入 Certificates, Identifiers & Profiles

    進入 Certificates, Identifiers & Profiles

  3. 進入 Identifiers 新增 Merchant ID

    進入 Identifiers 新增 Merchant IDs

  4. 新增 Merchant ID

    新增 Merchant IDs

  5. 填寫 Merchant ID
    Description 可以隨便寫, Identifier 建議將 domain 反過來寫以確保唯一, Apple 會再加上 merchant. 的前綴

    新增 Merchant IDs

  6. 確定就按下 Register

    按下 Register

  7. 回到 Identifiers ,剛剛設定的 Merchant ID 就已經設定好了

    回到 Identifiers

# 驗證 Merchant Domain

  1. 進入剛剛設定的 Merchant Identifiers ,在 Merchant Domains 按下 Add Domain

    Add Domain

  2. 接著就填要使用 Apple Pay 的 domain

    驗證 Merchant Domain

  3. 接著就會產出一個 apple-developer-merchantid-domain-association.txt 檔案,將這個檔案下載下來放在 https://domain/.well-known/apple-developer-merchantid-domain-association.txt 給 Apple 驗證

    驗證 apple-developer-merchantid-domain-association.txt

  4. 驗證成功後就可以看到 Merchant Domains 已經是綠色的 Verified 的狀態,另外 Verification Expires 會是 SSL 憑證到期日,所以用 Let's Encrypt 的憑證就是三個月內就會到期,建議可以買付費的憑證,這樣就可以撐一年了,至於買 SSL 憑證院長唯一推薦 GoGetSSL (opens new window) 這個網站

    驗證成功

# 申請 Merchant identity certificate

接著就是要申請 Merchant Identity Certificate ,這個憑證是用來啟用 merchant session 用的,在網頁上使用者要發動 Apple Pay 時會使用這個憑證跟 Apple 要一個 merchant session 回來,這樣才能進行接下來的 Apple Pay 付款,詳細可以看這邊

  1. 申請 Merchant Identity Certificate

    申請 Merchant Identity Certificate

  2. 產生 CSR(Certificate Signing Request)

    因為 Mac 上的憑證製作器實在很難用,所以院長都是用 OpenSSL 的指令來產生。

openssl req -new -newkey rsa:2048 -nodes -keyout apple_pay_merchant_id.key -out apple_pay_merchant_id.csr
  1. 上傳 CSR

    上傳 CSR

  2. 下載產生後的證書 cer 檔

    下載產生後的證書 cer 檔

  3. 轉成 pem 備用

openssl x509 -inform der -in apple_pay_merchant_id.cer -out apple_pay_merchant_id.cer.pem

# 申請 Payment processing certificate

再來就是申請 Payment processing certificate ,這個憑證可以解開 Apple Pay token ,如果是串接第三方金流公司(藍新、綠界之類的),申請完把憑證跟 Key 給金流公司就好,金流公司會處理解開 Apple Pay token 的工作。

如果是串接銀行就要自己用這個憑證把 Apple Pay token 解開,再把解開的 payload 傳給銀行,這邊其實還蠻麻煩的,詳細可以參考 輕鬆解密 Apple pay payment token. (跳過第三方金融服務商串接 Apple Pay ) (opens new window) 這篇文章。

  1. 申請 Payment processing certificate

    申請 Payment processing certificate

  2. 詢問是否要用在中國,如果要就選 Yes, 不用就選 No,好像是之後產生 CSR 時要使用不同的演算法

    詢問是否要用在中國

  3. 產生 CSR(Certificate Signing Request)

    一樣用 OpenSSL 的指令來產生,不過這邊的演算法就要用 ECC(256)的演算法。

openssl ecparam -out apple_pay_payment.key -name prime256v1 -genkey
openssl req -new -sha256 -key apple_pay_payment.key -nodes -out apple_pay_payment.csr
  1. 上傳 CSR

    上傳 CSR

  2. 下載產生後的證書 cer 檔

    下載產生後的證書 cer 檔

  3. 申請完就把這個憑證 cer 檔跟 Key 給第三方金流公司

# 網頁程式整合

# 測試環境

# 程式碼哪裡抄

Apple Pay on the Web Demo (opens new window) 下面有 Show Source 的分頁,裡面的 code 抄起來。

Apple Pay 範例網頁程式碼

# 定義 ApplePayPaymentRequest

// Define ApplePayPaymentRequest
const request = {
  'countryCode': 'TW',
  'currencyCode': 'TWD',
  'merchantCapabilities': [
    'supports3DS'
  ],
  'supportedNetworks': [
    'visa',
    'masterCard',
    'jcb'
  ],
  'total': {
    'label': '商店名稱',
    'type': 'final',
    'amount': amount
  }
}

主要改 supportedNetworks 這邊的設定,台灣的話只要支援 visa, masterCard, jcb 就可以了,更多可以參考 Apple Pay 的文件 (opens new window)

再來是改 total.amount 欄位,改成付款的總金額。這邊要注意,金額最好從後端算好傳到前端避免傳輸或是計算過程被篡改。

# 實作 onvalidatemerchant

session.onvalidatemerchant = async event => {
  // Call your own server to request a new merchant session.
  const res = await fetch('/api/validateMerchant',  {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ validationURL: event.validationURL })
  })
  const data = await res.json()
  if (data.status === 'success') {
    session.completeMerchantValidation(data.merchantSession)
  } else {
    session.completeMerchantValidation({
      'status': ApplePaySession.STATUS_FAILURE
    })
  }
}

這個步驟是要驗 Merchant ,將 event.validationURL 傳到 API server ,從 API server 發 request 取得 merchantSession

# API 端
const apple_merchant_id_agent = new https.Agent({
  cert: fs.readFileSync(`../credential/apple_pay_merchant_id.cer.pem`),
  key: fs.readFileSync(`../credential/apple_pay_merchant_id.key`)
})

api.post('/validateMerchant', async (ctx) => {
  return axios.post(ctx.request.body.validationURL, {
    merchantIdentifier: 'merchant.com.example.mystore',
    displayName: 'MyStore',
    initiative: 'web',
    initiativeContext: 'mystore.example.com',
  }, { httpsAgent: apple_merchant_id_agent }).then((body) => {
    return ctx.body = {
      status: 'success',
      merchantSession: body.data
    }
  }).catch((err) => {
    return ctx.body = { 
      status: 'error'
    }
  })
})

API 端這邊就是向 validationURL 發 post 取得 merchantSessionmerchantIdentifierinitiativeContext 要跟 申請 Merchant ID 這邊的設定一樣。

displayName 可以中文,基本上就是填網站名稱, initiative 固定帶 web

最後 axios 還要設定 https.Agent ,把之前 申請的 Merchant identity certificate 附上去。

# 刪掉無用的 handler

onpaymentmethodselected, onshippingmethodselected, onshippingcontactselected 這三個 handler 可以註解或是刪掉,這是讓你在結帳流程時如果有改變付款方式、寄送方式、寄送地址時呼叫的,用意是在這三個階段改變金額用的。

基本上沒啥用,因為一般的流程這些都會在按下 Pay with Apple Pay 按鈕前就就會計算好總金額。

# 實作 onpaymentauthorized

session.onpaymentauthorized = event => {
  // Define ApplePayPaymentAuthorizationResult
  const res = await fetch(`/api/apple_pay`,  {
    method: 'POST',
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      token: event.payment.token.paymentData
    })
  })
  const res = await res.json()
  if (res.status === 'success') {
    const result = {
      'status': ApplePaySession.STATUS_SUCCESS
    }
    session.completePayment(result)
  } else {
    const result = {
      'status': ApplePaySession.STATUS_FAILURE
    }
    session.completePayment(result)
  }
}

這個步驟可以透過 event.payment 取得 payment token ,那藍新只要 payment.token.paymentData 就可以,所以就只要傳 token 到 API server 就可以了。

# API 端
api.post('/apple_pay', async (ctx) => {
  const payload = {
    TimeStamp: Math.round(Date.now() / 1000),
    Version: '1.1',
    MerchantOrderNo: merchant_order_no,
    Amt: Big(amount),
    APPLEPAY: ctx.request.body.token
    APPLEPAYTYPE: '02'
    ProdDesc: 'Apple Pay 付款'
  }
  const postdata = (new URLSearchParams({
    MerchantID_: merchant_id,
    PostData_: toNewebPayPostData(key, iv, payload),
    Pos_: 'JSON'
  })).toString()
  const { data } = await axios.post('https://core.newebpay.com/API/CreditCard', postdata)
  if (data.Status === 'SUCCESS') {
    ctx.body = {
      status: 'success'
    }
  } else {
    ctx.body = {
      status: 'error'
    }
  }
})

API 這邊就看藍新的 API 文件把 token 跟其他必要的參數 POST 到付款的 endpoint ,再根據回傳的 status 將結果回傳給網頁前端。

# 實作 oncancel

session.oncancel = event => {
  alert('取消付款或付款失敗')
}

取消付款或是付款失敗會呼叫 oncancel 這個 handler ,就在這邊 handle 錯誤。

# Apple Pay 按鈕

Apple Pay 按鈕有嚴格的規範,最好是用 Apple Pay on the Web Demo (opens new window) 裡面 Display an Apple Pay button 這邊的產生器來產生下面的 Apple Pay 按鈕程式碼

<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>
<apple-pay-button buttonstyle="black" type="plain" locale="en"></apple-pay-button>

上面的程式碼有用到 Web Component ,如果你的開發環境沒辦法使用 Web Component (像院長用的 Marko (opens new window) 這個樣版引擎就不支援 Web Component ),可以參考 Displaying Apple Pay Buttons Using CSS (opens new window) 這篇文章用 CSS 的方式來 style Apple Pay button 。

另外,如果要完全自己畫的話,要參考 Buttons and Marks (opens new window) 的規範。

# 結語

網站導入 Apple Pay 之後,有滿多使用者使用 Apple Pay 結帳付款的,流程也比起傳統要跳轉藍新公司網頁結帳或是傳統銀行 3D 驗證來的流暢許多,雖然導入的步驟比較多也比較複雜,但是總得來說還是利大於弊的。

# 參考資料

Last Updated: 2022-3-27 6:45:15 ├F10: PM┤
贊助商連結
(adsbygoogle = window.adsbygoogle || []).push({});