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

Eiji Kitamura
Eiji Kitamura

針對特定信用卡或銀行帳戶,商家可以使用安全付款確認 (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 呼叫。您不需要自行指定「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,其中包含 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 就能告知商家交易成功。

後續步驟