Secure Payment Confirmation

This is a test website. Nothing is charged.

Notice:

  Secure Payment Confirmation has been enabled by default since Chrome 95. Chrome 94 still needs an Origin Trial token to support SPC. The Origin Trial will end by the end of 2021.
    

(with credential #1)

(with credential #2)

(with credential #1)

(with credential #2)


  

Enroll Credential will make the following request:

async function createCredentialCompat() {
  const rp = {
    id: window.location.hostname,
    name: 'Rouslan Solomakhin',
  };
  const pubKeyCredParams = [{
    type: 'public-key',
    alg: -7, // ECDSA, not supported on Windows.
  }, {
    type: 'public-key',
    alg: -257, // RSA, supported on Windows.
  }];
  const challenge = textEncoder.encode('Enrollment challenge');
  if (window.PaymentCredential) {
    const payment = {
      rp,
      instrument: {
        displayName: 'Troy ยทยทยทยท',
        icon: 'https://rsolomakhin.github.io/pr/spc/troy.png',
      },
      challenge,
      pubKeyCredParams,
      authenticatorSelection: {
        userVerification: 'required',
      },
    };
    return await navigator.credentials.create({
      payment
    });
  } else {
    const publicKey = {
      rp,
      user: {
        name: 'user@domain',
        id: Uint8Array.from(String(Math.random()*999999999), c => c.charCodeAt(0)),
        displayName: 'User',
      },
      challenge,
      pubKeyCredParams,
      authenticatorSelection: {
        userVerification: 'required',
        residentKey: 'required',
        authenticatorAttachment: 'platform',
      },
      extensions: {
        payment: {
          isPayment: true,
        },
      }
    };
    return await navigator.credentials.create({
      publicKey
    });
  }
}
const publicKeyCredential = createCredentialCompat();
window.localStorage.setItem(
    'credential_identifier',
    btoa(String.fromCharCode(...new Uint8Array(
        publicKeyCredential.rawId))));

Pay $0.01 will make the following request:

new PaymentRequest([{
  supportedMethods: 'secure-payment-confirmation',
  data: {
    credentialIds: [Uint8Array.from(
        atob(window.localStorage.getItem('credential_identifier')),
        c => c.charCodeAt(0))],
    instrument: {
      displayName: 'My Troy Card',
      icon: 'https://rsolomakhin.github.io/pr/spc/troy-alt-logo.png',
    },
    challenge: textEncoder.encode('network_data'),
    timeout: 60000,
    payeeOrigin: window.location.origin,
  },
}], {
  total: {
    label: 'Total',
    amount: {
      currency: 'USD',
      value: '0.01',
    },
  },
});

Login will make the following request:

const publicKey = {
  challenge: textEncoder.encode('Authentication challenge'),
  userVerification: 'required',
  allowCredentials: [{
    transports: ['internal'],
    type: 'public-key',
    id: Uint8Array.from(
      atob(window.localStorage.getItem('credential_identifier')),
      c => c.charCodeAt(0)),
  }],
};
navigator.credentials.get({publicKey});

Based on the Secure Payment Confirmation explainer.