A

Blog Post

Integrasi Payment Gateway pada Website E-Commerce dengan React

Panduan production-minded untuk menghubungkan payment gateway ke aplikasi React tanpa membuat checkout, webhook, dan rekonsiliasi transaksi menjadi rapuh.

5 min read
Integrasi Payment Gateway pada Website E-Commerce dengan React

TL;DR

  • Checkout di frontend harus nyaman, tetapi backend tetap memegang source of truth untuk state pembayaran.
  • Webhook verification dan idempotency lebih menentukan kestabilan sistem daripada redirect sukses di browser.
  • Masalah utama biasanya muncul pada rekonsiliasi, bukan pada request pertama ke gateway.

Pendahuluan

Integrasi payment gateway sering dipersempit menjadi pekerjaan frontend: tampilkan form bayar, panggil API, lalu tunggu status sukses. Dalam sistem e-commerce nyata, pendekatan itu terlalu naif. Pembayaran adalah jalur state paling sensitif karena menyentuh uang, inventori, order lifecycle, dan ekspektasi pengguna. Jika desainnya longgar, masalah yang muncul bukan hanya checkout gagal, tetapi order ganda, status nyangkut, dan dispute yang sulit direkonsiliasi.

React memang berperan penting di sisi pengalaman pengguna, tetapi logika kebenaran transaksi tidak boleh hidup di browser. Frontend seharusnya menjadi lapisan presentasi dan orchestration tipis, sementara backend memegang session pembayaran, validasi amount, webhook verification, dan perubahan state order.

Prerequisites

  • Aplikasi frontend React yang sudah memiliki cart/checkout screen.
  • Backend Node.js/Express, Go, Laravel, atau stack server lain yang bisa menerima webhook.
  • Akun sandbox atau production dari payment gateway pilihan.
  • Database untuk menyimpan order dan status payment.
  • HTTPS aktif untuk endpoint webhook.

Problem di Production

Di produksi, payment flow gagal dengan cara yang tidak linear: user refresh, gateway retry webhook, jaringan putus setelah pembayaran berhasil, atau diskon berubah tepat sebelum checkout. Jika arsitektur terlalu mengandalkan browser sebagai sumber kebenaran, state order cepat menjadi ambigu.

Mental Model

Anggap pembayaran sebagai state machine, bukan sebagai satu request API. Browser membuat intent, backend menciptakan payment session, provider mengirim status, dan order berubah hanya ketika transisi itu tervalidasi. Model ini membuat tim lebih disiplin terhadap status pending, paid, failed, dan expired.

Konsep Utama

Browser tidak boleh menjadi source of truth

Harga, diskon, item checkout, dan status transaksi harus diverifikasi di backend. Jika frontend mengendalikan semua ini, perubahan kecil di client dapat membuka celah manipulasi amount atau race condition order.

Webhook lebih penting daripada redirect sukses

Redirect payment success dari browser hanya memberi sinyal UX. Dalam banyak gateway, status final justru datang lewat webhook. Sistem yang sehat memperlakukan webhook sebagai sumber kebenaran dan menjadikan redirect sebagai pelengkap.

Idempotency menyelamatkan banyak hal

User bisa refresh, payment provider bisa retry webhook, dan jaringan bisa membuat request yang sama terlihat ambigu. Tanpa idempotency key dan state transition yang disiplin, transaksi ganda sangat mudah terjadi.

Concept diagram

Architecture / Context

Sistem yang dibangun memisahkan tiga peran: React sebagai checkout UI, backend sebagai payment controller, dan payment gateway sebagai external processor. Dalam produksi, backend adalah tempat perhitungan akhir, pembuatan payment session, dan update order berdasarkan event webhook.

Arsitektur / Cara Kerja

Flow yang sehat biasanya seperti ini:

  1. React mengirim intent checkout ke backend
  2. backend menghitung order final dari cart yang tervalidasi
  3. backend membuat payment session ke gateway
  4. frontend menerima token atau redirect URL pembayaran
  5. gateway mengirim webhook status ke backend
  6. backend memperbarui state order dan memicu notifikasi

Model ini sengaja menahan logika sensitif di server. React tetap penting untuk membangun checkout yang jelas, tetapi tidak bertugas menetapkan kebenaran finansial. Backend yang memegang order state juga lebih mudah direkonsiliasi dengan inventory, shipping, dan refund flow.

Architecture flow

Practical Implementation

Step 1: Buat order pending di backend

app.post("/api/checkout", async (req, res) => {
  const cart = await loadValidatedCart(req.user.id);
  const order = await createOrder({
    userId: req.user.id,
    amount: cart.total,
    status: "pending"
  });

  res.json({ orderId: order.id });
});

