The #ChromeDevSummit site is live, happening Nov 12-13 in San Francisco, CA
Check it out for details and request an invite. We'll be diving deep into modern web tech & looking ahead to the platform's future.

Payment Request API: Panduan Integrasi

Membeli barang secara online memang praktis namun sering kali menjadi pengalaman yang mengecewakan, khususnya pada perangkat seluler. Walaupun lalu lintas seluler terus meningkat, akun konversi seluler hanya sekitar sepertiga dari semua pembelian yang diselesaikan. Dengan kata lain, pengguna meninggalkan pembelian lewat seluler dua kali lebih sering daripada pembelian lewat desktop. Mengapa?

Mengapa pengguna meninggalkan formulir pembelian lewat seluler

Formulir pembelian online adalah intensif-pengguna, sulit digunakan, lambat dimuat dan disegarkan, serta perlu banyak langkah untuk menyelesaikannya. Ini karena kedua komponen utama pembayaran online—keamanan dan kenyamanan—sering kali berbenturan; mengutamakan yang satu berarti mengalahkan yang lain.

Kebanyakan masalah yang menyebabkan pengabaian bisa langsung dilacak ke formulir pembelian. Setiap aplikasi atau situs memiliki entri data dan proses validasinya sendiri, dan pengguna sering kali merasa mereka harus memasukkan informasi yang sama pada setiap titik pembelian di aplikasi. Juga, developer aplikasi berusaha membuat alur pembelian yang mendukung beberapa metode pembayaran berbeda sekaligus; bahkan perbedaan kecil dalam persyaratan metode pembayaran bisa memperumit penyelesaian formulir dan proses penyerahannya.

Sistem apa saja yang memperbaiki atau memecahkan satu atau beberapa masalah itu akan menjadi perubahan yang disambut baik. Kita sudah mulai memecahkan masalah dengan Isiotomatis, namun kita ingin membicarakan tentang solusi yang lebih komprehensif.

Memperkenalkan Payment Request API

Payment Request API adalah sistem yang dimaksudkan untuk meniadakan formulir pemeriksaan. Ini sangat memperbaiki alur kerja pengguna selama proses pembelian, sehingga memberikan pengalaman pengguna yang konsisten dan memungkinkan para penjual di web dengan mudah memanfaatkan metode pembayaran yang berbeda-beda. Payment Request API bukanlah metode pembayaran baru, tidak juga berintegrasi langsung dengan pemroses pembayaran; melainkan, layer proses yang bertujuan:

  • Memungkinkan browser bertindak sebagai perantara antara penjual, pengguna, dan metode pembayaran
  • Untuk menstandarkan alur komunikasi pembayaran sebanyak mungkin
  • Untuk mendukung dengan mulus beragam metode pembayaran yang aman
  • Agar bekerja di browser, perangkat, atau platform apa pun—seluler atau yang lain

Payment Request API adalah standar terbuka dan lintas-browser yang menggantikan alur checkout tradisional yang memungkinkan penjual meminta dan menerima pembayaran dalam satu panggilan API. Payment Request API memungkinkan laman web bertukar informasi dengan agen-pengguna selagi pengguna memberikan masukan, sebelum menyetujui atau menolak permintaan pembayaran.

Yang terpenting, dengan browser berfungsi sebagai perantara, semua informasi yang diperlukan untuk checkout cepat bisa disimpan di browser, sehingga pengguna bisa tinggal mengonfirmasikan dan membayar, cukup dengan satu klik.

Proses transaksi pembayaran

Menggunakan Payment Request API, proses transaksi berjalan semulus mungkin untuk pengguna dan penjual.

Proses transaksi pembayaran

Prosesnya dimulai saat situs penjual membuat PaymentRequest baru dan meneruskan semua informasi yang diperlukan ke browser untuk melakukan pembayaran: jumlah yang akan dibebankan, mata uang yang diharapkan pembayaran, dan metode pembayaran yang diterima oleh situs tersebut. Browser akan menentukan kompatibilitas antara metode pembayaran yang diterima untuk situs dan metode yang dipasang pengguna di perangkat target.

Antarmuka Permintaan Pembayaran

