Autenticar com confirmação de pagamento seguro

Os comerciantes podem usar a Confirmação de pagamento seguro (SPC, na sigla em inglês) como parte de um processo de Autenticação Segura do Cliente (SCA) para um determinado cartão de crédito ou conta bancária. O WebAuthn realiza a autenticação (frequentemente por biometria). O WebAuthn precisa ser registrado com antecedência. Saiba mais em Registrar uma confirmação de pagamento seguro.

Como funciona uma implementação típica

O uso mais comum do SPC é quando um cliente faz uma compra no site de um comerciante, e o emissor do cartão de crédito ou o banco exige a autenticação do pagador.

Fluxo de trabalho de autenticação.

Vamos analisar o fluxo de autenticação:

  1. Um cliente fornece suas credenciais de pagamento (como informações de cartão de crédito) ao comerciante.
  2. O comerciante pergunta ao emissor ou banco correspondente da credencial de pagamento (parte confiável ou RP) se o pagador precisa de uma autenticação separada. Essa troca pode acontecer, por exemplo, com o EMV® 3-D Secure.
    • Se a RP quiser que o comerciante use o SPC e o usuário já tiver se registrado, a RP responde com uma lista de IDs de credenciais registrados pelo pagador e um desafio.
    • Se uma autenticação não for necessária, o comerciante poderá continuar para concluir a transação.
  3. Se uma autenticação for necessária, o comerciante determina se o navegador é compatível com o SPC.
    • Se o navegador não for compatível com o SPC, continue com o fluxo de autenticação atual.
  4. O comerciante invoca o SPC. O navegador mostra uma caixa de diálogo de confirmação.
    • Se não houver IDs de credenciais transmitidos da parte restrita, volte para o fluxo de autenticação atual. Após uma autenticação bem-sucedida, use o registro de SPC para simplificar as autenticações futuras.
  5. O usuário confirma e autentica o valor e o destino do pagamento desbloqueando o dispositivo.
  6. O comerciante recebe uma credencial da autenticação.
  7. A parte restrita recebe a credencial do comerciante e verifica a autenticidade.
  8. Ele envia os resultados da verificação ao comerciante.
  9. O comerciante mostra ao usuário uma mensagem indicando se o pagamento foi concluído ou não.

Detecção de recursos

Para detectar se o navegador oferece suporte ao SPC, envie uma chamada falsa para canMakePayment().

Copie e cole o código a seguir para detectar a SPC no site de um comerciante.

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.
  }
});

Autenticar o usuário

Para autenticar o usuário, invoque o método PaymentRequest.show() com os parâmetros secure-payment-confirmation e WebAuthn:

Veja os parâmetros que você precisa fornecer para a propriedade data da forma de pagamento, SecurePaymentConfirmationRequest.

Parâmetro Descrição
rpId O nome do host da origem da RP como o ID da RP.
challenge Um desafio aleatório que impede ataques repetidos.
credentialIds Uma matriz de IDs de credenciais. Na autenticação do WebAuthn, a propriedade allowCredentials aceita uma matriz de objetos PublicKeyCredentialDescriptor, mas no SPC, você transmite apenas uma lista de IDs de credenciais.
payeeName (opcional) Nome do beneficiário.
payeeOrigin A origem do beneficiário. No cenário mencionado acima, é a origem do comerciante.
instrument Uma string para displayName e um URL para icon que aponta para um recurso de imagem. Um booleano opcional (o padrão é true) para iconMustBeShown que especifica um ícone precisa ser buscado e mostrado para que a solicitação seja atendida.
timeout Tempo limite para assinar a transação em milissegundos.
extensions Extensões adicionadas à chamada da WebAuthn. Não é necessário especificar a extensão "pagamento" por conta própria.

Confira este código de exemplo:

// 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);
}

A função .show() resulta em um objeto PaymentResponse, exceto que details contém uma credencial de chave pública com um clientDataJSON que contém os dados da transação (payment) para verificação da RP.

A credencial resultante precisa ser transferida de origem cruzada para a parte restrita e verificada.

Como a parte restrita verifica a transação

A verificação dos dados da transação no servidor da RP é a etapa mais importante no processo de pagamento.

Para verificar os dados da transação, a parte restrita pode seguir o processo de verificação de declaração de autenticação do WebAuthn. Além disso, ele precisa verificar o payment.

Um exemplo de payload de 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"
    }
  }
}
  • O rp corresponde à origem da RP.
  • O topOrigin corresponde à origem de nível superior que a parte restrita espera (a origem do comerciante no exemplo acima).
  • O payeeOrigin corresponde à origem do beneficiário que deveria ter sido mostrada ao usuário.
  • O total corresponde ao valor da transação que deveria ter sido mostrado ao usuário.
  • O instrument corresponde aos detalhes do instrumento de pagamento que deveriam ter sido exibidos ao usuário.
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.';
}

Depois que todos os critérios de verificação forem aprovados, a parte restrita poderá informar ao comerciante que a transação foi bem-sucedida.

Próximas etapas