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 forte do cliente (SCA) para um determinado cartão de crédito ou conta bancária. O WebAuthn realiza a autenticação (geralmente 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 as 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 o RP quiser que o comerciante use o SPC e se o usuário já tiver se registrado, o RP vai responder com uma lista de IDs de credencial registrados pelo pagador e um desafio.
    • Se não for necessário fazer uma autenticação, o comerciante poderá continuar concluindo a transação.
  3. Se for necessário fazer uma autenticação, o comerciante determina se o navegador oferece suporte ao SPC.
    • Se o navegador não oferecer suporte ao SPC, prossiga 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 credencial transmitidos pelo RP, volte ao fluxo de autenticação atual. Depois de uma autenticação bem-sucedida, use o registro de SPC para agilizar 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. O RP recebe a credencial do comerciante e verifica a autenticidade dela.
  8. O RP envia os resultados da verificação ao comerciante.
  9. O comerciante mostra ao usuário uma mensagem para indicar se o pagamento foi concluído ou não.

Detecção de recursos

Para detectar se o SPC é compatível com o navegador, envie uma chamada falsa para canMakePayment().

Copie e cole o código abaixo para detectar o 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.
  }
});

Autenticação do usuário

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

Confira os parâmetros que você precisa fornecer à propriedade data da forma de pagamento, SecurePaymentConfirmationRequest.

Parâmetro Descrição
rpId O nome do host da origem da RP como ID da RP.
challenge Um desafio aleatório que impede ataques de repetição.
credentialIds Uma matriz de IDs de credencial. Na autenticação do WebAuthn, a propriedade allowCredentials aceita uma matriz de objetos PublicKeyCredentialDescriptor, mas no SPC, você só transmite uma lista de IDs de credencial.
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 (padrão true) para iconMustBeShown que especifica que um ícone precisa ser buscado e mostrado para que a solicitação seja bem-sucedida.
timeout Tempo limite para assinar a transação em milissegundos
extensions Extensões adicionadas à chamada do WebAuthn. Não é necessário especificar a extensão "payment".

Confira este exemplo de código:

// 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 o details contém uma credencial de chave pública com um clientDataJSON que contém os dados da transação (payment) para verificação pelo RP.

A credencial resultante precisa ser transferida entre origens para o RP e verificada.

Como o RP verifica a transação

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

Para verificar os dados da transação, o RP pode seguir o processo de verificação de declaração de autenticação do WebAuthn. Além disso, eles precisam verificar o payment.

Um exemplo de payload do 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 do RP.
  • O topOrigin corresponde à origem de nível superior esperada pelo RP (a origem do comerciante no exemplo acima).
  • O payeeOrigin corresponde à origem do beneficiário que deveria ter sido exibida 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 atendidos, o RP poderá informar ao merchant que a transação foi concluída.

Próximas etapas