Kemudian browser menampilkan UI pembayaran kepada pengguna, yang memilih metode pembayaran dan mengesahkan transaksi. Metode pembayaran bisa sepraktis kartu kredit yang sudah disimpan oleh browser, atau serahasia aplikasi pihak ketiga yang ditulis khusus untuk menyerahkan pembayaran ke situs (fungsionalitas ini sebentar lagi tersedia). Setelah pengguna mengesahkan transaksi, semua detail pembayaran yang diperlukan akan langsung dikirim kembali ke situs. Misalnya, untuk pembayaran dengan kartu kredit, situs akan mengambil kembali nomor kartu, nama pemegang kartu, tanggal kedaluwarsa, dan CVC.

PaymentRequest juga bisa diperluas untuk mengembalikan informasi tambahan, seperti alamat dan opsi pengiriman, email pembayar, dan telepon pembayar. Ini memungkinkan Anda mendapatkan semua informasi yang diperlukan untuk menuntaskan pembayaran tanpa menampilkan formulir checkout kepada pengguna.

Manfaat proses baru ini tiga kali lipat: dari sudut pandang pengguna, semua interaksi—permintaan, otorisasi, pembayaran, dan hasil membosankan sebelumnya—sekarang dilakukan hanya dalam satu langkah; dari sudut pandang situs web, ini hanya memerlukan satu panggilan JavaScript API; dari sudut pandang metode pembayaran, tidak ada perubahan proses apa pun.

Menggunakan Payment Request API

Muat sisipan Payment Request API

Untuk mengurangi penderitaan mengejar API standar hidup ini, kami sangat menyarankan Anda untuk menambahkan sisipan ini di bagian <head> pada kode. Sisipan ini akan diperbarui begitu API berubah dan akan berusaha keras agar kode Anda tetap bekerja setidaknya pada 2 rilis utama Chrome.

<script src="https://storage.googleapis.com/prshim/v1/payment-shim.js">

Buat PaymentRequest

Langkah pertama adalah membuat objek PaymentRequest dengan memanggil konstruktor PaymentRequest. Langkah ini biasanya (namun tidak selalu) dikaitkan dengan tindakan yang diprakarsai pengguna, yang menunjukkan maksud mereka untuk melakukan pembelian. Objek tersebut dibuat menggunakan parameter berisi data yang diperlukan.

var request = new PaymentRequest(
  methodData, // required payment method data
  details,    // required information about transaction
  options     // optional parameter for things like shipping, etc.
);

Konstruktor PaymentRequest

Parameter methodData

Parameter methodData berisi daftar metode pembayaran yang didukung dan, jika relevan, informasi tambahan tentang metode pembayaran. Urutan ini berisi kamus-kamus PaymentMethodData, termasuk identifier standar yang dikaitkan dengan metode pembayaran yang ingin diterima oleh aplikasi, dan data spesifik metode pembayaran. Lihat Arsitektur Payment Request API untuk detail selengkapnya.

Saat ini, PaymentRequest di Chrome hanya mendukung kartu kredit standar berikut: 'amex', 'diners', 'discover', 'jcb', 'maestro', 'mastercard', 'unionpay', dan 'visa'.

var methodData = [
  {
    supportedMethods: ["visa", "mastercard"]
  }
]

Data dan metode pembayaran

Parameter details

Parameter details berisi informasi tentang transaksi. Ada dua komponen utama: sebuah total, yang merefleksikan jumlah total dan mata uang yang akan digunakan, dan set opsional displayItems yang menunjukkan cara menghitung jumlah akhir. Parameter ini tidak dimaksudkan menjadi daftar item lini, namun melainkan rangkuman komponen utama pesanan: subtotal, diskon, pajak, biaya kirim, dll.

Antarmuka Permintaan Pembayaran

Perlu diperhatikan bahwa Payment Request API tidak melakukan aritmetika. Yaitu, ia tidak dan tidak bisa memastikan bahwa komponen tampilan menjumlah dengan benar total jumlah yang harus dibayar. Penghitungan ini adalah tanggung jawab developer. Jadi Anda harus selalu memastikan bahwa jumlah item daftar sama dengan jumlah total. Juga, PaymentRequest tidak mendukung pengembalian uang, sehingga jumlahnya harus selalu positif (namun item daftar individual bisa berupa negatif, misalnya diskon).

