Аутентификация с безопасным подтверждением платежа

Продавцы могут использовать безопасное подтверждение платежа (SPC) как часть процесса строгой аутентификации клиента (SCA) для конкретной кредитной карты или банковского счета. WebAuthn выполняет аутентификацию (часто с помощью биометрии). WebAuthn необходимо зарегистрировать заранее, о чем вы можете узнать в разделе «Регистрация безопасного подтверждения платежа» .

Как работает типичная реализация

Чаще всего SPC используется, когда клиент совершает покупку на сайте продавца, а эмитент кредитной карты или банк требует аутентификации плательщика.

Рабочий процесс аутентификации.

Давайте пройдемся по процедуре аутентификации:

  1. Покупатель предоставляет продавцу свои платежные данные (например, данные кредитной карты).
  2. Продавец спрашивает у соответствующего эмитента платежных учетных данных или банка (проверяющей стороны или RP), требуется ли плательщику отдельная аутентификация. Такой обмен может произойти, например, с помощью EMV® 3-D Secure .
    • Если RP желает, чтобы продавец использовал SPC, и если пользователь ранее зарегистрировался, RP отвечает списком идентификаторов учетных данных, зарегистрированных плательщиком, и запросом.
    • Если аутентификация не требуется, продавец может продолжить выполнение транзакции.
  3. Если необходима аутентификация, продавец определяет, поддерживает ли браузер SPC .
    • Если браузер не поддерживает SPC, продолжите существующий процесс аутентификации.
  4. Продавец вызывает SPC. Браузер отображает диалоговое окно подтверждения.
    • Если от RP не переданы идентификаторы учетных данных, вернитесь к существующему потоку аутентификации. После успешной аутентификации рассмотрите возможность использования регистрации 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.
challenge Случайное испытание, предотвращающее повторные атаки.
credentialIds Массив идентификаторов учетных данных. При аутентификации WebAuthn allowCredentials принимает массив объектов PublicKeyCredentialDescriptor , но в SPC вы передаете только список идентификаторов учетных данных.
payeeName (необязательно) Имя получателя платежа.
payeeOrigin Происхождение получателя платежа. В вышеупомянутом сценарии это происхождение торговца.
instrument Строка для displayName и URL-адрес icon , указывающий на ресурс изображения. Необязательное логическое значение (по умолчанию true ) для iconMustBeShown , которое указывает, что значок должен быть успешно получен и показан для успешного выполнения запроса.
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 может сообщить продавцу, что транзакция прошла успешно.

Следующие шаги