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

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

一般實作方式

SPC 最常見的用途是客戶在商家的網站上消費,而信用卡發卡機構或銀行要求付款人進行驗證。

驗證工作流程。

以下說明驗證流程:

  1. 客戶將付款憑證 (例如信用卡資訊) 提供給商家。
  2. 如果付款人需要其他驗證,商家會詢問付款憑證的對應核發機構或銀行 (依賴方或 RP)。這種廣告交易平台可能就以 EMV® 3-D Secure 為例。
    • 如果 RP 要求商家使用 SPC,且使用者先前已註冊,則 RP 回應時,會提供付款人註冊的憑證 ID 和驗證問題。
    • 如果不需要驗證,商家可以繼續完成交易。
  3. 如果需要進行驗證,商家可以判斷瀏覽器是否支援 SPC
    • 如果瀏覽器不支援 SPC,請繼續現有的驗證流程。
  4. 商家叫用 SPC。瀏覽器隨即顯示確認對話方塊。
    • 如果沒有從 RP 傳遞的憑證 ID,請改回現有的驗證流程。驗證成功後,請考慮使用 SPC 註冊來簡化日後的驗證作業
  5. 使用者解鎖裝置,藉此確認並驗證付款金額和目的地。
  6. 商家收到驗證的憑證。
  7. RP 會接收商家提供的憑證,並驗證其真實性。
  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.
  }
});

驗證使用者

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

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

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

請參考以下程式碼範例:

// 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 含有 RP 驗證的交易資料 (payment)。

產生的憑證必須跨來源轉移至 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 就能告知商家交易成功。

後續步驟