Browser akan merender label seperti yang Anda definisikan dan secara otomatis merender format mata uang yang benar berdasarkan lokal pengguna. Perhatikan, label harus di-render dalam bahasa yang sama dengan materi Anda.

var details = {
  displayItems: [
    {
      label: "Original donation amount",
      amount: { currency: "USD", value : "65.00" }, // US$65.00
    },
    {
      label: "Friends and family discount",
      amount: { currency: "USD", value : "-10.00" }, // -US$10.00
      pending: true // The price is not determined yet
    }
  ],
  total:  {
    label: "Total",
    amount: { currency: "USD", value : "55.00" }, // US$55.00
  }
}

Detail transaksi

pending umumnya digunakan untuk menampilkan item seperti jumlah pajak atau pengiriman yang bergantung pada pemilihan alamat pengiriman atau opsi pengiriman. Chrome menunjukkan bidang tertunda pada UI untuk permintaan pembayaran.

Nilai berulang atau terhitung yang digunakan dalam details bisa ditetapkan baik sebagai literal string maupun variabel string individual.

var currency = "USD";
var amount = "65.00";
var discount = "-10.00";
var total = "55.00";

Variabel-variabel PaymentRequest

Tampilkan PaymentRequest

Antarmuka Permintaan Pembayaran

Aktifkan antarmuka PaymentRequest dengan memanggil metodenya show(). Metode ini memanggil UI bawaan yang memungkinkan pengguna memeriksa detail pembelian, menambahkan atau mengubah informasi, dan terakhir, membayarnya. Sebuah Promise (ditunjukkan oleh fungsi callback dan metode then()-nya) yang ditetapkan akan dikembalikan bila pengguna menerima atau menolak permintaan pembayaran.

request.show().then(function(paymentResponse) {
  // Process paymentResponse here
  paymentResponse.complete("success");
}).catch(function(err) {
  console.error("Uh oh, something bad happened", err.message);
});

Metode show PaymentRequest

Batalkan PaymentRequest

Anda bisa dengan sengaja membatalkan PaymentRequest dengan memanggil metode abort(). Ini terutama berguna bila waktu sesi belanja telah habis atau item di keranjang telah terjual habis selama transaksi.

Gunakan metode ini jika aplikasi perlu membatalkan permintaan pembayaran setelah metode show() dipanggil namun sebelum promise diproses — Misalnya, jika sebuah item tidak lagi tersedia, atau pengguna gagal mengonfirmasi pembelian dalam waktu yang dialokasikan.

Jika Anda membatalkan permintaan, Anda perlu membuat instance baru PaymentRequest sebelum bisa memanggil lagi show().

var paymentTimeout = window.setTimeout(function() {
  window.clearTimeout(paymentTimeout);
  request.abort().then(function() {
    console.log('Payment timed out after 20 minutes.');
  }).catch(function() {
    console.log('Unable to abort.');
  });
}, 20 * 60 * 1000);  /* 20 minutes */

Metode batalkan PaymentRequest

Proses PaymentResponse

Dengan persetujuan pengguna untuk permintaan pembayaran, promise metode show() akan diproses, yang menghasilkan objek PaymentResponse.

PaymentResponse memiliki bidang-bidang berikut:
methodName String yang menunjukkan metode pembayaran yang dipilih (mis., visa)
details Kamus berisi informasi untuk methodName
shippingAddress Alamat pengiriman pengguna, jika diminta
shippingOption ID opsi pengiriman yang dipilih, jika diminta
payerEmail Alamat email pembayar, jika diminta
payerPhone Nomor telepon pembayar, jika diminta
payerName Nama pembayar, jika diminta

Untuk pembayaran dengan kartu kredit, responsnya standar. Untuk pembayaran dengan selain kartu kredit (mis., Android Pay), respons akan didokumentasikan oleh penyedia. Respons kartu kredit berisi kamus berikut:

cardholderName cardNumber expiryMonth expiryYear cardSecurityCode billingAddress