Step 2: Buat payment session ke provider

const session = await gateway.createSession({
  reference: order.id,
  amount: order.amount,
  currency: "IDR",
  returnUrl: "https://shop.example.com/payment/result"
});

Step 3: Kirim URL atau token ke React

res.json({
  orderId: order.id,
  paymentUrl: session.paymentUrl
});

Step 4: Redirect user dari React

const response = await fetch("/api/checkout", { method: "POST" });
const data = await response.json();
window.location.href = data.paymentUrl;

Step 5: Verifikasi webhook di backend

app.post("/webhook/payment", async (req, res) => {
  verifySignature(req.rawBody, req.headers["x-signature"]);

  const event = req.body;
  await savePaymentEvent(event.id, event.reference, event.status);
  await reconcileOrder(event.reference, event.status);

  res.status(200).end();
});

Verification

Uji dengan akun sandbox dan skenario retry.

curl -X POST https://shop.example.com/webhook/payment \
  -H "Content-Type: application/json" \
  -H "x-signature: test-signature" \
  -d '{"id":"evt_123","reference":"order_42","status":"paid"}'

Tanda berhasil:

  • order berubah ke status yang benar
  • event webhook tercatat
  • retry event yang sama tidak menggandakan efek samping
  • redirect browser membuka payment page yang valid

Troubleshooting

Redirect sukses, tetapi order tetap pending

Biasanya webhook gagal diverifikasi atau endpoint tidak reachable dari provider.

Order ganda

Periksa apakah handler checkout atau webhook tidak idempotent.

Amount mismatch

Bandingkan amount backend, amount yang dikirim ke provider, dan cart yang ditampilkan di frontend.

Production Notes

  • Simpan raw event webhook untuk audit dan dispute.
  • Pisahkan status order dari status payment agar transisi sistem lebih jelas.
  • Pastikan payment webhook endpoint punya timeout yang baik dan tidak melakukan kerja berat sinkron.
  • Tambahkan alert untuk lonjakan payment failure atau retry webhook.

Studi Kasus / Masalah Nyata

Redirect sukses datang, tetapi webhook gagal

User melihat halaman sukses, tetapi backend tidak pernah menerima webhook karena signature validation salah. Jika redirect dijadikan sumber kebenaran, order bisa dikirim padahal pembayaran belum dikonfirmasi.

Amount berubah setelah promo

Frontend menampilkan promo terbaru, tetapi backend masih menghitung harga lama. Payment session dibuat dengan amount yang berbeda dari order yang disimpan. Ketidaksesuaian ini sulit dibereskan setelah transaksi berjalan.

Retry webhook menciptakan order duplikat

Gateway me-retry event karena respons lambat. Handler backend tidak idempotent lalu menambah stok keluar atau membuat invoice baru dua kali.

Trade-offs

  • Verifikasi server-side menambah kerja backend, tetapi mencegah manipulasi amount dan state di browser.
  • Webhook sebagai source of truth lebih akurat, tetapi membuat alur UX terlihat lebih lambat jika tidak dirancang baik.
  • State machine yang disiplin lebih aman, tetapi menuntut skema order dan retry handling yang lebih matang.

Best Practices

Simpan state machine transaksi yang sederhana

Status order dan pembayaran sebaiknya jelas dan terbatas.

Lindungi webhook sebagai endpoint kritis

Endpoint webhook harus diverifikasi, dibatasi, dan dicatat dengan baik.

Uji failure mode, bukan hanya happy path

Tes minimal harus mencakup user menutup browser setelah bayar, webhook terlambat, retry event, dan pembayaran expired.

Failure Modes

  • Redirect sukses membuat user merasa transaksi selesai padahal webhook belum pernah tervalidasi.
  • Retry webhook menggandakan efek samping karena handler tidak idempotent.
  • Amount yang dikirim frontend tidak cocok dengan perhitungan backend setelah promo atau perubahan cart.

Kesalahan Umum

  • membiarkan frontend menghitung amount final tanpa verifikasi server
  • menganggap redirect browser sama dengan pembayaran final
  • tidak membuat handler webhook idempotent
  • mencampur status order dan status payment tanpa definisi yang jelas
  • tidak menyimpan audit trail reference transaksi

Kesimpulan

Integrasi payment gateway yang stabil membutuhkan pembagian peran yang tegas antara React, backend, dan provider pembayaran. Frontend harus menjaga UX tetap jelas, sementara backend memegang validasi, state, dan rekonsiliasi agar jalur uang tidak menjadi bagian paling rapuh dalam aplikasi e-commerce.

Kalau artikel ini membantu, kamu bisa support eksperimen berikutnya.

Apresiasi di Trakteer