Autentikasi dengan Konfirmasi Pembayaran Aman

Eiji Kitamura
Eiji Kitamura

Penjual dapat menggunakan Konfirmasi Pembayaran Aman (SPC) sebagai bagian dari proses autentikasi pelanggan (SCA) yang ketat untuk kartu kredit atau rekening bank tertentu. WebAuthn melakukan autentikasi (sering kali melalui biometrik). WebAuthn harus terdaftar terlebih dahulu, yang dapat Anda pelajari di bagian Mendaftarkan Konfirmasi Pembayaran Aman.

Cara kerja implementasi standar

Penggunaan SPC yang paling umum adalah saat pelanggan melakukan pembelian di situs penjual, dan penerbit kartu kredit atau bank mewajibkan autentikasi pembayar.

Alur kerja autentikasi.

Mari kita pelajari alur autentikasi:

  1. Pelanggan memberikan kredensial pembayarannya (seperti informasi kartu kredit) kepada penjual.
  2. Penjual menanyakan penerbit atau bank terkait kredensial pembayaran (pihak tepercaya atau RP) jika pembayar memerlukan autentikasi terpisah. Pertukaran ini mungkin terjadi, misalnya, dengan EMV® 3-D Secure.
    • Jika RP ingin penjual menggunakan SPC, dan jika pengguna telah mendaftar sebelumnya, RP akan merespons dengan daftar ID kredensial yang didaftarkan oleh pembayar dan tantangan.
    • Jika autentikasi tidak diperlukan, penjual dapat melanjutkan transaksi.
  3. Jika autentikasi diperlukan, penjual menentukan apakah browser mendukung SPC.
    • Jika browser tidak mendukung SPC, lanjutkan dengan alur autentikasi yang ada.
  4. Penjual memanggil SPC. Browser akan menampilkan dialog konfirmasi.
    • Jika tidak ada ID kredensial yang diteruskan dari RP, kembali ke alur autentikasi yang ada. Setelah autentikasi berhasil, pertimbangkan untuk menggunakan pendaftaran SPM untuk menyederhanakan autentikasi di masa mendatang.
  5. Pengguna mengonfirmasi dan mengautentikasi jumlah dan tujuan pembayaran dengan membuka kunci perangkat.
  6. Penjual menerima kredensial dari autentikasi.
  7. RP menerima kredensial dari penjual dan memverifikasi keasliannya.
  8. RP akan mengirimkan hasil verifikasi ke penjual.
  9. Penjual menampilkan pesan kepada pengguna untuk menunjukkan apakah pembayaran berhasil atau gagal.

Deteksi fitur

Untuk mendeteksi apakah SPC didukung di browser, Anda dapat mengirim panggilan palsu ke canMakePayment().

Salin dan tempel kode berikut untuk mendeteksi SPC di situs penjual.

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

Mengautentikasi pengguna

Untuk mengautentikasi pengguna, panggil metode PaymentRequest.show() dengan parameter secure-payment-confirmation dan WebAuthn:

Berikut adalah parameter yang harus Anda berikan ke properti data metode pembayaran, SecurePaymentConfirmationRequest.

Parameter Deskripsi
rpId Nama host asal RP sebagai ID RP.
challenge Tantangan acak yang mencegah serangan replay.
credentialIds Array ID kredensial. Dalam autentikasi WebAuthn, properti allowCredentials menerima array objek PublicKeyCredentialDescriptor, tetapi di SPC, Anda hanya meneruskan daftar ID kredensial.
payeeName (opsional) Nama penerima pembayaran.
payeeOrigin Asal penerima pembayaran. Dalam skenario yang disebutkan di atas, situs tersebut adalah asal penjual.
instrument String untuk displayName dan URL untuk icon yang mengarah ke resource gambar. Boolean opsional (ditetapkan secara default ke true) untuk iconMustBeShown yang menentukan ikon harus berhasil diambil dan ditampilkan agar permintaan berhasil.
timeout Waktu tunggu untuk menandatangani transaksi dalam milidetik
extensions Ekstensi ditambahkan ke panggilan WebAuthn. Anda tidak perlu menentukan sendiri ekstensi "pembayaran".

Lihat kode contoh ini:

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

Fungsi .show() menghasilkan objek PaymentResponse, kecuali details berisi kredensial kunci publik dengan clientDataJSON yang berisi data transaksi (payment) untuk verifikasi oleh RP.

Kredensial yang dihasilkan harus ditransfer lintas origin ke RP dan diverifikasi.

Cara RP memverifikasi transaksi

Memverifikasi data transaksi di server RP adalah langkah terpenting dalam proses pembayaran.

Untuk memverifikasi data transaksi, RP dapat mengikuti proses verifikasi pernyataan autentikasi WebAuthn. Selain itu, mereka harus memverifikasi payment.

Contoh payload 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 cocok dengan asal RP.
  • topOrigin cocok dengan origin level teratas yang diharapkan RP (asal penjual pada contoh di atas).
  • payeeOrigin cocok dengan asal penerima pembayaran yang seharusnya ditampilkan kepada pengguna.
  • total cocok dengan jumlah transaksi yang seharusnya ditampilkan kepada pengguna.
  • instrument cocok dengan detail instrumen pembayaran yang seharusnya ditampilkan kepada pengguna.
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.';
}

Setelah semua kriteria verifikasi terpenuhi, RP dapat memberi tahu penjual bahwa transaksi berhasil.

Langkah berikutnya