Prompt dan Konteks yang Berlaku
Desainlah sistem pemrosesan pembayaran e-commerce. Setelah pembeli mengirimkan pesanan, sistem menggunakan penyedia layanan pembayaran eksternal untuk memproses pembayaran kartu satu kali. Sistem melakukan otorisasi saat checkout, melakukan capture setelah inventaris direservasi, dan melepaskan otorisasi jika pesanan dibatalkan. Setelah capture, sistem mendukung beberapa pengembalian dana sebagian (partial refund), tetapi jumlah kumulatifnya tidak boleh melebihi jumlah yang di-capture. Metode pembayaran mungkin memerlukan autentikasi tambahan seperti 3DS, dan hasil akhir mungkin tiba setelah peramban (browser) kembali.
Asumsikan lima juta upaya pembayaran per hari, rata-rata sekitar 58 TPS dan 500 TPS pada waktu puncak. API pembuatan pembayaran memiliki p99 di bawah 300 milidetik, pembacaan status memiliki p99 di bawah 200 milidetik, dan API lokal memiliki target ketersediaan bulanan 99,99%. Ketersediaan tersebut tidak menjamin penyelesaian oleh penyedia. Uang dinyatakan sebagai bilangan bulat dalam unit terkecil (minor unit) mata uang, dan satu pembayaran hanya memiliki tepat satu mata uang. Penyedia, jaringan, dan proses lokal semuanya dapat mengalami kegagalan. Angka-angka dan batas waktu ini merupakan asumsi wawancara, bukan janji yang dibuat oleh suatu produk pembayaran.
Cakupan mencakup pembuatan pembayaran, autentikasi tambahan, otorisasi, capture, pembatalan otorisasi, refund sebagian dan penuh, buku besar uang (money ledger), webhook penyedia, dan rekonsiliasi. Chargeback, model fraud, valuta asing, pembayaran ke merchant (merchant payout), pajak, dan program kepatuhan PCI lengkap berada di luar cakupan, tetapi jawaban harus mengidentifikasi batas-batas tersebut. Halaman yang di-host penyedia atau komponen tokenisasi bertugas mengumpulkan rincian kartu. Sistem ini menyimpan token metode pembayaran dan tidak boleh menerima atau mencatat nomor kartu mentah atau kode keamanan.
Hal yang Dievaluasi Pewawancara
Sinyal pertama adalah apakah kandidat memisahkan niat bisnis (business intent), upaya penyedia (provider attempts), dan fakta uang (money facts). Satu keranjang belanja dipetakan ke satu payment_id internal, tetapi dapat memiliki beberapa upaya autentikasi atau penyedia. Permintaan yang mengalami timeout juga tidak berarti pembayaran gagal. Jawaban yang kuat tidak memampatkan seluruh siklus hidup ke dalam paid=true. Jawaban tersebut menyimpan agregat pembayaran, setiap operasi dan hasil yang tidak diketahui, referensi penyedia, serta entri buku besar secara terpisah.
Sinyal kedua adalah penalaran yang akurat di seluruh batas terdistribusi. Transaksi basis data tidak dapat secara atomik menyertakan penyedia pembayaran eksternal. Penyedia mungkin melakukan capture sementara responsnya hilang, webhook mungkin tiba sebelum respons sinkron, atau sebuah proses mungkin mengalami crash setelah commit lokal. Caller idempotency key, ID operasi internal, provider idempotency key, transactional outbox, deduplikasi webhook, dan pencarian status membuat eksekusi berulang menjadi konvergen. Hal-hal tersebut tidak menciptakan transaksi exactly-once dari ujung ke ujung (end-to-end).
Sinyal ketiga adalah state machine yang monoton dan dapat diaudit. Otorisasi dan capture merupakan tahapan uang yang berbeda. REQUIRES_ACTION bukanlah sebuah kegagalan, dan timeout penyedia yang menghasilkan operasi UNKNOWN tidak boleh langsung menggagalkan pembayaran. Refund harus merujuk pada pembayaran yang telah di-capture, menggunakan nilai uang yang tepat, dan menjaga invarian bahwa refund yang telah dikonfirmasi ditambah refund yang sedang berjalan (in-flight) tidak melebihi jumlah yang di-capture di bawah kondisi konkurensi.
Sinyal keempat adalah pembagian antara status alur kerja (workflow state) dan akuntansi. Tabel pembayaran menjawab apa yang dapat dilakukan pengguna selanjutnya. Buku besar append-only menjawab mengapa suatu saldo memiliki nilainya saat ini. Fakta yang telah dibukukan tidak pernah diedit di tempat (in place); refund dan koreksi menambahkan entri pembalik (reversing) atau kompensasi. Setiap debit, kredit, mata uang, dan referensi bisnis dari jurnal divalidasi secara transaksional dan kemudian direkonsiliasi dengan laporan penyedia serta setoran bank.
Sinyal terakhir adalah keamanan dan sifat dapat diuji kesalahannya (falsifiability). Jawaban harus mengurangi paparan data kartu, memverifikasi tanda tangan webhook, melindungi kredensial/rahasia penyedia, membatasi izin dan log, serta menginjeksi respons yang hilang, webhook duplikat atau tidak berurutan, capture dan refund bersamaan, jurnal yang tidak seimbang, dan diskrepansi rekonsiliasi. Diagram layanan tanpa invarian, jalur pemulihan, dan verifikasi tidak menunjukkan kebenaran sistem.
Pertanyaan yang Perlu Diklarifikasi Sebelum Menjawab
- Siapa yang mengumpulkan data kartu? Prompt ini menggunakan halaman yang di-host penyedia atau komponen tokenisasi. Backend bisnis hanya menerima token metode pembayaran dan tidak menyimpan nomor kartu mentah, data track, PIN, atau kode keamanan. Tim kepatuhan tetap harus mengonfirmasi cakupan PCI yang sebenarnya.
- Kapan otorisasi dan capture terjadi? Otorisasi saat pengiriman pesanan dan capture setelah reservasi inventaris. Batalkan sebelum kedaluwarsa jika inventaris gagal. Tabel kapabilitas menentukan apakah metode pembayaran mendukung penundaan capture; desain tidak boleh mengasumsikan bahwa semua metode mendukungnya.
- Apa yang menentukan keberhasilan? Pengalihan peramban (browser redirect) hanyalah sinyal pengalaman pengguna dan tidak dapat mengotorisasi pemenuhan pesanan (fulfillment). Respons penyedia yang diautentikasi, webhook, atau kueri aktif menyediakan bukti otoritatif, yang harus diterima oleh state machine lokal sebelum tindakan bisnis terjadi.
- Apa kontrak untuk pengembalian dana (refund)? Mendukung beberapa refund sebagian dan satu refund penuh, tidak pernah melebihi capture. Tidak ada refund mandiri tanpa pembayaran asli. Refund dapat selesai secara asinkron atau ditolak oleh penyedia.
- Apakah kita memerlukan banyak penyedia? Versi satu memiliki satu penyedia, tetapi antarmuka menyimpan ID operasi internal dan referensi penyedia. Jangan pernah melakukan failover secara otomatis saat hasilnya belum diketahui, karena kedua penyedia dapat melakukan penagihan ganda.
- Apa saja yang dicakup oleh buku besar? Prompt ini mencatat piutang pemroses (processor receivable) dan utang merchant (merchant payable). Biaya penyedia, pembayaran ke merchant, dan pajak berada di luar cakupan. Setiap mata uang diseimbangkan secara independen; tidak ada uang floating-point atau nilai tukar implisit yang diizinkan di dalam buku besar.
- Apa persyaratan retensi dan audit? Retensi pembayaran, operasi, tanda terima webhook, dan referensi buku besar sesuai dengan kebijakan regulasi dan perusahaan, sembari meminimalkan, mengenkripsi, dan mengaudit akses ke payload sensitif. PCI SSC melarang penyimpanan data autentikasi sensitif setelah otorisasi, bahkan ketika dienkripsi.
- Bagaimana kompromi antara ketersediaan dan konsistensi? Jika penyedia tidak tersedia, sistem dapat menerima pekerjaan dan menampilkan status "memproses", tetapi tidak boleh menampilkan keberhasilan palsu. Kebenaran uang dan keterlacakan memiliki prioritas lebih tinggi daripada respons terminal langsung.
Kerangka Jawaban 30 Detik
"Saya akan menggunakan payment_id yang stabil untuk satu payment intent dan memisahkan status pembayaran dari operasi otorisasi, capture, dan refund. Caller idempotency key secara atomik menyimpan digest permintaan, operasi, dan outbox; worker menggunakan kembali ID operasi tersebut di sisi penyedia. Timeout menjadi UNKNOWN dan konvergen melalui webhook terverifikasi, pencarian status, dan rekonsiliasi alih-alih melakukan penagihan baru. Hasil otoritatif memajukan state machine yang monoton dan secara atomik menambahkan jurnal yang seimbang serta event bisnis. Pembuatan refund secara kondisional mereservasi sisa nilai yang dapat di-refund. Laporan penyedia dan setoran bank kemudian merekonsiliasi buku besar internal. Tokenisasi penyedia menjaga data kartu tetap di luar backend, dan halaman sukses pada peramban tidak dapat memicu fulfillment."
Pembahasan Mendalam Langkah demi Langkah
Langkah 1: Mulai dengan kapasitas, invarian, dan kepemilikan
Lima juta dibagi 86.400 detik adalah sekitar 58 TPS. Puncak 500 TPS tidak memerlukan setiap tabel untuk di-shard pada hari pertama. Kebenaran, auditabilitas, dan pemulihan di seluruh batas eksternal mendominasi desain ini. API pembayaran menskala secara horizontal, basis data relasional menyimpan status otoritatif, dan antrean tahan lama (durable queue) yang dirutekan berdasarkan payment_id menyerap lonjakan beban serta mengisolasi latensi penyedia. Nyatakan invarian terlebih dahulu:
amount > 0
currency is immutable after the first provider attempt
captured_amount <= authorized_amount
refunded_amount + pending_refund_amount <= captured_amount
for every journal: sum(debits) == sum(credits), per currency
one merchant + one idempotency_key describes one immutable request intent
one successful business operation produces at most one journal referencePada skala ini, basis data relasional dengan satu wilayah tulis (write region) dan mekanisme failover membuat idempotency key, versi status, dan urutan buku besar lebih mudah dipertahankan dibandingkan penulisan multi-wilayah aktif-aktif (active-active). Aktif-aktif dapat mengurangi waktu failover regional, tetapi harus menyelesaikan penggunaan bersamaan dari kunci yang sama dan konflik operasi uang; hal ini hanya dibenarkan oleh persyaratan ketersediaan regional yang eksplisit. Event sourcing penuh juga menjaga riwayat tetapi menyebarkan kompleksitas replay, migrasi skema, dan kueri ke seluruh alur kerja pembayaran. Desain ini menjaga hanya jurnal uang yang bersifat append-only dan menggunakan agregat saat ini yang diberi versi untuk status alur kerja, mencocokkan manfaat audit dengan biaya operasional.
Layanan pesanan (order service) memiliki inventaris dan fulfillment. Layanan pembayaran (payment service) memiliki status pembayaran, operasi, dan referensi ke fakta uang. Penyedia memiliki status jaringan kartu. Buku besar memiliki fakta uang internal. Layanan pesanan tidak dapat menulis baris pembayaran secara langsung, dan webhook pembayaran tidak dapat secara langsung menandai pesanan telah dikirim. Layanan pembayaran menerbitkan event bisnis yang stabil yang dikonsumsi oleh layanan pesanan secara idempoten berdasarkan payment_id.
Langkah 2: Definisikan API, sesi idempotensi, dan model data
Antarmuka inti dapat berupa:
POST /payments create one payment for an order
POST /payments/{id}/capture capture an authorized amount
POST /payments/{id}/cancel cancel an uncaptured authorization
POST /payments/{id}/refunds request a partial or full refund
GET /payments/{id} return current status and allowed next actions
POST /provider/webhooks persist a verified provider eventSetiap panggilan yang mengubah uang memerlukan idempotency key yang disediakan oleh pemanggil. Batasan unik pada (merchant_id, operation_type, idempotency_key) melindungi digest permintaan yang dinormalisasi, status in-progress, dan respons yang dapat diputar ulang (replayable). Permintaan pertama membuat payments, payment_operations, dan catatan outbox dalam satu transaksi basis data. Kunci dan digest yang sama memutar ulang respons yang diketahui. Kunci yang sama dengan jumlah, mata uang, pembayaran, atau jenis operasi yang berbeda akan mengembalikan konflik. Panduan rekayasa pihak pertama Amazon juga merekomendasikan ID permintaan yang disediakan pemanggil dan batas ACID yang mencakup ID serta mutasi; ID yang digunakan kembali dengan niat berbeda akan ditolak.
Simpan tanggung jawab secara terpisah:
payments: referensi pesanan, jumlah, mata uang, status agregat, total diotorisasi/di-capture/di-refund, dan versi;payment_operations: jenis, ID operasi, jumlah yang diminta, status, penyedia, referensi penyedia, upaya, dan alasan ketidaktahuan;provider_events: ID event penyedia, hasil tanda tangan, waktu diterima, referensi payload terenkripsi, dan status pemrosesan;journal_entries: ID jurnal, akun, debit atau kredit, jumlah, mata uang, dan referensi operasi;outbox_events: event bisnis yang di-commit secara lokal dan menunggu penerbitan.
ID operasi internal dapat menjadi provider idempotency key. Adyen mendokumentasikan percobaan ulang yang aman dengan kunci yang sama setelah timeout pembayaran, bersama dengan cakupan kunci, retensi, dan batasan lintas wilayah. Oleh karena itu, sistem memodelkan kontrak penyedia yang sebenarnya alih-alih memperlakukan UUID sebagai jaminan permanen dan global.
Langkah 3: Modelkan pembayaran dan operasi sebagai dua state machine
Agregat pembayaran adalah siklus hidup yang menghadap ke pesanan dan pengguna:
CREATED -> REQUIRES_ACTION -> AUTHORIZED -> CAPTURED
\-> FAILED \-> CANCELED
CAPTURED -> PARTIALLY_REFUNDED -> REFUNDEDSetiap operasi otorisasi, capture, pembatalan, atau refund memiliki siklus hidup operasi yang independen:
PENDING -> SUCCEEDED | FAILED | UNKNOWN
UNKNOWN -> SUCCEEDED | FAILED (after query, webhook, or reconciliation)FAILED berarti bukti otoritatif menyatakan operasi ini tidak akan berhasil di kemudian hari. Network timeout, 5xx, atau respons yang hilang adalah UNKNOWN. Agregat hanya menerima transisi monoton yang valid. Webhook yang melaporkan status lama dipertahankan untuk audit tetapi tidak dapat memundurkan status agregat. Ketika webhook dan kueri aktif saling berpacu (race condition), versi baris atau pembaruan kondisional melakukan commit pada transisi hanya sekali. Stripe mendokumentasikan payment intent sebagai sumber daya yang mencakup pembuatan hingga checkout, termasuk autentikasi tambahan, dan pembayaran manual-capture berubah menjadi capturable sebelum di-capture. Hal tersebut mendukung sumber daya pembayaran yang berumur panjang daripada menyamakan pembayaran dengan satu respons HTTP.
Otorisasi pesanan dipisahkan dari konfirmasi inventaris. Keberhasilan inventaris memicu capture, sementara kegagalan memicu pembatalan. Operasi tersebut dapat berpacu, sehingga pembaruan basis data kondisional hanya mengizinkan satu operasi untuk mengklaim versi AUTHORIZED saat ini. Webhook dapat tiba sebelum panggilan penyedia kembali. Baik respons sinkron maupun webhook harus masuk ke fungsi penerapan status (state-application function) yang sama daripada mengimplementasikan dua jalur transisi.
Langkah 4: Terima non-atomisitas eksternal dan buat alur kerja menjadi konvergen
Transaksi lokal melakukan commit pada status operasi bersama dengan outbox. Relay menerbitkan setidaknya sekali (at least once), dan worker mengklaim berdasarkan ID operasi. Worker menggunakan kembali ID tersebut sebagai provider idempotency key dan menerapkan batas waktu koneksi, permintaan, dan total yang terikat (bounded deadlines). Hasil yang pasti menyimpan referensi penyedia dan digest respons. Respons yang hilang menandai status sebagai UNKNOWN dan menjadwalkan pencarian status. Sistem tidak pernah membuat operasi baru atau beralih penyedia secara membabi buta.
Ada tiga jendela crash yang penting:
- Crash sebelum transaksi lokal melakukan commit tidak meninggalkan operasi yang terlihat, sehingga pemanggil mencoba lagi dengan kunci yang sama.
- Pengakuan penerbitan yang hilang setelah commit outbox membuat relay menerbitkan lagi; worker mengklaim operasi yang sama.
- Keberhasilan penyedia dengan respons yang hilang menggunakan kunci penyedia yang sama, pencarian referensi penyedia, atau webhook untuk konvergen.
Ini memberikan ekspresi yang dapat dicoba ulang dan diaudit untuk satu niat, bukan transaksi lintas perusahaan. Jika hasilnya masih belum diketahui setelah jendela retensi idempotensi penyedia berakhir, percobaan ulang otomatis akan dihentikan. Laporan penyedia dan operator harus menentukan hasilnya; berlalunya waktu saja tidak membuktikan kegagalan.
Langkah 5: Terima webhook asinkron dengan aman dan toleransi perubahan urutan
Endpoint mempertahankan body permintaan mentah dan memverifikasi tanda tangan serta jendela timestamp-nya dengan secret saat ini dan secret lama dalam periode rotasi. Endpoint menolak tanda tangan yang tidak valid. Tanda terima (provider, provider_event_id) yang unik dipersistensikan, setelah itu endpoint dengan cepat mengembalikan respons 2xx dan mengantrekan pemrosesan asinkron. Log berisi ID event, referensi penyedia, dan kode alasan, bukan payload sensitif lengkap atau secret.
Webhook dapat diduplikasi, tertunda, dan urutannya berubah. Stripe secara eksplisit mendokumentasikan percobaan ulang otomatis mode langsung dan tidak adanya jaminan urutan event. Handler tidak dapat berasumsi bahwa otorisasi selalu tiba sebelum capture. Handler dapat mengambil sumber daya penyedia saat ini melalui referensi objek event, atau memetakan fakta eksternal ke transisi lokal yang diizinkan. Setiap event dideduplikasi melalui ID operasi atau referensi penyedia yang sama. Capture yang telah dibukukan tidak dapat dibukukan dua kali, dan otorisasi lama tidak dapat menurunkan CAPTURED menjadi AUTHORIZED.
Peramban hanya melakukan polling atau berlangganan status pembayaran lokal. Parameter "success" pada URL pengalihan kembali (return URL) tidak dapat memenuhi pesanan, dan klien tidak dapat mengirimkan CAPTURED. Setelah keberhasilan capture otoritatif dan commit buku besar, layanan pembayaran menerbitkan PaymentCaptured melalui outbox. Layanan pesanan melakukan fulfillment secara idempoten berdasarkan ID event.
Langkah 6: Ekspresikan fakta uang dengan buku besar append-only
Status pembayaran adalah tampilan operasional; buku besar adalah catatan audit uang. Prompt ini menyederhanakan akuntansi menjadi dua akun: piutang pemroses (processor receivable) adalah aset dan utang merchant (merchant payable) adalah kewajiban. Capture sebesar CNY 100,00, di mana unit terkecilnya adalah fen, membukukan:
journal capture-<operation_id>, CNY
debit processor_receivable 10000
credit merchant_payable 10000Refund sebesar CNY 30,00 menambahkan jurnal pembalik:
journal refund-<operation_id>, CNY
debit merchant_payable 3000
credit processor_receivable 3000Setiap jurnal memiliki setidaknya dua entri dan total debit serta kredit yang sama per mata uang. journal_id dan referensi operasi bisnisnya bersifat unik. Transaksi yang memajukan status otoritatif ke CAPTURED atau refund yang berhasil juga menyisipkan jurnal dan event outbox. Jika biaya, chargeback, atau pembayaran merchant masuk dalam cakupan, tambahkan akun eksplisit dan entri baru; jangan pernah menulis ulang entri lama. Saldo diturunkan dari entri atau diakselerasi oleh proyeksi yang dapat dibangun kembali (rebuildable projection). Proyeksi tidak dapat menjadi sumber kebenaran tunggal (source of truth) untuk uang.
Refund yang terjadi secara bersamaan pertama-tama mengklaim kapasitas secara kondisional pada baris pembayaran. Operasi baru dan peningkatan jumlah in-flight hanya diizinkan jika captured_amount - refunded_amount - pending_refund_amount mencukupi. Keberhasilan memindahkan jumlah tersebut dari tertunda ke telah di-refund, kegagalan pasti melepaskannya, dan status tidak diketahui mempertahankan reservasi. Hal ini mencegah refund lain melebihi batas. Penyedia mungkin memberlakukan batas atas refund-nya sendiri, tetapi invarian lokal tidak dapat bergantung pada penahan eksternal tersebut.
Langkah 7: Rekonsiliasi diskrepansi tersembunyi dan perbaiki dengan aman
Jalur real-time tidak dapat membuktikan bahwa tidak ada yang terlewatkan. Lapisan rekonsiliasi pertama menyelesaikan operasi UNKNOWN berdasarkan referensi penyedia atau idempotency key dan membandingkan jumlah, mata uang, jenis operasi, dan status terminal. Lapisan kedua memuat laporan transaksi atau penyelesaian (settlement) penyedia yang tidak dapat diubah setiap hari dan mencocokkan referensi pembayaran, operasi, jurnal, dan pesanan. Lapisan ketiga mencocokkan batch penyelesaian penyedia dengan setoran bank aktual dan membedakan jumlah yang belum diselesaikan, biaya, refund, dan chargeback.
Klasifikasikan diskrepansi sebagai hanya-eksternal, berhasil-internal/hilang-eksternal, jumlah atau mata uang salah, status basi (stale), referensi duplikat, buku besar tidak seimbang, atau item penyelesaian hilang. Diskrepansi berisiko tinggi memblokir pelepasan saldo merchant yang terpengaruh dan memicu peringatan (alert). Komponen perbaikan (repairer) bersifat idempoten: mengimpor operasi penyedia yang sudah dikonfirmasi akan menambahkan jurnal baru dengan alasan audit, sementara koreksi akuntansi menggunakan jurnal kompensasi alih-alih melakukan UPDATE pada data historis. Dokumentasi pelaporan Stripe mendeskripsikan transaksi saldo sebagai immutable, dengan transaksi refund baru yang meniadakan fakta asli, dan merekonsiliasi pembayaran, batch pembayaran, serta tanda terima bank secara terpisah.
Metrik memisahkan keberhasilan API dan p99, distribusi status pembayaran, jumlah dan usia operasi UNKNOWN, error dan throttling penyedia, kegagalan/duplikasi/keterlambatan tanda tangan webhook, waktu dari otorisasi hingga capture, kapasitas refund yang direservasi, backlog outbox dan antrean, penolakan jurnal yang tidak seimbang, jumlah diskrepansi, dan diskrepansi tertua yang belum terselesaikan. Pelaporan bisnis membedakan tingkat otorisasi, capture, refund, dan penyelesaian. "Permintaan diterima" tidak boleh dihitung sebagai "uang diterima".
Langkah 8: Minimalkan permukaan sensitif dan injeksi kegagalan
Komponen tokenisasi penyedia mengumpulkan data kartu secara langsung. Backend hanya menyimpan token penyedia yang tidak dapat diubah kembali dan bidang tampilan yang disetujui seperti brand dan empat digit terakhir. Secret klien tidak pernah masuk ke URL atau log. Kunci penyedia disimpan dan dirotasi dalam sistem rahasia (secret system) yang terkontrol. Secret webhook bersifat terpisah, serta sandbox dan produksi diisolasi. Tindakan melihat pembayaran, memulai refund, membaca buku besar, dan melakukan perbaikan manual menggunakan izin terpisah. Setiap tindakan berisiko tinggi mencatat operator dan alasannya.
PCI SSC secara eksplisit melarang penyimpanan kode verifikasi kartu, PIN, dan blok PIN setelah otorisasi, bahkan ketika dienkripsi. Pengumpulan data yang di-host mempersempit paparan prompt ini, tetapi penilaian formal tetap menentukan kepatuhan. Jawaban wawancara tidak dapat mengklaim bahwa "menggunakan token menghilangkan PCI".
Uji penerimaan (acceptance tests) mencakup klien yang mencoba lagi dengan kunci yang sama dan mengubah jumlah di bawah kunci tersebut; keberhasilan penyedia dengan respons yang hilang; kedatangan webhook sebelum respons API; webhook duplikat, tidak berurutan, dan tertunda; penerbitan outbox duplikat; otorisasi yang berpacu dengan pembatalan; dua refund yang bersaing untuk kapasitas yang sama; refund penuh setelah refund sebagian; kedaluwarsanya idempotensi penyedia; rotasi secret webhook; penolakan jurnal sepihak; transaksi hanya-eksternal yang ditemukan oleh rekonsiliasi; eksekusi perbaikan berulang; dan pemulihan setelah pemadaman antrean atau penyedia yang berkepanjangan. Setiap skenario menguji dan memastikan (assert) status pembayaran, status operasi, jumlah jurnal, efek samping pesanan, dan peringatan.
Contoh Jawaban Berkualitas Tinggi
"Saya akan memodelkan pembayaran sebagai sumber daya bisnis yang berumur panjang daripada satu panggilan HTTP. Satu pesanan memiliki payment_id yang stabil. Agregat menyimpan jumlah, mata uang, dan status otorisasi/capture/refund. Setiap otorisasi, capture, pembatalan, dan refund menggunakan ID operasi terpisah dan status PENDING, SUCCEEDED, FAILED, atau UNKNOWN. Timeout eksternal menjadi UNKNOWN; ini tidak boleh langsung menggagalkan atau mengalihkan penyedia.
Pembuatan pembayaran dan setiap operasi uang memerlukan caller idempotency key. Merchant, jenis operasi, dan kunci bersifat unik. Permintaan pertama secara atomik menyimpan digest-nya, pembayaran atau operasi, dan outbox; permintaan yang sama memutar ulang hasilnya, sementara jumlah atau mata uang yang berbeda di bawah kunci tersebut akan mengalami konflik. Worker menggunakan ID operasi internal sebagai kunci penyedia. Outbox, antrean, dan worker semuanya berjalan setidaknya sekali (at least once), dan operasi yang sama menerapkan satu transisi otoritatif.
Respons sinkron penyedia, webhook dengan tanda tangan terverifikasi, dan pencarian aktif masuk ke dalam fungsi penerapan status yang sama. Webhook memverifikasi payload mentah, mendeduplikasi ID event-nya, melakukan persistensi sebelum mengembalikan 2xx, dan mentoleransi duplikat serta perubahan urutan. Event lama tidak dapat memundurkan status pembayaran. Peramban hanya menampilkan status lokal dan tidak dapat memicu fulfillment. Hanya keberhasilan capture otoritatif yang di-commit dengan jurnal yang seimbang dan outbox bisnis yang memungkinkan layanan pesanan melakukan fulfillment secara idempoten.
Jurnal capture mendebit piutang pemroses dan mengkredit utang merchant. Refund menambahkan jurnal pembalik; riwayat tidak dapat diubah (immutable). Pembuatan refund secara kondisional mereservasi nilai refundable yang tersedia, sehingga refund yang dikonfirmasi ditambah refund yang sedang berjalan tidak pernah melebihi capture. Uang menggunakan unit terkecil berupa bilangan bulat, mata uang tidak dapat diubah setelah upaya pertama, dan setiap mata uang diseimbangkan secara independen.
Pemulihan memiliki tiga lapisan: operasi yang tidak diketahui menggunakan kembali kunci penyedia atau melakukan kueri status, laporan harian penyedia merekonsiliasi pembayaran, operasi, dan jurnal, serta batch penyelesaian mencocokkan setoran bank. Diskrepansi dikarantina dan diberi peringatan; perbaikan hanya menambahkan impor idempoten atau jurnal kompensasi. Komponen penyedia yang di-host mengumpulkan data kartu, sehingga backend tidak menyimpan nomor kartu maupun kode keamanan. Respons yang hilang, webhook duplikat atau tidak berurutan, capture dan refund bersamaan, gangguan penyedia, jurnal tidak seimbang, dan item rekonsiliasi yang hanya ada di eksternal kemudian membuktikan bahwa setiap hasil eksternal konvergen ke satu fakta uang yang dapat diaudit."
Kesalahan Umum
- Memperlakukan halaman sukses peramban sebagai keberhasilan pembayaran → pengalihan dapat dipalsukan, dan pembayaran mungkin masih menunggu autentikasi atau konfirmasi asinkron → Hanya bukti penyedia otoritatif yang diterima oleh state machine lokal yang memicu fulfillment.
- Menggunakan satu kolom
paiduntuk seluruh siklus hidup → kolom ini tidak dapat mengekspresikan autentikasi tambahan, otorisasi, capture, refund sebagian, atau hasil yang tidak diketahui → Pisahkan agregat pembayaran dari upaya operasi. - Mencoba lagi dengan ID baru atau penyedia lain setelah timeout → upaya pertama mungkin telah menagih, menciptakan tagihan ganda → Gunakan kembali operasi dan kunci penyedia, lalu lakukan kueri atau rekonsiliasi hasil yang tidak diketahui.
- Hanya membandingkan idempotency key, bukan parameternya → pemanggil yang menggunakan kembali kunci dengan jumlah yang diubah akan mendapatkan hasil bisnis yang salah → Persistensikan digest permintaan yang dinormalisasi dan berikan konflik pada niat yang berubah.
- Bergantung pada urutan webhook → penyedia dapat mencoba lagi, menunda, dan mengubah urutan event → Deduplikasi dan ambil status saat ini atau terapkan hanya transisi monoton yang sah.
- Memperlakukan saldo tabel pembayaran sebagai buku besar → pembaruan langsung di tempat kehilangan alasan perubahan uang dan tidak dapat direkonsiliasi secara independen → Tambahkan jurnal yang seimbang; pertahankan saldo sebagai proyeksi yang dapat dibangun kembali.
- Membaca lalu menulis saldo yang dapat di-refund secara bersamaan → dua pemanggil dapat melihat kapasitas yang sama dan melebihi batas capture → Reservasi kapasitas dengan pembaruan kondisional transaksional yang terikat secara unik pada operasi.
- Hanya memantau respons API 200 → diterima (accepted), diotorisasi, di-capture, dan diselesaikan memiliki arti yang berbeda → Ukur setiap status, usia status tidak diketahui, dan diskrepansi rekonsiliasi.
- Mengklaim tokenisasi secara otomatis menghapus tanggung jawab PCI → halaman, log, skrip, dan proses operasional dapat tetap berada dalam cakupan → Minimalkan data kartu dan minta tim kepatuhan mengonfirmasi batas sebenarnya.
- Mengedit entri historis untuk memperbaiki diskrepansi → rantai audit terputus dan laporan historis tidak dapat diputar ulang → Tambahkan impor dengan penjelasan atau jurnal kompensasi.
Pertanyaan Lanjutan dan Tanggapannya
Pertanyaan Lanjutan 1: Penyedia melakukan capture, tetapi respons sinkron dan webhook hilang. Apa yang terjadi?
Operasi tetap berstatus UNKNOWN, mempertahankan otorisasi terkait atau reservasi refund dan mencegah operasi lain untuk niat yang sama. Pertama, coba lagi pencarian yang didukung atau panggil dengan provider idempotency key asli, lalu lakukan kueri aktif berdasarkan referensi penyedia. Jika tetap tidak pasti, tunggu rekonsiliasi laporan transaksi. Keberhasilan yang dikonfirmasi masuk ke fungsi penerapan status yang sama dan membukukan jurnal serta outbox; hanya kegagalan yang dikonfirmasi yang melepaskan kapasitas. Setelah masa retensi kunci penyedia kedaluwarsa tanpa bukti, teruskan kasus tersebut ke operator daripada menyimpulkan kegagalan hanya dari berlalunya waktu.
Pertanyaan Lanjutan 2: Dua refund masing-masing sebesar CNY 60 secara bersamaan menargetkan capture sebesar CNY 100. Bagaimana Anda mencegah over-refund?
Gunakan pembaruan kondisional pada baris pembayaran atau baris saldo refundable khusus. Pembaruan ini meningkatkan pending_refund_amount sebesar CNY 60 dan membuat operasi hanya jika setidaknya tersisa CNY 60. Kedua transaksi bersaing pada satu versi, sehingga satu berhasil dan yang lain membaca kembali kapasitas yang tidak mencukupi. Refund yang tidak diketahui mempertahankan reservasinya, kegagalan pasti melepaskannya, dan keberhasilan memindahkan status tertunda menjadi di-refund. Penegakan oleh penyedia hanyalah garis pertahanan kedua.
Pertanyaan Lanjutan 3: CAPTURED tiba melalui webhook sebelum AUTHORIZED. Bagaimana cara memprosesnya?
Persistensikan dan deduplikasi kedua tanda terima. Jika tanda tangan, jumlah, mata uang, dan referensi penyedia CAPTURED cocok, terapkan transisi capture dan jurnal. Event AUTHORIZED yang datang kemudian adalah fakta yang lebih lama. State machine menolak rollback dan hanya memperbarui metrik audit dan keterlambatan webhook. Jika payload kekurangan bukti versi yang memadai, ambil sumber daya pembayaran penyedia saat ini daripada menimpa berdasarkan urutan kedatangan.
Pertanyaan Lanjutan 4: Mengapa harus memiliki tabel pembayaran dan buku besar sekaligus?
Tabel pembayaran adalah agregat alur kerja yang cocok untuk menjawab apakah capture, pembatalan, atau refund diizinkan. Buku besar append-only adalah catatan uang yang cocok untuk menjelaskan bagaimana saldo terbentuk dan event bisnis apa yang menyebabkan setiap perubahan. Pembayaran dapat berpindah dari CAPTURED ke PARTIALLY_REFUNDED, sementara buku besar mempertahankan capture asli dan setiap jurnal refund seimbang yang independen. Referensi operasi yang unik menggabungkan keduanya, dan transisi otoritatif melakukan commit pada keduanya dalam satu transaksi lokal. Tidak ada tanggung jawab yang menggantikan yang lain.
Pertanyaan Lanjutan 5: Bagaimana Anda membuktikan bahwa repairer rekonsiliasi tidak dapat membukukan dua kali?
Berikan setiap baris laporan eksternal kunci sumber yang stabil seperti penyedia, jenis laporan, dan referensi transaksi. Ikat perbaikan ke ID diskrepansi dan tambahkan batasan (source_key, repair_type) yang unik. Transaksi pertama menambahkan jurnal, menandai diskrepansi, dan menulis outbox. Crash dan eksekusi ulang akan menemukan perbaikan yang sama dan memutar ulang hasilnya. Hentikan (kill) proses sebelum commit, setelah commit, dan setelah publikasi event; jumlah jurnal, saldo, dan jumlah event downstream tidak boleh bertambah untuk kedua kalinya.
Pertanyaan Lanjutan 6: Jika penyedia kedua ditambahkan nanti, kapan failover diizinkan?
Buat operasi di penyedia kedua hanya ketika penyedia pertama secara eksplisit menyatakan bahwa operasi tidak pernah dibuat atau telah gagal secara definitif, dan operasi lokal tidak memiliki fakta uang. Batas waktu koneksi, 5xx, dan status tidak diketahui tidak memenuhi kondisi tersebut. Audit pilihan perutean, kapabilitas penyedia, jumlah, mata uang, dan alasan. Jika kedua hasil mungkin ada, bekukan fulfillment dan pelepasan saldo sementara kueri dan rekonsiliasi mengeliminasi kemungkinan tagihan ganda. Pengembalian dana otomatis tidak dapat menyembunyikan hasil yang tidak diketahui.