透過安全付款確認機制進行驗證

商家可以使用安全付款確認 (SPC) 驗證特定信用卡或銀行帳戶的嚴格客戶驗證 (SCA) 程序。WebAuthn 會執行驗證 (經常透過生物特徵辨識)。您必須預先註冊 WebAuthn,詳情請參閱註冊安全付款確認

一般實作方式

SPC 最常見的用途是消費者 信用卡發卡機構或銀行要求付款人驗證。

驗證工作流程。

以下說明驗證流程:

  1. 客戶提供付款憑證 (例如信用卡) 資料) 提供給商家。
  2. 商家要求付款憑證對應的發卡機構或銀行 則需透過其他方式驗證付款人。這個 舉例來說 EMV® 3-D Secure
    • 是否 RP 要求商家使用 SPC,且使用者先前 受限方所註冊的憑證 ID 清單 付款人和挑戰
    • 如果不需要驗證,商家可以繼續 完成交易。
  3. 如果需要進行驗證,商家可以判斷瀏覽器是否支援 SPC
    • 如果瀏覽器不支援 SPC,請按照現有的 驗證流程
  4. 商家叫用 SPC。瀏覽器隨即會顯示確認對話方塊。
    • 如果沒有從 RP 傳遞的憑證 ID,請改用 現有驗證流程 驗證成功後,建議使用 SPC 註冊 簡化日後的驗證作業
  5. 使用者確認並驗證 藉此付款。
  6. 商家收到驗證的憑證。
  7. 受限方收到商家提供的憑證,並驗證 真實性
  8. RP 將驗證結果傳送給商家。
  9. 商家會向使用者顯示訊息,指出該筆付款是否 無論成功還是失敗
,瞭解如何調查及移除這項存取權。

特徵偵測

如要偵測瀏覽器是否支援 SPC,您可以傳送假呼叫給 canMakePayment()

複製及貼上以下程式碼,即可偵測商家網站上的 SPC。

const isSecurePaymentConfirmationSupported = async () => {
  if (!'PaymentRequest' in window) {
    return [false, 'Payment Request API is not supported'];
  }

  try {
    // The data below is the minimum required to create the request and
    // check if a payment can be made.
    const supportedInstruments = [
      {
        supportedMethods: "secure-payment-confirmation",
        data: {
          // RP's hostname as its ID
          rpId: 'rp.example',
          // A dummy credential ID
          credentialIds: [new Uint8Array(1)],
          // A dummy challenge
          challenge: new Uint8Array(1),
          instrument: {
            // Non-empty display name string
            displayName: ' ',
            // Transparent-black pixel.
            icon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg==',
          },
          // A dummy merchant origin
          payeeOrigin: 'https://non-existent.example',
        }
      }
    ];

    const details = {
      // Dummy shopping details
      total: {label: 'Total', amount: {currency: 'USD', value: '0'}},
    };

    const request = new PaymentRequest(supportedInstruments, details);
    const canMakePayment = await request.canMakePayment();
    return [canMakePayment, canMakePayment ? '' : 'SPC is not available'];
  } catch (error) {
    console.error(error);
    return [false, error.message];
  }
};

isSecurePaymentConfirmationSupported().then(result => {
  const [isSecurePaymentConfirmationSupported, reason] = result;
  if (isSecurePaymentConfirmationSupported) {
    // Display the payment button that invokes SPC.
  } else {
    // Fallback to the legacy authentication method.
  }
});

驗證使用者

如要驗證使用者,請使用以下指令叫用 PaymentRequest.show() 方法。 secure-payment-confirmation 和 WebAuthn 參數:

以下是您應提供給付款方式的 data 屬性 SecurePaymentConfirmationRequest 的參數。

參數 說明
rpId RP 來源的主機名稱,格式為 RP ID。
challenge 可防止重送攻擊的隨機挑戰。
credentialIds 憑證 ID 的陣列。在 WebAuthn 的驗證中,allowCredentials 屬性接受 PublicKeyCredentialDescriptor 物件陣列,但在 SPC 中,您只會傳遞憑證 ID 清單。
payeeName (非必要) 收款人名稱。
payeeOrigin 收款人來源。在上述情境中,這是商家的來源。
instrument displayName 的字串,以及指向圖片資源的 icon 網址。iconMustBeShown 的選用布林值 (預設為 true),用於指定圖示必須成功擷取並顯示,要求才能成功。
timeout 簽署交易逾時時間 (毫秒)
extensions 已新增至 WebAuthn 呼叫的擴充功能。您不必指定「payment」。