Setelah informasi pembayaran diterima, aplikasi harus menyerahkan informasi pembayaran kepada pemroses pembayaran untuk diproses. UI akan menampilkan spinner saat permintaan terjadi. Bila respons telah kembali, aplikasi harus memanggil complete() untuk menutup UI.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string, e.g. “visa”
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details
  };
  return fetch('/pay', {
    method: 'POST',
    credentials: 'include',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(paymentData)
  }).then(res => {
    if (res.status === 200) {
      return res.json();
    } else {
      throw 'Payment Error';
    }
  }).then(res => {
    paymentResponse.complete("success");
  }, err => {
    paymentResponse.complete("fail");
  });
}).catch(err => {
  console.error("Uh oh, something bad happened", err.message);
});
Antarmuka Permintaan Pembayaran

Metode complete() akan memberi tahu agen-pengguna bahwa interaksi pengguna telah selesai dan mengizinkan aplikasi memberi tahu pengguna mengenai hasilnya dan menangani disposisi elemen UI selebihnya.

paymentResponse.complete('success').then(() => {
  // Success UI
}

paymentResponse.complete('fail').then(() => {
  // Error UI
};

Metode complete PaymentRequest

Mengumpulkan alamat pengiriman

Antarmuka Permintaan Pembayaran

Jika Anda penjual yang menjual barang fisik, Anda mungkin ingin mengumpulkan alamat pengiriman pengguna dengan menggunakan Payment Request API. Caranya adalah dengan menambahkan requestShipping: true ke parameter options. Bila telah menyetel parameter ini, "Shipping" akan ditambahkan ke UI, dan pengguna bisa memilih alamat yang telah disimpan dari daftar atau menambahkan alamat pengiriman baru.

Atau Anda bisa menggunakan "Delivery" atau "Pickup" sebagai ganti "Delivery" di UI dengan menetapkan shippingType. Hal ini semata-mata untuk keperluan tampilan.

var options = {
  requestShipping: true,
  shippingType: "shipping" // "shipping"(default), "delivery" or "pickup"
};

var request = new PaymentRequest(methodData, details, options);

Opsi transaksi

Antarmuka Permintaan Pembayaran

Opsi pengiriman bisa dihitung secara dinamis bila pengguna memilih atau menambahkan alamat pengiriman baru. Anda bisa menambahkan event listener untuk kejadian shippingaddresschange, yang akan memicu pemilihan alamat pengiriman oleh pengguna. Anda nanti bisa memvalidasi kesanggupan mengirim ke alamat itu, menghitung opsi pengiriman, dan memperbarui details.shippingOptions Anda dengan opsi pengiriman baru dan informasi harga. Anda bisa menawarkan opsi pengiriman default dengan menyetel selected ke true pada sebuah opsi.

Untuk menolak alamat karena alasan seperti region yang tidak didukung, teruskan larik kosong ke details.shippingOptions. UI akan memberi tahu pengguna bahwa alamat yang dipilih tidak bisa digunakan untuk pengiriman.

request.addEventListener('shippingaddresschange', e => {
  e.updateWith(((details, addr) => {
    if (addr.country === 'US') {
      var shippingOption = {
        id: '',
        label: '',
        amount: {currency: 'USD', value: '0.00'},
        selected: true
      };
      if (addr.region === 'US') {
        shippingOption.id = 'us';
        shippingOption.label = 'Standard shipping in US';
        shippingOption.amount.value = '0.00';
        details.total.amount.value = '55.00';
      } else {
        shippingOption.id = 'others';
        shippingOption.label = 'International shipping';
        shippingOption.amount.value = '10.00';
        details.total.amount.value = '65.00';
      }
      if (details.displayItems.length === 2) {
        details.displayItems.splice(1, 0, shippingOption);
      } else {
        details.displayItems.splice(1, 1, shippingOption);
      }
      details.shippingOptions = [shippingOption];
    } else {
      details.shippingOptions = [];
    }
    return Promise.resolve(details);
  })(details, request.shippingAddress));
});
Antarmuka Permintaan Pembayaran

Dengan persetujuan pengguna untuk permintaan pembayaran, promise metode show() memberi solusi. Aplikasi boleh menggunakan properti .shippingAddress objek PaymentResponse untuk memberitahukan alamat pengiriman kepada pemroses pembayaran, bersama properti lainnya.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details.toJSON(),
    // shipping address information
    address: paymentResponse.shippingAddress.toJSON()
  };
  // Send information to the server
});

