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.
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.
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:
- React mengirim intent checkout ke backend
- backend menghitung order final dari cart yang tervalidasi
- backend membuat payment session ke gateway
- frontend menerima token atau redirect URL pembayaran
- gateway mengirim webhook status ke backend
- 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.
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