使用安全付款确认功能进行身份验证

对于特定的信用卡或银行账户,商家可以在强健的客户身份验证 (SCA) 流程中使用安全付款确认 (SPC)。WebAuthn 会执行身份验证(通常是通过生物识别)。您必须提前注册 WebAuthn,相关信息请参阅注册安全付款确认

典型实现的工作原理

SPC 最常见的用途是,顾客在商家的网店购物 网站,并且信用卡发卡机构或银行要求付款人进行身份验证。

身份验证工作流。

我们来了解一下身份验证流程:

  1. 客户提供其付款凭据(例如信用卡) 信息)。
  2. 商家询问付款凭据的对应发卡机构或银行 (依赖方或 RP)。这个 例如 EMV® 3-D Secure
    • 如果 RP 希望商家使用 SPC,并且用户之前 RP 会在响应中包含 和验证。
    • 如果不需要身份验证,商家可以继续 以便完成交易
  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.
  }
});

对用户进行身份验证

如需对用户进行身份验证,请使用以下代码调用 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 调用的扩展程序。您不必指定“付款”扩展程序

请查看此示例代码:

// 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 可以告知 表明交易成功。

后续步骤