請參考以下程式碼範例:

// After confirming SPC is available on this browser via a feature detection,
// fetch the request options cross-origin from the RP server.
const options = fetchFromServer('https://rp.example/spc-auth-request');
const { credentialIds, challenge } = options;

const request = new PaymentRequest([{
  // Specify `secure-payment-confirmation` as payment method.
  supportedMethods: "secure-payment-confirmation",
  data: {
    // The RP ID
    rpId: 'rp.example',

    // List of credential IDs obtained from the RP server.
    credentialIds,

    // The challenge is also obtained from the RP server.
    challenge,

    // A display name and an icon that represent the payment instrument.
    instrument: {
      displayName: "Fancy Card ****1234",
      icon: "https://rp.example/card-art.png",
      iconMustBeShown: false
    },

    // The origin of the payee (merchant)
    payeeOrigin: "https://merchant.example",

    // The number of milliseconds to timeout.
    timeout: 360000,  // 6 minutes
  }
}], {
  // Payment details.
  total: {
    label: "Total",
    amount: {
      currency: "USD",
      value: "5.00",
    },
  },
});

try {
  const response = await request.show();

  // response.details is a PublicKeyCredential, with a clientDataJSON that
  // contains the transaction data for verification by the issuing bank.
  // Make sure to serialize the binary part of the credential before
  // transferring to the server.
  const result = fetchFromServer('https://rp.example/spc-auth-response', response.details);
  if (result.success) {
    await response.complete('success');
  } else {
    await response.complete('fail');
  }
} catch (err) {
  // SPC cannot be used; merchant should fallback to traditional flows
  console.error(err);
}

.show() 函式會產生 PaymentResponse 物件,但 details 中的公開金鑰憑證, 包含交易資料的 clientDataJSON (payment) 以進行 RP 驗證

產生的憑證必須跨來源傳輸至 RP,並 已驗證。

RP 驗證交易的方式

在 RP 伺服器上驗證交易資料是 付款程序。

為驗證交易資料,RP 可以遵循 WebAuthn 的驗證斷言驗證程序。 另外,他們必須 驗證 payment

clientDataJSON 酬載範例:

{
  "type":"payment.get",
  "challenge":"SAxYy64IvwWpoqpr8JV1CVLHDNLKXlxbtPv4Xg3cnoc",
  "origin":"https://spc-merchant.glitch.me",
  "crossOrigin":false,
  "payment":{
    "rp":"spc-rp.glitch.me",
    "topOrigin":"https://spc-merchant.glitch.me",
    "payeeOrigin":"https://spc-merchant.glitch.me",
    "total":{
      "value":"15.00",
      "currency":"USD"
    },
    "instrument":{
      "icon":"https://cdn.glitch.me/94838ffe-241b-4a67-a9e0-290bfe34c351%2Fbank.png?v=1639111444422",
      "displayName":"Fancy Card 825809751248"
    }
  }
}
敬上
  • rp 與 RP 的起點相符。
  • topOrigin 符合 RP 預期的頂層起點 ( 商家來源)。
  • payeeOrigin 與應收款的收款人來源相符 向使用者顯示
  • total 與應顯示的交易金額相符 以便傳達給使用者
  • instrument 與應有的付款方式詳細資料相符 並未向使用者顯示
const clientData = base64url.decode(response.clientDataJSON);
const clientDataJSON = JSON.parse(clientData);

if (!clientDataJSON.payment) {
  throw 'The credential does not contain payment payload.';
}

const payment = clientDataJSON.payment;
if (payment.rp !== expectedRPID ||
    payment.topOrigin !== expectedOrigin ||
    payment.payeeOrigin !== expectedOrigin ||
    payment.total.value !== '15.00' ||
    payment.total.currency !== 'USD') {
  throw 'Malformed payment information.';
}

一旦通過所有驗證標準,RP 就能通知 代表交易成功的商家。

後續步驟