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

Эйдзи Китамура
Eiji Kitamura

Продавцы могут использовать безопасное подтверждение платежа (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 может сообщить продавцу, что транзакция прошла успешно.

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