Menambahkan opsi pengiriman

Jika layanan Anda memungkinkan pengguna untuk memilih opsi pengiriman seperti "gratis", "standar", atau "kilat", Anda juga bisa melakukannya melalui UI Payment Request. Untuk menawarkan pilihan tersebut, tambahkan properti shippingOptions dan opsinya ke objek details. Dengan menyetel satu pilihan ke selected: true, UI akan merendernya sebagai telah terpilih (berarti jumlah total Anda harus merefleksikan harga untuk opsi pengiriman itu).

var details = {
  total: {label: 'Donation', amount: {currency: 'USD', value: '55.00'}},
  displayItems: [
    {
      label: 'Original donation amount',
      amount: {currency: 'USD', value: '65.00'}
    },
    {
      label: 'Friends and family discount',
      amount: {currency: 'USD', value: '-10.00'}
    }
  ],
  shippingOptions: [
    {
      id: 'standard',
      label: 'Standard shipping',
      amount: {currency: 'USD', value: '0.00'},
      selected: true
    },
    {
      id: 'express',
      label: 'Express shipping',
      amount: {currency: 'USD', value: '12.00'}
    }
  ]
};
var request = new PaymentRequest(methodData, details, options);

Mengubah opsi pengiriman mungkin akan menghasilkan harga yang berbeda. Untuk menambahkan ongkos kirim dan mengubah total harga, Anda dapat menambahkan event listener untuk kejadian shippingoptionchange, yang akan terpicu saat pengguna memilih opsi pengiriman, sehingga Anda bisa menjalankan pemeriksaan terprogram terhadap data opsi. Anda juga dapat mengubah ongkos kirim sesuai alamat pengiriman.

request.addEventListener('shippingoptionchange', e => {
  e.updateWith(((details, shippingOption) => {
    var selectedShippingOption;
    var otherShippingOption;
    if (shippingOption === 'standard') {
      selectedShippingOption = details.shippingOptions[0];
      otherShippingOption = details.shippingOptions[1];
      details.total.amount.value = '55.00';
    } else {
      selectedShippingOption = details.shippingOptions[1];
      otherShippingOption = details.shippingOptions[0];
      details.total.amount.value = '67.00';
    }
    if (details.displayItems.length === 2) {
      details.displayItems.splice(1, 0, selectedShippingOption);
    } else {
      details.displayItems.splice(1, 1, selectedShippingOption);
    }
    selectedShippingOption.selected = true;
    otherShippingOption.selected = false;
    return Promise.resolve(details);
  })(details, request.shippingOption));
});
Antarmuka Permintaan Pembayaran

Dengan persetujuan pengguna untuk permintaan pembayaran, promise metode show() memberi solusi. Aplikasi dapat menggunakan properti .shippingOption objek PaymentResponse untuk memberi tahu opsi pengiriman kepada pemroses pembayaran, bersama properti lainnya.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details.toJSON(),
    // shipping address information
    address: paymentResponse.shippingAddress.toJSON(),
    // shipping option
    shippingOption: paymentResponse.shippingOption
  };
  // Send information to the server
});

Menambahkan informasi kontak opsional

Anda juga bisa mengumpulkan alamat email pengguna, nomor telepon, atau nama dengan mengonfigurasi objek options.

var options = {
  requestPayerPhone: true,  // Request user's phone number
  requestPayerEmail: true,  // Request user's email address
  requestPayerName:  true   // Request user's name
};

var request = new PaymentRequest(methodData, details, options);
Antarmuka Permintaan Pembayaran

Dengan persetujuan pengguna untuk permintaan pembayaran, promise metode show() memberi solusi. Aplikasi dapat menggunakan .payerPhone, .payerEmail dan/atau properti .payerName objek PaymentResponse untuk memberi tahu pemroses pembayaran mengenai pilihan pengguna, bersama properti lainnya.

