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

商家可以使用安全付款確認 (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 包含交易資料 (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 即可通知商家交易成功。

後續步驟