Kasir Chat yang Nggak Boleh Ngitung Uang
Bedah agent n8n yang gw bikin buat Kelas Beta: kasir toko keripik yang nyambung ke MCP server DOKU, dan kenapa pengaman paling ampuhnya ada di Postgres, bukan di prompt.
Buat demo di Kelas Beta, gw nggak mau peserta cuma duduk nonton gw ngetik. Maunya mereka nyoba sendiri dari HP.
Jadi gw bikin kasir buat toko keripik bohongan. Produknya delapan: keripik singkong original, balado, daun jeruk, pisang manis, basreng, tempe, talas, sama usus. Chat-nya gw taruh di balik QR code, satu slide penuh. Peserta tinggal scan, pesan pakai bahasa sehari-hari (“keripik tempe 1, basreng 2”), terus agent-nya bikin link checkout DOKU sandbox beneran. Bayarnya lewat payment simulator DOKU yang bisa dibuka siapa aja tanpa login. Habis bayar, balik lagi ke chat, bilang “udah bayar ya”.
Isinya n8n, modelnya Gemini, dan nyambung ke DOKU lewat MCP client yang udah dibahas di tulisan kedua. Di tulisan ini gw bedah cara agent ini disusun, dan yang paling penting: di mana pengamannya ternyata ditaruh.

Dari pesan masuk sampai pesanan lunas
Pesan dari HP masuk lewat chat trigger. Tiap HP dapat session sendiri, jadi walaupun puluhan orang pesan barengan, chat-nya nggak bakal campur aduk.
Sebelum sampai ke model, pesan lewat dulu node “Di luar topik?”. Isinya pola teks buat nangkep percobaan iseng yang paling kelihatan, kayak “abaikan instruksi”, “kamu sekarang adalah…”, “act as”, “jailbreak”, atau “buatkan kode python”. Yang kena langsung dibelokin ke “Tolak halus”, nggak pernah nyampe ke agent.
Biar jelas, node ini cuma regex. Bukan AI. Dia cuma nangkep kalimat yang kedengarannya kayak serangan. Kalau serangannya dibungkus kayak pesanan keripik biasa, ya lolos. Makanya semua yang ada di belakangnya gw bikin dengan anggapan: pasti ada yang tembus.
Yang lolos baru masuk ke AI Agent. Agent ini punya memory, output parser biar jawabannya terstruktur, dan lima tool: DOKU MCP client (cuma dikasih 4 dari 35 tool), baca katalog dari Supabase, HTTP request buat ngitung total, sama update status pesanan.
Nah, habis agent bilang “checkout udah dibuat”, masih ada satu gerbang lagi: “Pesanan sah?”. Di sini dicek tiga hal: has_order harus true, invoice_number nggak boleh kosong, checkout_url juga nggak boleh kosong. Satu aja kosong, pesanannya nggak bakal disimpan ke database.
Kalau sah, pesanan disimpan, balasan dikirim. Kalau udah lunas dan pembeli ngasih email, bukti bayarnya dikirim ke email itu. Kalau ada error di tengah jalan, admin dapat email, dan pembeli dapat pesan kalau lagi ada gangguan.
Larangan di prompt itu nggak cukup
Di system prompt-nya ada satu aturan yang gw tulis pakai huruf kapital semua: KAMU TIDAK BOLEH MENGHITUNG UANG. Subtotal, diskon, pajak, ongkir, kembalian, semuanya nggak boleh. Alasannya juga gw tulis di situ: angka yang masuk ke sistem pembayaran harus datang dari sistem yang bisa diaudit, dan model bahasa jelas bukan sistem kayak gitu. Ada juga aturan buat nggak boleh ngarang nomor invoice, status bayar, atau nominal. Kalau nggak tahu, panggil tool.
Aturan itu ngebantu, tapi gw nggak terlalu ngandelin. Prompt itu bisa dirayu. Orang yang cukup niat lama-lama pasti nemu kalimat yang bikin agent nurut.
Yang gw percaya ada di bawahnya, di database. Di situ nggak ada yang bisa dirayu.
- Uang disimpan pakai
numeric(19,2), jangan float. Float itu kalau dijumlahin pasti meleset dikit, dan selisih sekecil apa pun bakal bikin rekonsiliasi nggak cocok. - Semua hitungan dikerjain fungsi SQL. Model cuma ngirim sku sama jumlahnya. Totalnya dihitung database, terus dibalikin ke agent buat dibacain ke pembeli.
simpan_pesananjalan dalam satu transaksi. Potong stok sama simpan pesanan terjadi barengan. Satu item gagal, satu pesanan batal semua.invoice_numberdibikin UNIQUE. Ini buat idempotency. Kalau agent manggil dua kali gara-gara timeout, tetap cuma ada satu baris per invoice.amountdibikin NOT NULL. Yang ini ada ceritanya sendiri, lihat bagian berikutnya.- View katalog sengaja nggak bawa kolom
description. Hasil tool itu masuk ke konteks model di setiap giliran chat, jadi kolom yang nggak kepakai cuma buang-buang token. Ini lebih ke urusan hemat daripada keamanan, sih. Tapi yang kayak gini baru kepikiran setelah lihat sendiri satu hasil tool bisa makan ribuan token per giliran.
Agent yang ngaku udah bikin checkout
Waktu ngebangun demo ini, beberapa kali agent-nya bilang ke pembeli kalau link checkout udah dibuat. Padahal belum. Tool-nya nggak dipanggil, invoice-nya nggak ada. Cuma kalimat yang pede banget.
Yang akhirnya nahan justru constraint di database. Baris pesanan wajib punya amount, dan amount cuma bisa didapat dari checkout yang beneran dibuat. Jadi pesanan bohongan itu nggak bisa masuk. Gerbang “Pesanan sah?” juga nangkep kasus yang sama satu langkah lebih awal, karena dia nggak mau lanjut kalau nomor invoice sama URL checkout-nya kosong.
Kalau ada satu hal yang mau gw tekankan dari seluruh seri ini, ya ini: bikin sistemnya supaya klaim palsu dari agent nggak bisa disimpan. Ngecek output agent itu bagus. Tapi bikin kebohongannya nggak muat di schema jauh lebih ampuh, karena tetap jalan jam tiga pagi pas nggak ada orang yang ngecek.
Tapi nggak semua kegagalan itu bohong, ya. Ini percakapan asli waktu uji coba. Pembuatan link-nya gagal, agent ngaku gagal, terus pas dicoba lagi berhasil:

Coba perhatiin juga apa yang dia minta habis ngasih link: balik ke chat dan ketik “cek status pesanan” plus nomor invoice-nya. Pas pembeli bilang udah bayar, agent nggak langsung percaya. Dia nanya dulu ke DOKU lewat get_transaction_by_invoice_number, terus nyampein jawabannya. Status bayar itu cuma DOKU yang tahu. Pembeli mau ngomong apa juga, yang dipegang tetap jawaban DOKU.
Stok habis? Malah bagus
Produknya cuma delapan, yang pesan barengan banyak, jadi stok pasti cepat habis. Buat demo, ini justru momen yang bagus. Contohnya waktu ada yang pesan keripik tempe 27 bungkus, padahal stoknya tinggal 25:

Kelihatannya agent-nya teliti banget, ya. Padahal modelnya nggak ngerti apa-apa soal stok. Yang nolak itu fungsi SQL, di dalam transaksi. Agent cuma nyampein. Makanya kalau stoknya gw reset, yang gw reset ya database-nya. Agent-nya sendiri nggak nyimpen apa-apa.
Ruginya apa?
Desain kayak gini ada harganya, dan kebanyakan soal fleksibilitas. Agent nggak bisa ngasih diskon, gratisin ongkir, atau bulatin total, walaupun itu bakal bikin pembeli senang. Semua itu nggak ada di tabel harga, dan modelnya dilarang ngitung. Setiap ada aturan harga baru, berarti harus ubah schema dan bikin fungsi SQL baru. Nggak bisa cuma nambah satu kalimat di prompt.
Buat demo, agak nyebelin juga sih. Tapi buat sistem yang mindahin uang beneran, gw rasa ini harga yang pantas dibayar. Walaupun gw bisa ngerti kalau tim produk ngerasa jadi lambat.
Satu lagi yang perlu gw akui: ini demo buat satu toko dengan delapan produk. Gw belum pernah coba pola ini di skala marketplace beneran. Bisa jadi di sana muncul masalah baru, misalnya rebutan stok di produk yang lagi laris, yang nggak bakal kelihatan di ruangan isi empat puluhan orang.
Benang merahnya
Kalau dilihat dari tiga tulisan ini, polanya berulang. Di tahap membangun, yang jaga itu approval orang dan dokumen di git. Di tahap menjalankan, yang jaga itu tool yang memang nggak pernah dimasukin ke daftar. Di demo ini, satu kolom NOT NULL sama satu fungsi SQL. Nggak ada satu pun yang bergantung sama modelnya mau nurut atau nggak, dan pengaman model begitulah yang gw mau ada di depan urusan uang.
Seri dari sesi Kelas Beta, 23 September 2026: 1 · AI-assisted development · 2 · MCP server DOKU itu buat apa · 3 · Kasir chat yang nggak boleh ngitung uang