request.show().then(paymentResponse => {
  var paymentData = {
    // payment method string
    method: paymentResponse.methodName,
    // payment details as you requested
    details: paymentResponse.details.toJSON(),
    // shipping address information
    address: paymentResponse.shippingAddress.toJSON(),
    // shipping option string
    shippingOption: paymentResponse.shippingOption,
    // payer's phone number string
    phone: paymentResponse.payerPhone,
    // payer's email address string
    email: paymentResponse.payerEmail,
    // payer's name string
    name: paymentResponse.payerName
  };
  // Send information to the server
});

Membuat PaymentRequest sebagai penyempurnaan progresif

Karena Payment Request API adalah fitur yang berkembang, banyak browser yang belum mendukungnya. Untuk menentukan apakah fitur ini tersedia, buat kueri window.PaymentRequest.

if (window.PaymentRequest) {
  // PaymentRequest supported
  // Continue with PaymentRequest API
} else {
  // PaymentRequest NOT supported
  // Continue with existing form based solution
}

Menyatukan semuanya

function onBuyClicked(event) {
  if (!window.PaymentRequest) {
    return;
  }
  // Payment Request API is available.
  // Stop the default anchor redirect.
  event.preventDefault();

  var supportedInstruments = [{
    supportedMethods: [
      'visa', 'mastercard', 'amex', 'discover', 'maestro',
      'diners', 'jcb', 'unionpay', 'bitcoin'
    ]
  }];

  var details = {
    displayItems: [{
      label: 'Original donation amount',
      amount: { currency: 'USD', value: '65.00' }
    }, {
      label: 'Friends and family discount',
      amount: { currency: 'USD', value: '-10.00' }
    }],
    total: {
      label: 'Total due',
      amount: { currency: 'USD', value : '55.00' }
    }
  };

  var options = {
    requestShipping: true,
    requestPayerEmail: true,
    requestPayerPhone: true,
    requestPayerName: true
  };

  // Initialization
  var request = new PaymentRequest(supportedInstruments, details, options);

  // When user selects a shipping address
  request.addEventListener('shippingaddresschange', e => {
    e.updateWith(((details, addr) => {
      var shippingOption = {
        id: '',
        label: '',
        amount: { currency: 'USD', value: '0.00' },
        selected: true
      };
      // Shipping to US is supported
      if (addr.country === 'US') {
        shippingOption.id = 'us';
        shippingOption.label = 'Standard shipping in US';
        shippingOption.amount.value = '0.00';
        details.total.amount.value = '55.00';
      // Shipping to JP is supported
      } else if (addr.country === 'JP') {
        shippingOption.id = 'jp';
        shippingOption.label = 'International shipping';
        shippingOption.amount.value = '10.00';
        details.total.amount.value = '65.00';
      // Shipping to elsewhere is unsupported
      } else {
        // Empty array indicates rejection of the address
        details.shippingOptions = [];
        return Promise.resolve(details);
      }
      // Hardcode for simplicity
      if (details.displayItems.length === 2) {
        details.displayItems[2] = shippingOption;
      } else {
        details.displayItems.push(shippingOption);
      }
      details.shippingOptions = [shippingOption];

      return Promise.resolve(details);
    })(details, request.shippingAddress));
  });

  // When user selects a shipping option
  request.addEventListener('shippingoptionchange', e => {
    e.updateWith(((details) => {
      // There should be only one option. Do nothing.
      return Promise.resolve(details);
    })(details));
  });

  // Show UI then continue with user payment info
  request.show().then(result => {
    // POST the result to the server
    return fetch('/pay', {
      method: 'POST',
      credentials: 'include',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(result.toJSON())
    }).then(res => {
      // Only if successful
      if (res.status === 200) {
        return res.json();
      } else {
        throw 'Failure';
      }
    }).then(response => {
      // You should have received a JSON object
      if (response.success == true) {
        return result.complete('success');
      } else {
        return result.complete('fail');
      }
    }).then(() => {
      console.log('Thank you!',
          result.shippingAddress.toJSON(),
          result.methodName,
          result.details.toJSON());
    }).catch(() => {
      return result.complete('fail');
    });
  }).catch(function(err) {
    console.error('Uh oh, something bad happened: ' + err.message);
  });
}

// Assuming an anchor is the target for the event listener.
document.querySelector('#start').addEventListener('click', onBuyClicked);