[{"data":1,"prerenderedAt":3992},["ShallowReactive",2],{"tech-openclaw-cs-whatsapp-gateway":3},{"id":4,"title":5,"author":6,"body":7,"category":3974,"date":3975,"description":3976,"extension":3977,"image":40,"meta":3978,"navigation":334,"path":3979,"readingTime":3980,"seo":3981,"stem":3982,"tags":3983,"__hash__":3991},"tech\u002Ftech\u002Fopenclaw-cs-whatsapp-gateway.md","OpenClaw sebagai CS Otomatis — Arsitektur WhatsApp Gateway, Invoice & Database Strict","Zainul Fanani",{"type":8,"value":9,"toc":3921},"minimark",[10,34,41,45,48,51,58,74,81,84,111,114,119,122,129,140,147,151,154,159,165,170,184,187,198,202,208,212,226,231,249,399,403,410,415,420,425,430,435,440,445,450,455,460,471,473,477,484,684,688,813,819,824,826,830,833,850,854,860,864,870,874,1025,1029,1032,1157,1163,1165,1169,1175,1179,1444,1448,1656,1660,1733,1739,1741,1745,1748,1918,1922,2146,2150,2384,2388,2394,2400,2402,2406,2417,2421,2427,2431,2576,2580,2740,2744,2926,2932,2934,2938,2945,2949,2955,2959,3217,3221,3316,3322,3326,3329,3398,3400,3404,3407,3413,3417,3536,3538,3542,3548,3550,3554,3557,3561,3575,3579,3590,3594,3605,3609,3620,3624,3632,3634,3638,3644,3724,3726,3730,3737,3831,3837,3839,3843,3850,3888,3894,3903,3906,3917],[11,12,13],"blockquote",{},[14,15,16,20,21,25,26,33],"p",{},[17,18,19],"span",{},"!NOTE"," ",[22,23,24],"strong",{},"Mau bikin CS bot WhatsApp dengan AI?"," Kalau belum punya OpenClaw, daftar dulu di ",[27,28,32],"a",{"href":29,"rel":30},"https:\u002F\u002Fblog.fanani.co\u002Fsumopod",[31],"nofollow","Sumopod"," — bisa langsung setup dan deploy ke VPS.",[14,35,36],{},[37,38],"img",{"alt":39,"src":40},"Arsitektur CS WhatsApp dengan OpenClaw","\u002Fimages\u002Fposts\u002Fcs-wa-architecture.jpg",[42,43,5],"h1",{"id":44},"openclaw-sebagai-cs-otomatis-arsitektur-whatsapp-gateway-invoice-database-strict",[14,46,47],{},"Bayangin punya toko online yang jualan 24\u002F7 tanpa perlu rekrut CS. Customer chat di WhatsApp → bot jawab pertanyaan soal ukuran, stok, warna → customer bilang mau pesan → bot langsung kasih invoice + link bayar → done.",[14,49,50],{},"Bukan mimpi. Ini udah bisa dibikin hari ini.",[14,52,53,54,57],{},"Tapi — dan ini penting — ada ",[22,55,56],{},"dua pendekatan"," yang perlu lo pahami sebelum mulai:",[59,60,61,68],"ol",{},[62,63,64,67],"li",{},[22,65,66],{},"OpenClaw sebagai AI CS (jalan 24\u002F7)"," — OpenClaw langsung jadi otak CS, menerima pesan dari gateway, dan menjawab customer. Cocok untuk yang pengen setup cepat.",[62,69,70,73],{},[22,71,72],{},"OpenClaw sebagai builder tools"," — OpenClaw DIPAKAI untuk bikin seluruh infrastruktur (services, database, script), lalu di production-nya pakai AI terpisah yang lebih ringan dan dedicated. Ini pendekatan yang lebih \"production-ready\".",[14,75,76,77,80],{},"Dan apapun pendekatannya — ",[22,78,79],{},"AI NGGAK langsung connect ke WhatsApp",". Selalu ada gateway di tengah.",[14,82,83],{},"Artikel ini bakal ngebahas:",[85,86,87,90,93,96,99,102,108],"ul",{},[62,88,89],{},"Dua pendekatan: OpenClaw as CS vs OpenClaw as Builder",[62,91,92],{},"Kenapa gateway pattern itu wajib, bukan optional",[62,94,95],{},"3+ service yang dibutuhkan: WA-Gateway, Contact-Service, Invoice-Service, Stock-Service",[62,97,98],{},"Gimana setup masing-masing service (dengan bantuan OpenClaw)",[62,100,101],{},"RAG (Retrieval-Augmented Generation) untuk product knowledge",[62,103,104,107],{},[22,105,106],{},"Security",": strict database access, no knowledge base leakage",[62,109,110],{},"Contoh real implementation dengan Baileys.js & GOWA",[112,113],"hr",{},[115,116,118],"h2",{"id":117},"kenapa-openclaw-bukan-cs-biasa","🧠 Kenapa OpenClaw Bukan CS Biasa",[14,120,121],{},"Chatbot CS yang ada sekarang kebanyakan pakai decision tree — \"tekan 1 untuk cek pesanan, tekan 2 untuk komplain.\" Boring, rigid, dan customer bosen.",[14,123,124,125,128],{},"OpenClaw beda. Dia ",[22,126,127],{},"AI agent"," yang ngerti konteks, bisa nerima instruksi bebas, dan bisa akses tools. Bukan chatbot — lebih kayak CS manusia yang punya akses ke semua sistem.",[130,131,136],"pre",{"className":132,"code":134,"language":135},[133],"language-text","CS Tradisional vs OpenClaw CS\n\nCS Bot Biasa:\n  Customer: \"Kak, ukuran L ready ga?\"\n  Bot:      \"Maaf, saya tidak mengerti. Silakan pilih menu: 1. Cek stok 2. ...\"\n\nOpenClaw CS:\n  Customer: \"Kak, ukuran L ready ga? sama warna biru ada berapa?\"\n  OC:       \"Halo kak! 👋 Size L ready kak, tersedia 12 pcs. Warna biru ada 3 shade: Navy, Baby Blue, dan Sky Blue. Navy yang paling laku nih, cuma sisa 2 kak. Mau akureservasi dulu?\"\n","text",[137,138,134],"code",{"__ignoreMap":139},"",[14,141,142,143,146],{},"Tapi kekuatan ini juga jadi risiko kalau arsitekturnya salah. Makanya gue tekankan: ",[22,144,145],{},"gateway pattern itu bukan opsional",".",[115,148,150],{"id":149},"dua-pendekatan-pilih-yang-mana","🔄 Dua Pendekatan: Pilih yang Mana?",[14,152,153],{},"Sebelum masuk ke teknikal, penting banget paham dua cara ngebangun CS bot ini. Banyak yang salah persepsi di sini.",[155,156,158],"h3",{"id":157},"pendekatan-1-openclaw-sebagai-ai-cs-langsung","Pendekatan 1: OpenClaw sebagai AI CS (Langsung)",[130,160,163],{"className":161,"code":162,"language":135},[133],"Customer → WA → Gateway → OpenClaw Agent → Response\n\nOpenClaw jalan 24\u002F7 sebagai otak CS.\nMenerima pesan, proses, dan jawab.\n",[137,164,162],{"__ignoreMap":139},[14,166,167],{},[22,168,169],{},"Cocok kalau:",[85,171,172,175,178,181],{},[62,173,174],{},"Mau setup cepat, MVP dulu",[62,176,177],{},"Volume CS nggak terlalu tinggi (\u003C 100 chat\u002Fhari)",[62,179,180],{},"Butuh fleksibilitas tinggi (AI bisa handle edge case)",[62,182,183],{},"Nggak punya dev team dedicated",[14,185,186],{},"** Risiko:**",[85,188,189,192,195],{},[62,190,191],{},"OpenClaw bukan tool yang didesain untuk CS production 24\u002F7",[62,193,194],{},"Cost LLM bisa numpuk kalau volume tinggi",[62,196,197],{},"Kalau OpenClaw down = CS mati",[155,199,201],{"id":200},"pendekatan-2-openclaw-sebagai-builder-recommended","Pendekatan 2: OpenClaw sebagai Builder (Recommended)",[130,203,206],{"className":204,"code":205,"language":135},[133],"DEVELOPMENT:\n  Kamu → OpenClaw → \"Bikinin WA-Gateway dong\"\n  Kamu → OpenClaw → \"Setup Contact-Service dengan PostgreSQL\"\n  Kamu → OpenClaw → \"Bikin Invoice-Service + Midglass integration\"\n  OpenClaw → Generate semua kode, setup DB, test\n\nPRODUCTION:\n  Customer → WA → Gateway → AI Service (ringan) → Response\n                                  ↓\n                            Stock-Service (DB)\n                            Contact-Service (DB)\n                            Invoice-Service (DB)\n",[137,207,205],{"__ignoreMap":139},[14,209,210],{},[22,211,169],{},[85,213,214,217,220,223],{},[62,215,216],{},"Mau production-ready system",[62,218,219],{},"Butuh uptime tinggi",[62,221,222],{},"Pengen kontrol penuh atas AI behavior",[62,224,225],{},"Volume CS tinggi",[14,227,228],{},[22,229,230],{},"Keuntungan:",[85,232,233,240,243,246],{},[62,234,235,236,239],{},"OpenClaw dipakai sebagai ",[22,237,238],{},"development tool"," — bikin kode, setup infra, debugging",[62,241,242],{},"Di production, pakai AI service yang lebih ringan dan dedicated",[62,244,245],{},"Lebih murah di jangka panjang",[62,247,248],{},"Lebih reliable",[130,250,254],{"className":251,"code":252,"language":253,"meta":139,"style":139},"language-mermaid shiki shiki-themes github-light github-dark","flowchart TD\n    subgraph Dev[\"🛠️ Development Phase (OpenClaw)\"]\n        D1[\"Prompt: Bikin WA-Gateway pakai Baileys\"]\n        D2[\"OpenClaw generate kode + setup\"]\n        D3[\"Prompt: Setup Contact DB + API\"]\n        D4[\"OpenClaw generate migration + endpoint\"]\n        D5[\"Prompt: Bikin Invoice + PG integration\"]\n        D6[\"OpenClaw generate service + webhook handler\"]\n        D7[\"Prompt: Vectorize product catalog\"]\n        D8[\"OpenClaw chunk + embed + store\"]\n        D1 --> D2 --> D3 --> D4 --> D5 --> D6 --> D7 --> D8\n    end\n\n    subgraph Prod[\"🚀 Production (Tanpa OpenClaw)\"]\n        P1[\"WA → Gateway → Lightweight AI → Response\"]\n        P2[\"AI query Stock-Service, Contact-Service\"]\n        P3[\"AI trigger Invoice-Service untuk checkout\"]\n        P1 --> P2 --> P3\n    end\n\n    Dev -->|\"Deploy semua service\"| Prod\n\n    style Dev fill:#fff3cd,stroke:#ffc107\n    style Prod fill:#d4edda,stroke:#28a745\n","mermaid",[137,255,256,263,269,275,281,287,293,299,305,311,317,323,329,336,342,348,354,360,366,371,376,382,387,393],{"__ignoreMap":139},[17,257,260],{"class":258,"line":259},"line",1,[17,261,262],{},"flowchart TD\n",[17,264,266],{"class":258,"line":265},2,[17,267,268],{},"    subgraph Dev[\"🛠️ Development Phase (OpenClaw)\"]\n",[17,270,272],{"class":258,"line":271},3,[17,273,274],{},"        D1[\"Prompt: Bikin WA-Gateway pakai Baileys\"]\n",[17,276,278],{"class":258,"line":277},4,[17,279,280],{},"        D2[\"OpenClaw generate kode + setup\"]\n",[17,282,284],{"class":258,"line":283},5,[17,285,286],{},"        D3[\"Prompt: Setup Contact DB + API\"]\n",[17,288,290],{"class":258,"line":289},6,[17,291,292],{},"        D4[\"OpenClaw generate migration + endpoint\"]\n",[17,294,296],{"class":258,"line":295},7,[17,297,298],{},"        D5[\"Prompt: Bikin Invoice + PG integration\"]\n",[17,300,302],{"class":258,"line":301},8,[17,303,304],{},"        D6[\"OpenClaw generate service + webhook handler\"]\n",[17,306,308],{"class":258,"line":307},9,[17,309,310],{},"        D7[\"Prompt: Vectorize product catalog\"]\n",[17,312,314],{"class":258,"line":313},10,[17,315,316],{},"        D8[\"OpenClaw chunk + embed + store\"]\n",[17,318,320],{"class":258,"line":319},11,[17,321,322],{},"        D1 --> D2 --> D3 --> D4 --> D5 --> D6 --> D7 --> D8\n",[17,324,326],{"class":258,"line":325},12,[17,327,328],{},"    end\n",[17,330,332],{"class":258,"line":331},13,[17,333,335],{"emptyLinePlaceholder":334},true,"\n",[17,337,339],{"class":258,"line":338},14,[17,340,341],{},"    subgraph Prod[\"🚀 Production (Tanpa OpenClaw)\"]\n",[17,343,345],{"class":258,"line":344},15,[17,346,347],{},"        P1[\"WA → Gateway → Lightweight AI → Response\"]\n",[17,349,351],{"class":258,"line":350},16,[17,352,353],{},"        P2[\"AI query Stock-Service, Contact-Service\"]\n",[17,355,357],{"class":258,"line":356},17,[17,358,359],{},"        P3[\"AI trigger Invoice-Service untuk checkout\"]\n",[17,361,363],{"class":258,"line":362},18,[17,364,365],{},"        P1 --> P2 --> P3\n",[17,367,369],{"class":258,"line":368},19,[17,370,328],{},[17,372,374],{"class":258,"line":373},20,[17,375,335],{"emptyLinePlaceholder":334},[17,377,379],{"class":258,"line":378},21,[17,380,381],{},"    Dev -->|\"Deploy semua service\"| Prod\n",[17,383,385],{"class":258,"line":384},22,[17,386,335],{"emptyLinePlaceholder":334},[17,388,390],{"class":258,"line":389},23,[17,391,392],{},"    style Dev fill:#fff3cd,stroke:#ffc107\n",[17,394,396],{"class":258,"line":395},24,[17,397,398],{},"    style Prod fill:#d4edda,stroke:#28a745\n",[155,400,402],{"id":401},"panduan-workflow-dengan-openclaw-sebagai-builder","Panduan Workflow dengan OpenClaw sebagai Builder",[14,404,405,406,409],{},"Nah, kalau lo pilih pendekatan 2 (yang ",[22,407,408],{},"direkomendasikan","), ini workflow-nya:",[14,411,412],{},[22,413,414],{},"Step 1: Setup WA-Gateway",[11,416,417],{},[14,418,419],{},"\"OpenClaw, bikinin WA-Gateway pakai Baileys.js. Service ini nerima pesan dari WhatsApp, queue ke Redis, dan expose webhook endpoint buat AI response. Include auth middleware dan rate limiting. Masing-masing script tolong dokumentasiin di TOOLS.md.\"**",[14,421,422],{},[22,423,424],{},"Step 2: Setup Contact-Service",[11,426,427],{},[14,428,429],{},"\"OpenClaw, bikin Contact-Service dengan PostgreSQL. Schema: contacts (wa_number, name, email, address, order_history) dan addresses (label, full_address, is_default). Expose REST API: GET \u002Flookup?wa_number=xxx, PATCH \u002Fcontacts\u002F:id, GET \u002Fcontacts\u002F:id\u002Forders. Include audit logging.\"**",[14,431,432],{},[22,433,434],{},"Step 3: Setup Invoice-Service",[11,436,437],{},[14,438,439],{},"\"OpenClaw, bikin Invoice-Service. Schema: orders dan invoices. API: POST \u002Forders (create + generate invoice), GET \u002Forders\u002F:id\u002Fstatus, webhook \u002Fpayment\u002Fcallback untuk terima notifikasi dari Payment Gateway. Integration dengan Midtrans\u002FXendit.\"**",[14,441,442],{},[22,443,444],{},"Step 4: Setup Stock-Service + RAG",[11,446,447],{},[14,448,449],{},"\"OpenClaw, bikin Stock-Service untuk product catalog. Vectorize semua data produk pakai PgVector. Query endpoint: POST \u002Fproducts\u002Fsearch (semantic search pakai embedding). Filter: in_stock=true.\"**",[14,451,452],{},[22,453,454],{},"Step 5: Hubungkan semua",[11,456,457],{},[14,458,459],{},"\"OpenClaw, bikin AI service ringan yang jadi otak CS. Service ini subscribe ke Redis queue dari WA-Gateway, query Stock-Service + Contact-Service, dan generate response. Makin semua endpoint ke TOOLS.md biar gampang maintenance.\"**",[14,461,462,463,466,467,470],{},"💡 ",[22,464,465],{},"Tips dari komunitas:"," Masing-masing service WAJIB punya dokumentasi sendiri dan di-link ke ",[137,468,469],{},"TOOLS.md",". Biar AI (baik OpenClaw saat development maupun AI service saat production) nggak bingung endpoint apa yang tersedia.",[112,472],{},[115,474,476],{"id":475},"️-arsitektur-gateway-pattern-wajib","🏗️ Arsitektur: Gateway Pattern (WAJIB)",[14,478,479,480,483],{},"Ini arsitektur yang ",[22,481,482],{},"harus"," dipakai. Jangan skip.",[130,485,487],{"className":251,"code":486,"language":253,"meta":139,"style":139},"flowchart LR\n    subgraph Customer[\"📱 Customer\"]\n        WA[\"WhatsApp\\nCustomer\"]\n    end\n\n    subgraph Gateway[\"🔐 Gateway Layer\"]\n        WA_API[\"WhatsApp API\\n(Baileys\u002FGOWA)\"]\n        MSG_Q[\"Message Queue\\nRedis\"]\n        AUTH[\"Auth & Rate Limit\"]\n        LOG[\"Chat Log DB\"]\n    end\n\n    subgraph AI[\"🧠 AI Service (Production)\"]\n        AGENT[\"Lightweight CS Agent\"]\n    end\n\n    subgraph Services[\"⚙️ Backend Services\"]\n        CONTACT_DB[\"Contact Service\\n(PostgreSQL)\"]\n        STOCK_SVC[\"Stock Service\\n(Vector DB + RAG)\"]\n        INVOICE_SVC[\"Invoice Service\\n(REST API)\"]\n        PG[\"Payment Gateway\\n(Midtrans\u002FXendit)\"]\n    end\n\n    WA --> WA_API\n    WA_API --> AUTH\n    AUTH --> MSG_Q\n    MSG_Q --> LOG\n    MSG_Q --> AGENT\n    AGENT --> CONTACT_DB\n    AGENT --> STOCK_SVC\n    AGENT --> INVOICE_SVC\n    INVOICE_SVC --> PG\n    AGENT --> WA_API\n    WA_API --> WA\n\n    style Gateway fill:#fff3cd,stroke:#ffc107\n    style AI fill:#d4edda,stroke:#28a745\n    style Services fill:#d1ecf1,stroke:#17a2b8\n",[137,488,489,494,499,504,508,512,517,522,527,532,537,541,545,550,555,559,563,568,573,578,583,588,592,596,601,607,613,619,625,631,637,643,649,655,661,666,672,678],{"__ignoreMap":139},[17,490,491],{"class":258,"line":259},[17,492,493],{},"flowchart LR\n",[17,495,496],{"class":258,"line":265},[17,497,498],{},"    subgraph Customer[\"📱 Customer\"]\n",[17,500,501],{"class":258,"line":271},[17,502,503],{},"        WA[\"WhatsApp\\nCustomer\"]\n",[17,505,506],{"class":258,"line":277},[17,507,328],{},[17,509,510],{"class":258,"line":283},[17,511,335],{"emptyLinePlaceholder":334},[17,513,514],{"class":258,"line":289},[17,515,516],{},"    subgraph Gateway[\"🔐 Gateway Layer\"]\n",[17,518,519],{"class":258,"line":295},[17,520,521],{},"        WA_API[\"WhatsApp API\\n(Baileys\u002FGOWA)\"]\n",[17,523,524],{"class":258,"line":301},[17,525,526],{},"        MSG_Q[\"Message Queue\\nRedis\"]\n",[17,528,529],{"class":258,"line":307},[17,530,531],{},"        AUTH[\"Auth & Rate Limit\"]\n",[17,533,534],{"class":258,"line":313},[17,535,536],{},"        LOG[\"Chat Log DB\"]\n",[17,538,539],{"class":258,"line":319},[17,540,328],{},[17,542,543],{"class":258,"line":325},[17,544,335],{"emptyLinePlaceholder":334},[17,546,547],{"class":258,"line":331},[17,548,549],{},"    subgraph AI[\"🧠 AI Service (Production)\"]\n",[17,551,552],{"class":258,"line":338},[17,553,554],{},"        AGENT[\"Lightweight CS Agent\"]\n",[17,556,557],{"class":258,"line":344},[17,558,328],{},[17,560,561],{"class":258,"line":350},[17,562,335],{"emptyLinePlaceholder":334},[17,564,565],{"class":258,"line":356},[17,566,567],{},"    subgraph Services[\"⚙️ Backend Services\"]\n",[17,569,570],{"class":258,"line":362},[17,571,572],{},"        CONTACT_DB[\"Contact Service\\n(PostgreSQL)\"]\n",[17,574,575],{"class":258,"line":368},[17,576,577],{},"        STOCK_SVC[\"Stock Service\\n(Vector DB + RAG)\"]\n",[17,579,580],{"class":258,"line":373},[17,581,582],{},"        INVOICE_SVC[\"Invoice Service\\n(REST API)\"]\n",[17,584,585],{"class":258,"line":378},[17,586,587],{},"        PG[\"Payment Gateway\\n(Midtrans\u002FXendit)\"]\n",[17,589,590],{"class":258,"line":384},[17,591,328],{},[17,593,594],{"class":258,"line":389},[17,595,335],{"emptyLinePlaceholder":334},[17,597,598],{"class":258,"line":395},[17,599,600],{},"    WA --> WA_API\n",[17,602,604],{"class":258,"line":603},25,[17,605,606],{},"    WA_API --> AUTH\n",[17,608,610],{"class":258,"line":609},26,[17,611,612],{},"    AUTH --> MSG_Q\n",[17,614,616],{"class":258,"line":615},27,[17,617,618],{},"    MSG_Q --> LOG\n",[17,620,622],{"class":258,"line":621},28,[17,623,624],{},"    MSG_Q --> AGENT\n",[17,626,628],{"class":258,"line":627},29,[17,629,630],{},"    AGENT --> CONTACT_DB\n",[17,632,634],{"class":258,"line":633},30,[17,635,636],{},"    AGENT --> STOCK_SVC\n",[17,638,640],{"class":258,"line":639},31,[17,641,642],{},"    AGENT --> INVOICE_SVC\n",[17,644,646],{"class":258,"line":645},32,[17,647,648],{},"    INVOICE_SVC --> PG\n",[17,650,652],{"class":258,"line":651},33,[17,653,654],{},"    AGENT --> WA_API\n",[17,656,658],{"class":258,"line":657},34,[17,659,660],{},"    WA_API --> WA\n",[17,662,664],{"class":258,"line":663},35,[17,665,335],{"emptyLinePlaceholder":334},[17,667,669],{"class":258,"line":668},36,[17,670,671],{},"    style Gateway fill:#fff3cd,stroke:#ffc107\n",[17,673,675],{"class":258,"line":674},37,[17,676,677],{},"    style AI fill:#d4edda,stroke:#28a745\n",[17,679,681],{"class":258,"line":680},38,[17,682,683],{},"    style Services fill:#d1ecf1,stroke:#17a2b8\n",[155,685,687],{"id":686},"kenapa-nggak-langsung-ai-whatsapp","Kenapa Nggak Langsung AI → WhatsApp?",[689,690,691,707],"table",{},[692,693,694],"thead",{},[695,696,697,701,704],"tr",{},[698,699,700],"th",{},"Aspek",[698,702,703],{},"Direct Connect",[698,705,706],{},"Via Gateway",[708,709,710,723,736,749,762,775,788,800],"tbody",{},[695,711,712,717,720],{},[713,714,715],"td",{},[22,716,106],{},[713,718,719],{},"AI punya akses penuh ke WA",[713,721,722],{},"Gateway filter + sanitize",[695,724,725,730,733],{},[713,726,727],{},[22,728,729],{},"Uptime",[713,731,732],{},"Kalau AI down, CS mati",[713,734,735],{},"Gateway bisa queue messages",[695,737,738,743,746],{},[713,739,740],{},[22,741,742],{},"Scale",[713,744,745],{},"Satu instance handle semua",[713,747,748],{},"Gateway bisa load balance",[695,750,751,756,759],{},[713,752,753],{},[22,754,755],{},"Rate Limit",[713,757,758],{},"Nggak ada",[713,760,761],{},"Gateway enforce rate limit",[695,763,764,769,772],{},[713,765,766],{},[22,767,768],{},"Audit",[713,770,771],{},"Susah trace",[713,773,774],{},"Semua message logged",[695,776,777,782,785],{},[713,778,779],{},[22,780,781],{},"Multi-tenant",[713,783,784],{},"Ribet",[713,786,787],{},"Gateway handle routing",[695,789,790,795,797],{},[713,791,792],{},[22,793,794],{},"Fallback",[713,796,758],{},[713,798,799],{},"Gateway bisa fallback ke human CS",[695,801,802,807,810],{},[713,803,804],{},[22,805,806],{},"Hot swap AI",[713,808,809],{},"Susak ganti model",[713,811,812],{},"Gateway nggak peduli AI-nya apa",[14,814,815,818],{},[22,816,817],{},"Point terakhir itu kunci."," Kalau AI-nya lewat gateway, lo bisa ganti-ganti model AI (GPT, Claude, Gemini, local LLM) tanpa sentuh gateway sama sekali. Gateway cuma terima pesan, kirim ke AI, terima response, kirim ke WA. Simple.",[14,820,821],{},[22,822,823],{},"Jawabannya jelas: selalu pakai gateway.",[112,825],{},[115,827,829],{"id":828},"️-komponen-1-wa-gateway","⚙️ Komponen 1: WA-Gateway",[14,831,832],{},"Ini jembatan antara WhatsApp dan OpenClaw. Tugasnya:",[59,834,835,838,841,844,847],{},[62,836,837],{},"Terima pesan masuk dari WA → queue → kirim ke OpenClaw",[62,839,840],{},"Terima response dari OpenClaw → kirim ke WA",[62,842,843],{},"Log semua conversation ke database",[62,845,846],{},"Rate limiting & auth",[62,848,849],{},"Fallback ke human CS kalau AI bingung",[155,851,853],{"id":852},"tech-stack","Tech Stack",[130,855,858],{"className":856,"code":857,"language":135},[133],"WA-Gateway Stack:\n\nWhatsApp API    → Baileys.js (open source, free) \u002F GOWA (managed, nyaman)\nMessage Queue   → Redis (Bull\u002FBullMQ)\nWeb Framework   → Express.js \u002F Fastify\nDatabase        → PostgreSQL (chat logs)\nAuth            → JWT + API Key\n",[137,859,857],{"__ignoreMap":139},[155,861,863],{"id":862},"struktur-folder","Struktur Folder",[130,865,868],{"className":866,"code":867,"language":135},[133],"wa-gateway\u002F\n├── src\u002F\n│   ├── index.js              # Entry point\n│   ├── whatsapp\u002F\n│   │   ├── client.js         # Baileys connection\n│   │   ├── message-handler.js # Parse incoming messages\n│   │   └── sender.js         # Send messages to WA\n│   ├── queue\u002F\n│   │   ├── producer.js       # Push to OpenClaw\n│   │   └── consumer.js       # Receive from OpenClaw\n│   ├── routes\u002F\n│   │   ├── webhook.js        # OpenClaw callback endpoint\n│   │   └── health.js         # Health check\n│   ├── db\u002F\n│   │   ├── chat-log.js       # Log all messages\n│   │   └── contact-sync.js   # Sync contacts\n│   └── middleware\u002F\n│       ├── auth.js           # API key validation\n│       └── rate-limit.js     # Rate limiting\n├── package.json\n└── .env\n",[137,869,867],{"__ignoreMap":139},[155,871,873],{"id":872},"key-endpoint-webhook","Key Endpoint: Webhook",[130,875,879],{"className":876,"code":877,"language":878,"meta":139,"style":139},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F wa-gateway\u002Fsrc\u002Froutes\u002Fwebhook.js\n\u002F\u002F Endpoint ini dipanggil OpenClaw untuk kirim response\n\napp.post('\u002Fapi\u002Fopenclaw\u002Fresponse', authMiddleware, async (req, res) => {\n  const { session_id, message, metadata } = req.body;\n  \n  \u002F\u002F 1. Validate session masih aktif\n  const session = await getSession(session_id);\n  if (!session) return res.status(404).json({ error: 'Session not found' });\n  \n  \u002F\u002F 2. Log response dari OpenClaw\n  await db.chatLog.create({\n    session_id,\n    direction: 'outbound',\n    content: message,\n    source: 'openclaw',\n    metadata\n  });\n  \n  \u002F\u002F 3. Kirim ke WhatsApp\n  await whatsappClient.sendMessage(session.wa_number, message);\n  \n  \u002F\u002F 4. Update session status\n  await db.sessions.update(session_id, {\n    last_activity: new Date(),\n    status: 'active'\n  });\n  \n  res.json({ success: true });\n});\n","javascript",[137,880,881,886,891,895,900,905,910,915,920,925,929,934,939,944,949,954,959,964,969,973,978,983,987,992,997,1002,1007,1011,1015,1020],{"__ignoreMap":139},[17,882,883],{"class":258,"line":259},[17,884,885],{},"\u002F\u002F wa-gateway\u002Fsrc\u002Froutes\u002Fwebhook.js\n",[17,887,888],{"class":258,"line":265},[17,889,890],{},"\u002F\u002F Endpoint ini dipanggil OpenClaw untuk kirim response\n",[17,892,893],{"class":258,"line":271},[17,894,335],{"emptyLinePlaceholder":334},[17,896,897],{"class":258,"line":277},[17,898,899],{},"app.post('\u002Fapi\u002Fopenclaw\u002Fresponse', authMiddleware, async (req, res) => {\n",[17,901,902],{"class":258,"line":283},[17,903,904],{},"  const { session_id, message, metadata } = req.body;\n",[17,906,907],{"class":258,"line":289},[17,908,909],{},"  \n",[17,911,912],{"class":258,"line":295},[17,913,914],{},"  \u002F\u002F 1. Validate session masih aktif\n",[17,916,917],{"class":258,"line":301},[17,918,919],{},"  const session = await getSession(session_id);\n",[17,921,922],{"class":258,"line":307},[17,923,924],{},"  if (!session) return res.status(404).json({ error: 'Session not found' });\n",[17,926,927],{"class":258,"line":313},[17,928,909],{},[17,930,931],{"class":258,"line":319},[17,932,933],{},"  \u002F\u002F 2. Log response dari OpenClaw\n",[17,935,936],{"class":258,"line":325},[17,937,938],{},"  await db.chatLog.create({\n",[17,940,941],{"class":258,"line":331},[17,942,943],{},"    session_id,\n",[17,945,946],{"class":258,"line":338},[17,947,948],{},"    direction: 'outbound',\n",[17,950,951],{"class":258,"line":344},[17,952,953],{},"    content: message,\n",[17,955,956],{"class":258,"line":350},[17,957,958],{},"    source: 'openclaw',\n",[17,960,961],{"class":258,"line":356},[17,962,963],{},"    metadata\n",[17,965,966],{"class":258,"line":362},[17,967,968],{},"  });\n",[17,970,971],{"class":258,"line":368},[17,972,909],{},[17,974,975],{"class":258,"line":373},[17,976,977],{},"  \u002F\u002F 3. Kirim ke WhatsApp\n",[17,979,980],{"class":258,"line":378},[17,981,982],{},"  await whatsappClient.sendMessage(session.wa_number, message);\n",[17,984,985],{"class":258,"line":384},[17,986,909],{},[17,988,989],{"class":258,"line":389},[17,990,991],{},"  \u002F\u002F 4. Update session status\n",[17,993,994],{"class":258,"line":395},[17,995,996],{},"  await db.sessions.update(session_id, {\n",[17,998,999],{"class":258,"line":603},[17,1000,1001],{},"    last_activity: new Date(),\n",[17,1003,1004],{"class":258,"line":609},[17,1005,1006],{},"    status: 'active'\n",[17,1008,1009],{"class":258,"line":615},[17,1010,968],{},[17,1012,1013],{"class":258,"line":621},[17,1014,909],{},[17,1016,1017],{"class":258,"line":627},[17,1018,1019],{},"  res.json({ success: true });\n",[17,1021,1022],{"class":258,"line":633},[17,1023,1024],{},"});\n",[155,1026,1028],{"id":1027},"openclaw-panggil-gateway","OpenClaw Panggil Gateway",[14,1030,1031],{},"Di OpenClaw workspace, buat skill yang manggil gateway:",[130,1033,1035],{"className":876,"code":1034,"language":878,"meta":139,"style":139},"\u002F\u002F skills\u002Fcs-gateway\u002Fsend-message.js\n\u002F\u002F Dipanggil oleh OpenClaw agent setelah proses customer message\n\nasync function sendMessage(sessionId, message, metadata = {}) {\n  const response = await fetch(`${process.env.GATEWAY_URL}\u002Fapi\u002Fopenclaw\u002Fresponse`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'Authorization': `Bearer ${process.env.GATEWAY_API_KEY}`\n    },\n    body: JSON.stringify({\n      session_id: sessionId,\n      message,\n      metadata\n    })\n  });\n  \n  if (!response.ok) {\n    throw new Error(`Gateway error: ${response.status}`);\n  }\n  \n  return response.json();\n}\n\nmodule.exports = { sendMessage };\n",[137,1036,1037,1042,1047,1051,1056,1061,1066,1071,1076,1081,1086,1091,1096,1101,1106,1111,1115,1119,1124,1129,1134,1138,1143,1148,1152],{"__ignoreMap":139},[17,1038,1039],{"class":258,"line":259},[17,1040,1041],{},"\u002F\u002F skills\u002Fcs-gateway\u002Fsend-message.js\n",[17,1043,1044],{"class":258,"line":265},[17,1045,1046],{},"\u002F\u002F Dipanggil oleh OpenClaw agent setelah proses customer message\n",[17,1048,1049],{"class":258,"line":271},[17,1050,335],{"emptyLinePlaceholder":334},[17,1052,1053],{"class":258,"line":277},[17,1054,1055],{},"async function sendMessage(sessionId, message, metadata = {}) {\n",[17,1057,1058],{"class":258,"line":283},[17,1059,1060],{},"  const response = await fetch(`${process.env.GATEWAY_URL}\u002Fapi\u002Fopenclaw\u002Fresponse`, {\n",[17,1062,1063],{"class":258,"line":289},[17,1064,1065],{},"    method: 'POST',\n",[17,1067,1068],{"class":258,"line":295},[17,1069,1070],{},"    headers: {\n",[17,1072,1073],{"class":258,"line":301},[17,1074,1075],{},"      'Content-Type': 'application\u002Fjson',\n",[17,1077,1078],{"class":258,"line":307},[17,1079,1080],{},"      'Authorization': `Bearer ${process.env.GATEWAY_API_KEY}`\n",[17,1082,1083],{"class":258,"line":313},[17,1084,1085],{},"    },\n",[17,1087,1088],{"class":258,"line":319},[17,1089,1090],{},"    body: JSON.stringify({\n",[17,1092,1093],{"class":258,"line":325},[17,1094,1095],{},"      session_id: sessionId,\n",[17,1097,1098],{"class":258,"line":331},[17,1099,1100],{},"      message,\n",[17,1102,1103],{"class":258,"line":338},[17,1104,1105],{},"      metadata\n",[17,1107,1108],{"class":258,"line":344},[17,1109,1110],{},"    })\n",[17,1112,1113],{"class":258,"line":350},[17,1114,968],{},[17,1116,1117],{"class":258,"line":356},[17,1118,909],{},[17,1120,1121],{"class":258,"line":362},[17,1122,1123],{},"  if (!response.ok) {\n",[17,1125,1126],{"class":258,"line":368},[17,1127,1128],{},"    throw new Error(`Gateway error: ${response.status}`);\n",[17,1130,1131],{"class":258,"line":373},[17,1132,1133],{},"  }\n",[17,1135,1136],{"class":258,"line":378},[17,1137,909],{},[17,1139,1140],{"class":258,"line":384},[17,1141,1142],{},"  return response.json();\n",[17,1144,1145],{"class":258,"line":389},[17,1146,1147],{},"}\n",[17,1149,1150],{"class":258,"line":395},[17,1151,335],{"emptyLinePlaceholder":334},[17,1153,1154],{"class":258,"line":603},[17,1155,1156],{},"module.exports = { sendMessage };\n",[14,1158,1159],{},[37,1160],{"alt":1161,"src":1162},"Chatbot flow dari customer message ke AI response","\u002Fimages\u002Fposts\u002Fcs-chatbot-flow.jpg",[112,1164],{},[115,1166,1168],{"id":1167},"komponen-2-contact-service","👤 Komponen 2: Contact-Service",[14,1170,1171,1172,146],{},"Ini service yang nyimpen data customer. Penting banget karena customer yang udah pernah beli ",[22,1173,1174],{},"nggak perlu isi data lagi",[155,1176,1178],{"id":1177},"database-schema","Database Schema",[130,1180,1184],{"className":1181,"code":1182,"language":1183,"meta":139,"style":139},"language-sql shiki shiki-themes github-light github-dark","-- contact-service\u002Fschema.sql\n\nCREATE TABLE contacts (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  wa_number VARCHAR(20) UNIQUE NOT NULL,\n  wa_name VARCHAR(100),\n  full_name VARCHAR(100),\n  email VARCHAR(100),\n  phone VARCHAR(20),\n  address TEXT,\n  province VARCHAR(50),\n  city VARCHAR(50),\n  postal_code VARCHAR(10),\n  \n  -- Metadata\n  first_seen TIMESTAMP DEFAULT NOW(),\n  last_order_at TIMESTAMP,\n  total_orders INTEGER DEFAULT 0,\n  total_spent DECIMAL(12,2) DEFAULT 0,\n  \n  -- Tags & Notes\n  tags TEXT[],\n  notes TEXT,\n  vip BOOLEAN DEFAULT FALSE,\n  \n  created_at TIMESTAMP DEFAULT NOW(),\n  updated_at TIMESTAMP DEFAULT NOW()\n);\n\nCREATE TABLE addresses (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  contact_id UUID REFERENCES contacts(id),\n  label VARCHAR(50),      -- \"Rumah\", \"Kantor\", etc.\n  recipient_name VARCHAR(100),\n  phone VARCHAR(20),\n  full_address TEXT NOT NULL,\n  province VARCHAR(50),\n  city VARCHAR(50),\n  postal_code VARCHAR(10),\n  is_default BOOLEAN DEFAULT FALSE,\n  created_at TIMESTAMP DEFAULT NOW()\n);\n\n-- Audit log — untuk tracking siapa akses data kapan\nCREATE TABLE contact_audit_log (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  contact_id UUID REFERENCES contacts(id),\n  action VARCHAR(50) NOT NULL,  -- 'view', 'update', 'create'\n  accessed_by VARCHAR(100) NOT NULL, -- 'openclaw-cs', 'admin'\n  changes JSONB,\n  created_at TIMESTAMP DEFAULT NOW()\n);\n","sql",[137,1185,1186,1191,1195,1200,1205,1210,1215,1220,1225,1230,1235,1240,1245,1250,1254,1259,1264,1269,1274,1279,1283,1288,1293,1298,1303,1307,1312,1317,1322,1326,1331,1335,1340,1345,1350,1354,1359,1363,1367,1372,1378,1384,1389,1394,1400,1406,1411,1416,1422,1428,1434,1439],{"__ignoreMap":139},[17,1187,1188],{"class":258,"line":259},[17,1189,1190],{},"-- contact-service\u002Fschema.sql\n",[17,1192,1193],{"class":258,"line":265},[17,1194,335],{"emptyLinePlaceholder":334},[17,1196,1197],{"class":258,"line":271},[17,1198,1199],{},"CREATE TABLE contacts (\n",[17,1201,1202],{"class":258,"line":277},[17,1203,1204],{},"  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n",[17,1206,1207],{"class":258,"line":283},[17,1208,1209],{},"  wa_number VARCHAR(20) UNIQUE NOT NULL,\n",[17,1211,1212],{"class":258,"line":289},[17,1213,1214],{},"  wa_name VARCHAR(100),\n",[17,1216,1217],{"class":258,"line":295},[17,1218,1219],{},"  full_name VARCHAR(100),\n",[17,1221,1222],{"class":258,"line":301},[17,1223,1224],{},"  email VARCHAR(100),\n",[17,1226,1227],{"class":258,"line":307},[17,1228,1229],{},"  phone VARCHAR(20),\n",[17,1231,1232],{"class":258,"line":313},[17,1233,1234],{},"  address TEXT,\n",[17,1236,1237],{"class":258,"line":319},[17,1238,1239],{},"  province VARCHAR(50),\n",[17,1241,1242],{"class":258,"line":325},[17,1243,1244],{},"  city VARCHAR(50),\n",[17,1246,1247],{"class":258,"line":331},[17,1248,1249],{},"  postal_code VARCHAR(10),\n",[17,1251,1252],{"class":258,"line":338},[17,1253,909],{},[17,1255,1256],{"class":258,"line":344},[17,1257,1258],{},"  -- Metadata\n",[17,1260,1261],{"class":258,"line":350},[17,1262,1263],{},"  first_seen TIMESTAMP DEFAULT NOW(),\n",[17,1265,1266],{"class":258,"line":356},[17,1267,1268],{},"  last_order_at TIMESTAMP,\n",[17,1270,1271],{"class":258,"line":362},[17,1272,1273],{},"  total_orders INTEGER DEFAULT 0,\n",[17,1275,1276],{"class":258,"line":368},[17,1277,1278],{},"  total_spent DECIMAL(12,2) DEFAULT 0,\n",[17,1280,1281],{"class":258,"line":373},[17,1282,909],{},[17,1284,1285],{"class":258,"line":378},[17,1286,1287],{},"  -- Tags & Notes\n",[17,1289,1290],{"class":258,"line":384},[17,1291,1292],{},"  tags TEXT[],\n",[17,1294,1295],{"class":258,"line":389},[17,1296,1297],{},"  notes TEXT,\n",[17,1299,1300],{"class":258,"line":395},[17,1301,1302],{},"  vip BOOLEAN DEFAULT FALSE,\n",[17,1304,1305],{"class":258,"line":603},[17,1306,909],{},[17,1308,1309],{"class":258,"line":609},[17,1310,1311],{},"  created_at TIMESTAMP DEFAULT NOW(),\n",[17,1313,1314],{"class":258,"line":615},[17,1315,1316],{},"  updated_at TIMESTAMP DEFAULT NOW()\n",[17,1318,1319],{"class":258,"line":621},[17,1320,1321],{},");\n",[17,1323,1324],{"class":258,"line":627},[17,1325,335],{"emptyLinePlaceholder":334},[17,1327,1328],{"class":258,"line":633},[17,1329,1330],{},"CREATE TABLE addresses (\n",[17,1332,1333],{"class":258,"line":639},[17,1334,1204],{},[17,1336,1337],{"class":258,"line":645},[17,1338,1339],{},"  contact_id UUID REFERENCES contacts(id),\n",[17,1341,1342],{"class":258,"line":651},[17,1343,1344],{},"  label VARCHAR(50),      -- \"Rumah\", \"Kantor\", etc.\n",[17,1346,1347],{"class":258,"line":657},[17,1348,1349],{},"  recipient_name VARCHAR(100),\n",[17,1351,1352],{"class":258,"line":663},[17,1353,1229],{},[17,1355,1356],{"class":258,"line":668},[17,1357,1358],{},"  full_address TEXT NOT NULL,\n",[17,1360,1361],{"class":258,"line":674},[17,1362,1239],{},[17,1364,1365],{"class":258,"line":680},[17,1366,1244],{},[17,1368,1370],{"class":258,"line":1369},39,[17,1371,1249],{},[17,1373,1375],{"class":258,"line":1374},40,[17,1376,1377],{},"  is_default BOOLEAN DEFAULT FALSE,\n",[17,1379,1381],{"class":258,"line":1380},41,[17,1382,1383],{},"  created_at TIMESTAMP DEFAULT NOW()\n",[17,1385,1387],{"class":258,"line":1386},42,[17,1388,1321],{},[17,1390,1392],{"class":258,"line":1391},43,[17,1393,335],{"emptyLinePlaceholder":334},[17,1395,1397],{"class":258,"line":1396},44,[17,1398,1399],{},"-- Audit log — untuk tracking siapa akses data kapan\n",[17,1401,1403],{"class":258,"line":1402},45,[17,1404,1405],{},"CREATE TABLE contact_audit_log (\n",[17,1407,1409],{"class":258,"line":1408},46,[17,1410,1204],{},[17,1412,1414],{"class":258,"line":1413},47,[17,1415,1339],{},[17,1417,1419],{"class":258,"line":1418},48,[17,1420,1421],{},"  action VARCHAR(50) NOT NULL,  -- 'view', 'update', 'create'\n",[17,1423,1425],{"class":258,"line":1424},49,[17,1426,1427],{},"  accessed_by VARCHAR(100) NOT NULL, -- 'openclaw-cs', 'admin'\n",[17,1429,1431],{"class":258,"line":1430},50,[17,1432,1433],{},"  changes JSONB,\n",[17,1435,1437],{"class":258,"line":1436},51,[17,1438,1383],{},[17,1440,1442],{"class":258,"line":1441},52,[17,1443,1321],{},[155,1445,1447],{"id":1446},"openclaw-skill-contact-service","OpenClaw Skill: Contact Service",[130,1449,1451],{"className":876,"code":1450,"language":878,"meta":139,"style":139},"\u002F\u002F skills\u002Fcontact-service\u002Flookup.js\n\u002F\u002F Cari contact berdasarkan WA number (auto-detect dari incoming message)\n\nasync function lookupContact(waNumber) {\n  const response = await fetch(`${process.env.CONTACT_SERVICE_URL}\u002Fapi\u002Fcontacts\u002Flookup`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'X-Service-Key': process.env.CONTACT_SERVICE_KEY\n    },\n    body: JSON.stringify({ wa_number: waNumber })\n  });\n  \n  if (!response.ok) return null;\n  \n  const data = await response.json();\n  \n  \u002F\u002F Log access untuk audit\n  await logContactAccess(data.id, 'view', 'openclaw-cs');\n  \n  return data;\n}\n\n\u002F\u002F Update contact data (setelah user konfirmasi)\nasync function updateContact(contactId, updates) {\n  const response = await fetch(\n    `${process.env.CONTACT_SERVICE_URL}\u002Fapi\u002Fcontacts\u002F${contactId}`,\n    {\n      method: 'PATCH',\n      headers: {\n        'Content-Type': 'application\u002Fjson',\n        'X-Service-Key': process.env.CONTACT_SERVICE_KEY\n      },\n      body: JSON.stringify(updates)\n    }\n  );\n  \n  if (!response.ok) throw new Error('Failed to update contact');\n  \n  \u002F\u002F Log perubahan\n  await logContactAccess(contactId, 'update', 'openclaw-cs', updates);\n  \n  return response.json();\n}\n",[137,1452,1453,1458,1463,1467,1472,1477,1481,1485,1489,1494,1498,1503,1507,1511,1516,1520,1525,1529,1534,1539,1543,1548,1552,1556,1561,1566,1571,1576,1581,1586,1591,1596,1601,1606,1611,1616,1621,1625,1630,1634,1639,1644,1648,1652],{"__ignoreMap":139},[17,1454,1455],{"class":258,"line":259},[17,1456,1457],{},"\u002F\u002F skills\u002Fcontact-service\u002Flookup.js\n",[17,1459,1460],{"class":258,"line":265},[17,1461,1462],{},"\u002F\u002F Cari contact berdasarkan WA number (auto-detect dari incoming message)\n",[17,1464,1465],{"class":258,"line":271},[17,1466,335],{"emptyLinePlaceholder":334},[17,1468,1469],{"class":258,"line":277},[17,1470,1471],{},"async function lookupContact(waNumber) {\n",[17,1473,1474],{"class":258,"line":283},[17,1475,1476],{},"  const response = await fetch(`${process.env.CONTACT_SERVICE_URL}\u002Fapi\u002Fcontacts\u002Flookup`, {\n",[17,1478,1479],{"class":258,"line":289},[17,1480,1065],{},[17,1482,1483],{"class":258,"line":295},[17,1484,1070],{},[17,1486,1487],{"class":258,"line":301},[17,1488,1075],{},[17,1490,1491],{"class":258,"line":307},[17,1492,1493],{},"      'X-Service-Key': process.env.CONTACT_SERVICE_KEY\n",[17,1495,1496],{"class":258,"line":313},[17,1497,1085],{},[17,1499,1500],{"class":258,"line":319},[17,1501,1502],{},"    body: JSON.stringify({ wa_number: waNumber })\n",[17,1504,1505],{"class":258,"line":325},[17,1506,968],{},[17,1508,1509],{"class":258,"line":331},[17,1510,909],{},[17,1512,1513],{"class":258,"line":338},[17,1514,1515],{},"  if (!response.ok) return null;\n",[17,1517,1518],{"class":258,"line":344},[17,1519,909],{},[17,1521,1522],{"class":258,"line":350},[17,1523,1524],{},"  const data = await response.json();\n",[17,1526,1527],{"class":258,"line":356},[17,1528,909],{},[17,1530,1531],{"class":258,"line":362},[17,1532,1533],{},"  \u002F\u002F Log access untuk audit\n",[17,1535,1536],{"class":258,"line":368},[17,1537,1538],{},"  await logContactAccess(data.id, 'view', 'openclaw-cs');\n",[17,1540,1541],{"class":258,"line":373},[17,1542,909],{},[17,1544,1545],{"class":258,"line":378},[17,1546,1547],{},"  return data;\n",[17,1549,1550],{"class":258,"line":384},[17,1551,1147],{},[17,1553,1554],{"class":258,"line":389},[17,1555,335],{"emptyLinePlaceholder":334},[17,1557,1558],{"class":258,"line":395},[17,1559,1560],{},"\u002F\u002F Update contact data (setelah user konfirmasi)\n",[17,1562,1563],{"class":258,"line":603},[17,1564,1565],{},"async function updateContact(contactId, updates) {\n",[17,1567,1568],{"class":258,"line":609},[17,1569,1570],{},"  const response = await fetch(\n",[17,1572,1573],{"class":258,"line":615},[17,1574,1575],{},"    `${process.env.CONTACT_SERVICE_URL}\u002Fapi\u002Fcontacts\u002F${contactId}`,\n",[17,1577,1578],{"class":258,"line":621},[17,1579,1580],{},"    {\n",[17,1582,1583],{"class":258,"line":627},[17,1584,1585],{},"      method: 'PATCH',\n",[17,1587,1588],{"class":258,"line":633},[17,1589,1590],{},"      headers: {\n",[17,1592,1593],{"class":258,"line":639},[17,1594,1595],{},"        'Content-Type': 'application\u002Fjson',\n",[17,1597,1598],{"class":258,"line":645},[17,1599,1600],{},"        'X-Service-Key': process.env.CONTACT_SERVICE_KEY\n",[17,1602,1603],{"class":258,"line":651},[17,1604,1605],{},"      },\n",[17,1607,1608],{"class":258,"line":657},[17,1609,1610],{},"      body: JSON.stringify(updates)\n",[17,1612,1613],{"class":258,"line":663},[17,1614,1615],{},"    }\n",[17,1617,1618],{"class":258,"line":668},[17,1619,1620],{},"  );\n",[17,1622,1623],{"class":258,"line":674},[17,1624,909],{},[17,1626,1627],{"class":258,"line":680},[17,1628,1629],{},"  if (!response.ok) throw new Error('Failed to update contact');\n",[17,1631,1632],{"class":258,"line":1369},[17,1633,909],{},[17,1635,1636],{"class":258,"line":1374},[17,1637,1638],{},"  \u002F\u002F Log perubahan\n",[17,1640,1641],{"class":258,"line":1380},[17,1642,1643],{},"  await logContactAccess(contactId, 'update', 'openclaw-cs', updates);\n",[17,1645,1646],{"class":258,"line":1386},[17,1647,909],{},[17,1649,1650],{"class":258,"line":1391},[17,1651,1142],{},[17,1653,1654],{"class":258,"line":1396},[17,1655,1147],{},[155,1657,1659],{"id":1658},"flow-existing-customer-recognition","Flow: Existing Customer Recognition",[130,1661,1663],{"className":251,"code":1662,"language":253,"meta":139,"style":139},"flowchart TD\n    A[\"📱 Customer: Mau pesan kak\"] --> B{\"🔍 Cek WA number\\ndi Contact DB\"}\n    B -->|Found| C[\"📋 Load data:\\nNama, Alamat, Order History\"]\n    C --> D[\"🧠 OpenClaw:\\n'Halo Kak Rina! Pesan lagi nih?\\nKirim ke alamat Jl. Sudirman 45 ya?'\"]\n    D --> E[\"✅ Customer:\\n'Iya kak, tapi ke alamat kantor'\"]\n    \n    B -->|Not Found| F[\"🆕 New Customer Flow\"]\n    F --> G[\"🧠 OpenClaw:\\n'Halo kak! Baru pertama kali pesan ya?\\nBoleh minta nama dan alamat pengiriman?'\"]\n    G --> H[\"📝 Simpan ke Contact DB\"]\n    H --> I[\"🧠 OpenClaw:\\n'Terima kasih Kak Rina! Data sudah tersimpan.\\nNggak perlu isi lagi di pesanan berikutnya 👍'\"]\n\n    style A fill:#d4edda\n    style D fill:#fff3cd\n    style F fill:#d1ecf1\n",[137,1664,1665,1669,1674,1679,1684,1689,1694,1699,1704,1709,1714,1718,1723,1728],{"__ignoreMap":139},[17,1666,1667],{"class":258,"line":259},[17,1668,262],{},[17,1670,1671],{"class":258,"line":265},[17,1672,1673],{},"    A[\"📱 Customer: Mau pesan kak\"] --> B{\"🔍 Cek WA number\\ndi Contact DB\"}\n",[17,1675,1676],{"class":258,"line":271},[17,1677,1678],{},"    B -->|Found| C[\"📋 Load data:\\nNama, Alamat, Order History\"]\n",[17,1680,1681],{"class":258,"line":277},[17,1682,1683],{},"    C --> D[\"🧠 OpenClaw:\\n'Halo Kak Rina! Pesan lagi nih?\\nKirim ke alamat Jl. Sudirman 45 ya?'\"]\n",[17,1685,1686],{"class":258,"line":283},[17,1687,1688],{},"    D --> E[\"✅ Customer:\\n'Iya kak, tapi ke alamat kantor'\"]\n",[17,1690,1691],{"class":258,"line":289},[17,1692,1693],{},"    \n",[17,1695,1696],{"class":258,"line":295},[17,1697,1698],{},"    B -->|Not Found| F[\"🆕 New Customer Flow\"]\n",[17,1700,1701],{"class":258,"line":301},[17,1702,1703],{},"    F --> G[\"🧠 OpenClaw:\\n'Halo kak! Baru pertama kali pesan ya?\\nBoleh minta nama dan alamat pengiriman?'\"]\n",[17,1705,1706],{"class":258,"line":307},[17,1707,1708],{},"    G --> H[\"📝 Simpan ke Contact DB\"]\n",[17,1710,1711],{"class":258,"line":313},[17,1712,1713],{},"    H --> I[\"🧠 OpenClaw:\\n'Terima kasih Kak Rina! Data sudah tersimpan.\\nNggak perlu isi lagi di pesanan berikutnya 👍'\"]\n",[17,1715,1716],{"class":258,"line":319},[17,1717,335],{"emptyLinePlaceholder":334},[17,1719,1720],{"class":258,"line":325},[17,1721,1722],{},"    style A fill:#d4edda\n",[17,1724,1725],{"class":258,"line":331},[17,1726,1727],{},"    style D fill:#fff3cd\n",[17,1729,1730],{"class":258,"line":338},[17,1731,1732],{},"    style F fill:#d1ecf1\n",[14,1734,1735,1738],{},[22,1736,1737],{},"Ini yang bikin beda dari CS bot biasa."," Customer yang udah pernah beli bisa langsung checkout tanpa isi form lagi. Tapi data tetap aman di database — bukan di \"memory\" AI yang bisa bocor.",[112,1740],{},[115,1742,1744],{"id":1743},"komponen-3-invoice-service","🧾 Komponen 3: Invoice-Service",[14,1746,1747],{},"Ini service yang handle invoice generation, payment gateway, dan order tracking.",[130,1749,1751],{"className":251,"code":1750,"language":253,"meta":139,"style":139},"flowchart LR\n    subgraph Trigger[\"OpenClaw Trigger\"]\n        OC_MSG[\"Customer: Saya pesan 3 L biru\"]\n    end\n\n    subgraph Invoice_SVC[\"Invoice Service\"]\n        CREATE[\"Generate Invoice\\n(DB Insert)\"]\n        CALC[\"Calculate Total\\n+ Ongkir)\"]\n        FORMAT[\"Format Invoice\\n(HTML\u002FPDF)\"]\n    end\n\n    subgraph Payment[\"Payment\"]\n        PG_LINK[\"Generate\\nPG Link\"]\n        WEBHOOK[\"PG Webhook\\nCallback\"]\n        CONFIRM[\"Confirm Payment\"]\n    end\n\n    subgraph Result[\"Customer\"]\n        INV[\"📩 Invoice + Link Bayar\"]\n        PAID[\"✅ Payment Confirmed\"]\n        TRACK[\"📦 Order Tracking\"]\n    end\n\n    OC_MSG --> CREATE\n    CREATE --> CALC\n    CALC --> FORMAT\n    FORMAT --> PG_LINK\n    PG_LINK --> INV\n    INV --> WEBHOOK\n    WEBHOOK --> CONFIRM\n    CONFIRM --> PAID\n    CONFIRM --> TRACK\n\n    style Invoice_SVC fill:#d4edda\n    style Payment fill:#fff3cd\n",[137,1752,1753,1757,1762,1767,1771,1775,1780,1785,1790,1795,1799,1803,1808,1813,1818,1823,1827,1831,1836,1841,1846,1851,1855,1859,1864,1869,1874,1879,1884,1889,1894,1899,1904,1908,1913],{"__ignoreMap":139},[17,1754,1755],{"class":258,"line":259},[17,1756,493],{},[17,1758,1759],{"class":258,"line":265},[17,1760,1761],{},"    subgraph Trigger[\"OpenClaw Trigger\"]\n",[17,1763,1764],{"class":258,"line":271},[17,1765,1766],{},"        OC_MSG[\"Customer: Saya pesan 3 L biru\"]\n",[17,1768,1769],{"class":258,"line":277},[17,1770,328],{},[17,1772,1773],{"class":258,"line":283},[17,1774,335],{"emptyLinePlaceholder":334},[17,1776,1777],{"class":258,"line":289},[17,1778,1779],{},"    subgraph Invoice_SVC[\"Invoice Service\"]\n",[17,1781,1782],{"class":258,"line":295},[17,1783,1784],{},"        CREATE[\"Generate Invoice\\n(DB Insert)\"]\n",[17,1786,1787],{"class":258,"line":301},[17,1788,1789],{},"        CALC[\"Calculate Total\\n+ Ongkir)\"]\n",[17,1791,1792],{"class":258,"line":307},[17,1793,1794],{},"        FORMAT[\"Format Invoice\\n(HTML\u002FPDF)\"]\n",[17,1796,1797],{"class":258,"line":313},[17,1798,328],{},[17,1800,1801],{"class":258,"line":319},[17,1802,335],{"emptyLinePlaceholder":334},[17,1804,1805],{"class":258,"line":325},[17,1806,1807],{},"    subgraph Payment[\"Payment\"]\n",[17,1809,1810],{"class":258,"line":331},[17,1811,1812],{},"        PG_LINK[\"Generate\\nPG Link\"]\n",[17,1814,1815],{"class":258,"line":338},[17,1816,1817],{},"        WEBHOOK[\"PG Webhook\\nCallback\"]\n",[17,1819,1820],{"class":258,"line":344},[17,1821,1822],{},"        CONFIRM[\"Confirm Payment\"]\n",[17,1824,1825],{"class":258,"line":350},[17,1826,328],{},[17,1828,1829],{"class":258,"line":356},[17,1830,335],{"emptyLinePlaceholder":334},[17,1832,1833],{"class":258,"line":362},[17,1834,1835],{},"    subgraph Result[\"Customer\"]\n",[17,1837,1838],{"class":258,"line":368},[17,1839,1840],{},"        INV[\"📩 Invoice + Link Bayar\"]\n",[17,1842,1843],{"class":258,"line":373},[17,1844,1845],{},"        PAID[\"✅ Payment Confirmed\"]\n",[17,1847,1848],{"class":258,"line":378},[17,1849,1850],{},"        TRACK[\"📦 Order Tracking\"]\n",[17,1852,1853],{"class":258,"line":384},[17,1854,328],{},[17,1856,1857],{"class":258,"line":389},[17,1858,335],{"emptyLinePlaceholder":334},[17,1860,1861],{"class":258,"line":395},[17,1862,1863],{},"    OC_MSG --> CREATE\n",[17,1865,1866],{"class":258,"line":603},[17,1867,1868],{},"    CREATE --> CALC\n",[17,1870,1871],{"class":258,"line":609},[17,1872,1873],{},"    CALC --> FORMAT\n",[17,1875,1876],{"class":258,"line":615},[17,1877,1878],{},"    FORMAT --> PG_LINK\n",[17,1880,1881],{"class":258,"line":621},[17,1882,1883],{},"    PG_LINK --> INV\n",[17,1885,1886],{"class":258,"line":627},[17,1887,1888],{},"    INV --> WEBHOOK\n",[17,1890,1891],{"class":258,"line":633},[17,1892,1893],{},"    WEBHOOK --> CONFIRM\n",[17,1895,1896],{"class":258,"line":639},[17,1897,1898],{},"    CONFIRM --> PAID\n",[17,1900,1901],{"class":258,"line":645},[17,1902,1903],{},"    CONFIRM --> TRACK\n",[17,1905,1906],{"class":258,"line":651},[17,1907,335],{"emptyLinePlaceholder":334},[17,1909,1910],{"class":258,"line":657},[17,1911,1912],{},"    style Invoice_SVC fill:#d4edda\n",[17,1914,1915],{"class":258,"line":663},[17,1916,1917],{},"    style Payment fill:#fff3cd\n",[155,1919,1921],{"id":1920},"database-schema-orders-invoices","Database Schema: Orders & Invoices",[130,1923,1925],{"className":1181,"code":1924,"language":1183,"meta":139,"style":139},"-- invoice-service\u002Fschema.sql\n\nCREATE TABLE orders (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  order_number VARCHAR(20) UNIQUE NOT NULL,\n  contact_id UUID NOT NULL,\n  \n  -- Items (JSON array)\n  items JSONB NOT NULL,\n  -- Example: [{\"sku\":\"BPL-L-NVY\",\"name\":\"Baju Polos L Navy\",\"qty\":3,\"price\":85000}]\n  \n  -- Pricing\n  subtotal DECIMAL(12,2) NOT NULL,\n  shipping_cost DECIMAL(12,2) DEFAULT 0,\n  discount DECIMAL(12,2) DEFAULT 0,\n  total DECIMAL(12,2) NOT NULL,\n  \n  -- Shipping\n  shipping_address JSONB NOT NULL,\n  courier VARCHAR(50),\n  tracking_number VARCHAR(50),\n  \n  -- Status\n  status VARCHAR(20) DEFAULT 'pending',\n  -- pending → paid → processing → shipped → delivered → completed\n  \n  payment_method VARCHAR(30),\n  paid_at TIMESTAMP,\n  shipped_at TIMESTAMP,\n  delivered_at TIMESTAMP,\n  \n  created_at TIMESTAMP DEFAULT NOW(),\n  updated_at TIMESTAMP DEFAULT NOW()\n);\n\nCREATE TABLE invoices (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  order_id UUID UNIQUE REFERENCES orders(id),\n  invoice_number VARCHAR(20) UNIQUE NOT NULL,\n  amount DECIMAL(12,2) NOT NULL,\n  due_date TIMESTAMP,\n  status VARCHAR(20) DEFAULT 'unpaid',\n  payment_url VARCHAR(500),  -- PG payment link\n  pg_transaction_id VARCHAR(100),\n  paid_at TIMESTAMP,\n  created_at TIMESTAMP DEFAULT NOW()\n);\n",[137,1926,1927,1932,1936,1941,1945,1950,1955,1959,1964,1969,1974,1978,1983,1988,1993,1998,2003,2007,2012,2017,2022,2027,2031,2036,2041,2046,2050,2055,2060,2065,2070,2074,2078,2082,2086,2090,2095,2099,2104,2109,2114,2119,2124,2129,2134,2138,2142],{"__ignoreMap":139},[17,1928,1929],{"class":258,"line":259},[17,1930,1931],{},"-- invoice-service\u002Fschema.sql\n",[17,1933,1934],{"class":258,"line":265},[17,1935,335],{"emptyLinePlaceholder":334},[17,1937,1938],{"class":258,"line":271},[17,1939,1940],{},"CREATE TABLE orders (\n",[17,1942,1943],{"class":258,"line":277},[17,1944,1204],{},[17,1946,1947],{"class":258,"line":283},[17,1948,1949],{},"  order_number VARCHAR(20) UNIQUE NOT NULL,\n",[17,1951,1952],{"class":258,"line":289},[17,1953,1954],{},"  contact_id UUID NOT NULL,\n",[17,1956,1957],{"class":258,"line":295},[17,1958,909],{},[17,1960,1961],{"class":258,"line":301},[17,1962,1963],{},"  -- Items (JSON array)\n",[17,1965,1966],{"class":258,"line":307},[17,1967,1968],{},"  items JSONB NOT NULL,\n",[17,1970,1971],{"class":258,"line":313},[17,1972,1973],{},"  -- Example: [{\"sku\":\"BPL-L-NVY\",\"name\":\"Baju Polos L Navy\",\"qty\":3,\"price\":85000}]\n",[17,1975,1976],{"class":258,"line":319},[17,1977,909],{},[17,1979,1980],{"class":258,"line":325},[17,1981,1982],{},"  -- Pricing\n",[17,1984,1985],{"class":258,"line":331},[17,1986,1987],{},"  subtotal DECIMAL(12,2) NOT NULL,\n",[17,1989,1990],{"class":258,"line":338},[17,1991,1992],{},"  shipping_cost DECIMAL(12,2) DEFAULT 0,\n",[17,1994,1995],{"class":258,"line":344},[17,1996,1997],{},"  discount DECIMAL(12,2) DEFAULT 0,\n",[17,1999,2000],{"class":258,"line":350},[17,2001,2002],{},"  total DECIMAL(12,2) NOT NULL,\n",[17,2004,2005],{"class":258,"line":356},[17,2006,909],{},[17,2008,2009],{"class":258,"line":362},[17,2010,2011],{},"  -- Shipping\n",[17,2013,2014],{"class":258,"line":368},[17,2015,2016],{},"  shipping_address JSONB NOT NULL,\n",[17,2018,2019],{"class":258,"line":373},[17,2020,2021],{},"  courier VARCHAR(50),\n",[17,2023,2024],{"class":258,"line":378},[17,2025,2026],{},"  tracking_number VARCHAR(50),\n",[17,2028,2029],{"class":258,"line":384},[17,2030,909],{},[17,2032,2033],{"class":258,"line":389},[17,2034,2035],{},"  -- Status\n",[17,2037,2038],{"class":258,"line":395},[17,2039,2040],{},"  status VARCHAR(20) DEFAULT 'pending',\n",[17,2042,2043],{"class":258,"line":603},[17,2044,2045],{},"  -- pending → paid → processing → shipped → delivered → completed\n",[17,2047,2048],{"class":258,"line":609},[17,2049,909],{},[17,2051,2052],{"class":258,"line":615},[17,2053,2054],{},"  payment_method VARCHAR(30),\n",[17,2056,2057],{"class":258,"line":621},[17,2058,2059],{},"  paid_at TIMESTAMP,\n",[17,2061,2062],{"class":258,"line":627},[17,2063,2064],{},"  shipped_at TIMESTAMP,\n",[17,2066,2067],{"class":258,"line":633},[17,2068,2069],{},"  delivered_at TIMESTAMP,\n",[17,2071,2072],{"class":258,"line":639},[17,2073,909],{},[17,2075,2076],{"class":258,"line":645},[17,2077,1311],{},[17,2079,2080],{"class":258,"line":651},[17,2081,1316],{},[17,2083,2084],{"class":258,"line":657},[17,2085,1321],{},[17,2087,2088],{"class":258,"line":663},[17,2089,335],{"emptyLinePlaceholder":334},[17,2091,2092],{"class":258,"line":668},[17,2093,2094],{},"CREATE TABLE invoices (\n",[17,2096,2097],{"class":258,"line":674},[17,2098,1204],{},[17,2100,2101],{"class":258,"line":680},[17,2102,2103],{},"  order_id UUID UNIQUE REFERENCES orders(id),\n",[17,2105,2106],{"class":258,"line":1369},[17,2107,2108],{},"  invoice_number VARCHAR(20) UNIQUE NOT NULL,\n",[17,2110,2111],{"class":258,"line":1374},[17,2112,2113],{},"  amount DECIMAL(12,2) NOT NULL,\n",[17,2115,2116],{"class":258,"line":1380},[17,2117,2118],{},"  due_date TIMESTAMP,\n",[17,2120,2121],{"class":258,"line":1386},[17,2122,2123],{},"  status VARCHAR(20) DEFAULT 'unpaid',\n",[17,2125,2126],{"class":258,"line":1391},[17,2127,2128],{},"  payment_url VARCHAR(500),  -- PG payment link\n",[17,2130,2131],{"class":258,"line":1396},[17,2132,2133],{},"  pg_transaction_id VARCHAR(100),\n",[17,2135,2136],{"class":258,"line":1402},[17,2137,2059],{},[17,2139,2140],{"class":258,"line":1408},[17,2141,1383],{},[17,2143,2144],{"class":258,"line":1413},[17,2145,1321],{},[155,2147,2149],{"id":2148},"openclaw-skill-create-order","OpenClaw Skill: Create Order",[130,2151,2153],{"className":876,"code":2152,"language":878,"meta":139,"style":139},"\u002F\u002F skills\u002Finvoice-service\u002Fcreate-order.js\n\nasync function createOrder(contactId, items, shippingAddress) {\n  \u002F\u002F 1. Hitung total\n  const subtotal = items.reduce((sum, item) => sum + (item.price * item.qty), 0);\n  const shippingCost = await calculateShipping(shippingAddress.city);\n  const total = subtotal + shippingCost;\n  \n  \u002F\u002F 2. Create order di database\n  const order = await fetch(`${process.env.INVOICE_SERVICE_URL}\u002Fapi\u002Forders`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application\u002Fjson',\n      'X-Service-Key': process.env.INVOICE_SERVICE_KEY\n    },\n    body: JSON.stringify({\n      contact_id: contactId,\n      items,\n      subtotal,\n      shipping_cost: shippingCost,\n      total,\n      shipping_address: shippingAddress\n    })\n  }).then(r => r.json());\n  \n  \u002F\u002F 3. Create invoice + payment link\n  const invoice = await fetch(\n    `${process.env.INVOICE_SERVICE_URL}\u002Fapi\u002Finvoices`,\n    {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application\u002Fjson',\n        'X-Service-Key': process.env.INVOICE_SERVICE_KEY\n      },\n      body: JSON.stringify({\n        order_id: order.id,\n        amount: total\n      })\n    }\n  ).then(r => r.json());\n  \n  return {\n    order_number: order.order_number,\n    invoice_number: invoice.invoice_number,\n    total: total,\n    payment_url: invoice.payment_url,\n    due_date: invoice.due_date\n  };\n}\n",[137,2154,2155,2160,2164,2169,2174,2179,2184,2189,2193,2198,2203,2207,2211,2215,2220,2224,2228,2233,2238,2243,2248,2253,2258,2262,2267,2271,2276,2281,2286,2290,2295,2299,2303,2308,2312,2317,2322,2327,2332,2336,2341,2345,2350,2355,2360,2365,2370,2375,2380],{"__ignoreMap":139},[17,2156,2157],{"class":258,"line":259},[17,2158,2159],{},"\u002F\u002F skills\u002Finvoice-service\u002Fcreate-order.js\n",[17,2161,2162],{"class":258,"line":265},[17,2163,335],{"emptyLinePlaceholder":334},[17,2165,2166],{"class":258,"line":271},[17,2167,2168],{},"async function createOrder(contactId, items, shippingAddress) {\n",[17,2170,2171],{"class":258,"line":277},[17,2172,2173],{},"  \u002F\u002F 1. Hitung total\n",[17,2175,2176],{"class":258,"line":283},[17,2177,2178],{},"  const subtotal = items.reduce((sum, item) => sum + (item.price * item.qty), 0);\n",[17,2180,2181],{"class":258,"line":289},[17,2182,2183],{},"  const shippingCost = await calculateShipping(shippingAddress.city);\n",[17,2185,2186],{"class":258,"line":295},[17,2187,2188],{},"  const total = subtotal + shippingCost;\n",[17,2190,2191],{"class":258,"line":301},[17,2192,909],{},[17,2194,2195],{"class":258,"line":307},[17,2196,2197],{},"  \u002F\u002F 2. Create order di database\n",[17,2199,2200],{"class":258,"line":313},[17,2201,2202],{},"  const order = await fetch(`${process.env.INVOICE_SERVICE_URL}\u002Fapi\u002Forders`, {\n",[17,2204,2205],{"class":258,"line":319},[17,2206,1065],{},[17,2208,2209],{"class":258,"line":325},[17,2210,1070],{},[17,2212,2213],{"class":258,"line":331},[17,2214,1075],{},[17,2216,2217],{"class":258,"line":338},[17,2218,2219],{},"      'X-Service-Key': process.env.INVOICE_SERVICE_KEY\n",[17,2221,2222],{"class":258,"line":344},[17,2223,1085],{},[17,2225,2226],{"class":258,"line":350},[17,2227,1090],{},[17,2229,2230],{"class":258,"line":356},[17,2231,2232],{},"      contact_id: contactId,\n",[17,2234,2235],{"class":258,"line":362},[17,2236,2237],{},"      items,\n",[17,2239,2240],{"class":258,"line":368},[17,2241,2242],{},"      subtotal,\n",[17,2244,2245],{"class":258,"line":373},[17,2246,2247],{},"      shipping_cost: shippingCost,\n",[17,2249,2250],{"class":258,"line":378},[17,2251,2252],{},"      total,\n",[17,2254,2255],{"class":258,"line":384},[17,2256,2257],{},"      shipping_address: shippingAddress\n",[17,2259,2260],{"class":258,"line":389},[17,2261,1110],{},[17,2263,2264],{"class":258,"line":395},[17,2265,2266],{},"  }).then(r => r.json());\n",[17,2268,2269],{"class":258,"line":603},[17,2270,909],{},[17,2272,2273],{"class":258,"line":609},[17,2274,2275],{},"  \u002F\u002F 3. Create invoice + payment link\n",[17,2277,2278],{"class":258,"line":615},[17,2279,2280],{},"  const invoice = await fetch(\n",[17,2282,2283],{"class":258,"line":621},[17,2284,2285],{},"    `${process.env.INVOICE_SERVICE_URL}\u002Fapi\u002Finvoices`,\n",[17,2287,2288],{"class":258,"line":627},[17,2289,1580],{},[17,2291,2292],{"class":258,"line":633},[17,2293,2294],{},"      method: 'POST',\n",[17,2296,2297],{"class":258,"line":639},[17,2298,1590],{},[17,2300,2301],{"class":258,"line":645},[17,2302,1595],{},[17,2304,2305],{"class":258,"line":651},[17,2306,2307],{},"        'X-Service-Key': process.env.INVOICE_SERVICE_KEY\n",[17,2309,2310],{"class":258,"line":657},[17,2311,1605],{},[17,2313,2314],{"class":258,"line":663},[17,2315,2316],{},"      body: JSON.stringify({\n",[17,2318,2319],{"class":258,"line":668},[17,2320,2321],{},"        order_id: order.id,\n",[17,2323,2324],{"class":258,"line":674},[17,2325,2326],{},"        amount: total\n",[17,2328,2329],{"class":258,"line":680},[17,2330,2331],{},"      })\n",[17,2333,2334],{"class":258,"line":1369},[17,2335,1615],{},[17,2337,2338],{"class":258,"line":1374},[17,2339,2340],{},"  ).then(r => r.json());\n",[17,2342,2343],{"class":258,"line":1380},[17,2344,909],{},[17,2346,2347],{"class":258,"line":1386},[17,2348,2349],{},"  return {\n",[17,2351,2352],{"class":258,"line":1391},[17,2353,2354],{},"    order_number: order.order_number,\n",[17,2356,2357],{"class":258,"line":1396},[17,2358,2359],{},"    invoice_number: invoice.invoice_number,\n",[17,2361,2362],{"class":258,"line":1402},[17,2363,2364],{},"    total: total,\n",[17,2366,2367],{"class":258,"line":1408},[17,2368,2369],{},"    payment_url: invoice.payment_url,\n",[17,2371,2372],{"class":258,"line":1413},[17,2373,2374],{},"    due_date: invoice.due_date\n",[17,2376,2377],{"class":258,"line":1418},[17,2378,2379],{},"  };\n",[17,2381,2382],{"class":258,"line":1424},[17,2383,1147],{},[155,2385,2387],{"id":2386},"contoh-response-openclaw-ke-customer","Contoh Response OpenClaw ke Customer",[130,2389,2392],{"className":2390,"code":2391,"language":135},[133],"📋 Invoice #INV-2026-0404-001\n\n🛍️ Pesanan:\n• Baju Polos Size L - Navy × 3 pcs = Rp 255.000\n• Baju Polos Size L - Baby Blue × 2 pcs = Rp 170.000\n\n💰 Subtotal: Rp 425.000\n📦 Ongkir (JNE REG, Balikpapan): Rp 18.000\n━━━━━━━━━━━━━━\n💵 Total: Rp 443.000\n\n⏰ Batas bayar: Hari ini 20:00 WITA\n\n💳 Bayar di sini: https:\u002F\u002Fpay.example.com\u002Finv\u002F001\n\nKonfirmasi pembayaran otomatis ya kak! 🙏\n",[137,2393,2391],{"__ignoreMap":139},[14,2395,2396],{},[37,2397],{"alt":2398,"src":2399},"Invoice dan checkout automation flow","\u002Fimages\u002Fposts\u002Fcs-invoice-checkout.jpg",[112,2401],{},[115,2403,2405],{"id":2404},"rag-product-knowledge-base","📚 RAG: Product Knowledge Base",[14,2407,2408,2409,2413,2414,146],{},"CS bot perlu tau semua info produk — ukuran, warna, stok, harga, bahan, cara pakai, dll. Ini bukan data yang harus di-",[2410,2411,2412],"em",{},"memorize"," AI. Ini harus dari ",[22,2415,2416],{},"database",[155,2418,2420],{"id":2419},"kenapa-bukan-letakkan-di-promptcontext","Kenapa Bukan \"Letakkan di Prompt\u002FContext\"?",[130,2422,2425],{"className":2423,"code":2424,"language":135},[133],"❌ SALAH: Masukkan semua info produk ke system prompt AI\n\nMasalah:\n1. Context window terbatas → nggak bisa muat semua produk\n2. Kalau produk berubah → harus update prompt (manual!)\n3. AI bisa \"halusinasi\" info produk yang nggak ada\n4. Data bocor ke conversation lain (cross-contamination)\n\n✅ BENAR: RAG — Retrieve dari database, baru inject ke context\n\nKeuntungan:\n1. Database bisa update kapan saja → AI otomatis pakai data terbaru\n2. Hanya retrieve yang relevan → hemat context\n3. AI referensi data real → nggak halusinasi\n4. Data terpisah per session → nggak bocor\n",[137,2426,2424],{"__ignoreMap":139},[155,2428,2430],{"id":2429},"rag-architecture","RAG Architecture",[130,2432,2434],{"className":251,"code":2433,"language":253,"meta":139,"style":139},"flowchart TD\n    subgraph Input[\"Customer Message\"]\n        MSG[\"Ukuran L ready ga kak?\"]\n    end\n\n    subgraph RAG_Pipeline[\"RAG Pipeline\"]\n        EMBED[\"Embedding Model\\n(text → vector)\"]\n        VDB[\"Vector Database\\n(Qdrant\u002FPgVector)\"]\n        SIMILARITY[\"Cosine Similarity\\nSearch\"]\n        RETRIEVE[\"Top-K Results\"]\n    end\n\n    subgraph Context[\"Context Assembly\"]\n        INJECT[\"Inject ke Prompt:\\n+ Product data\\n+ Stock status\\n+ Price info\"]\n    end\n\n    subgraph LLM[\"OpenClaw Agent\"]\n        GENERATE[\"Generate Response\\nberdasarkan context\"]\n    end\n\n    MSG --> EMBED\n    EMBED --> SIMILARITY\n    SIMILARITY --> VDB\n    VDB --> RETRIEVE\n    RETRIEVE --> INJECT\n    INJECT --> GENERATE\n    GENERATE --> RESP[\"Respons ke Customer\"]\n\n    style RAG_Pipeline fill:#e8daef\n    style Context fill:#fff3cd\n",[137,2435,2436,2440,2445,2450,2454,2458,2463,2468,2473,2478,2483,2487,2491,2496,2501,2505,2509,2514,2519,2523,2527,2532,2537,2542,2547,2552,2557,2562,2566,2571],{"__ignoreMap":139},[17,2437,2438],{"class":258,"line":259},[17,2439,262],{},[17,2441,2442],{"class":258,"line":265},[17,2443,2444],{},"    subgraph Input[\"Customer Message\"]\n",[17,2446,2447],{"class":258,"line":271},[17,2448,2449],{},"        MSG[\"Ukuran L ready ga kak?\"]\n",[17,2451,2452],{"class":258,"line":277},[17,2453,328],{},[17,2455,2456],{"class":258,"line":283},[17,2457,335],{"emptyLinePlaceholder":334},[17,2459,2460],{"class":258,"line":289},[17,2461,2462],{},"    subgraph RAG_Pipeline[\"RAG Pipeline\"]\n",[17,2464,2465],{"class":258,"line":295},[17,2466,2467],{},"        EMBED[\"Embedding Model\\n(text → vector)\"]\n",[17,2469,2470],{"class":258,"line":301},[17,2471,2472],{},"        VDB[\"Vector Database\\n(Qdrant\u002FPgVector)\"]\n",[17,2474,2475],{"class":258,"line":307},[17,2476,2477],{},"        SIMILARITY[\"Cosine Similarity\\nSearch\"]\n",[17,2479,2480],{"class":258,"line":313},[17,2481,2482],{},"        RETRIEVE[\"Top-K Results\"]\n",[17,2484,2485],{"class":258,"line":319},[17,2486,328],{},[17,2488,2489],{"class":258,"line":325},[17,2490,335],{"emptyLinePlaceholder":334},[17,2492,2493],{"class":258,"line":331},[17,2494,2495],{},"    subgraph Context[\"Context Assembly\"]\n",[17,2497,2498],{"class":258,"line":338},[17,2499,2500],{},"        INJECT[\"Inject ke Prompt:\\n+ Product data\\n+ Stock status\\n+ Price info\"]\n",[17,2502,2503],{"class":258,"line":344},[17,2504,328],{},[17,2506,2507],{"class":258,"line":350},[17,2508,335],{"emptyLinePlaceholder":334},[17,2510,2511],{"class":258,"line":356},[17,2512,2513],{},"    subgraph LLM[\"OpenClaw Agent\"]\n",[17,2515,2516],{"class":258,"line":362},[17,2517,2518],{},"        GENERATE[\"Generate Response\\nberdasarkan context\"]\n",[17,2520,2521],{"class":258,"line":368},[17,2522,328],{},[17,2524,2525],{"class":258,"line":373},[17,2526,335],{"emptyLinePlaceholder":334},[17,2528,2529],{"class":258,"line":378},[17,2530,2531],{},"    MSG --> EMBED\n",[17,2533,2534],{"class":258,"line":384},[17,2535,2536],{},"    EMBED --> SIMILARITY\n",[17,2538,2539],{"class":258,"line":389},[17,2540,2541],{},"    SIMILARITY --> VDB\n",[17,2543,2544],{"class":258,"line":395},[17,2545,2546],{},"    VDB --> RETRIEVE\n",[17,2548,2549],{"class":258,"line":603},[17,2550,2551],{},"    RETRIEVE --> INJECT\n",[17,2553,2554],{"class":258,"line":609},[17,2555,2556],{},"    INJECT --> GENERATE\n",[17,2558,2559],{"class":258,"line":615},[17,2560,2561],{},"    GENERATE --> RESP[\"Respons ke Customer\"]\n",[17,2563,2564],{"class":258,"line":621},[17,2565,335],{"emptyLinePlaceholder":334},[17,2567,2568],{"class":258,"line":627},[17,2569,2570],{},"    style RAG_Pipeline fill:#e8daef\n",[17,2572,2573],{"class":258,"line":633},[17,2574,2575],{},"    style Context fill:#fff3cd\n",[155,2577,2579],{"id":2578},"setup-vector-database","Setup Vector Database",[130,2581,2583],{"className":876,"code":2582,"language":878,"meta":139,"style":139},"\u002F\u002F skills\u002Fproduct-knowledge\u002Fsearch.js\n\u002F\u002F RAG implementation menggunakan PgVector (PostgreSQL extension)\n\nasync function searchProducts(query, topK = 5) {\n  \u002F\u002F 1. Generate embedding dari query\n  const embedding = await generateEmbedding(query);\n  \n  \u002F\u002F 2. Search di vector database\n  const response = await fetch(\n    `${process.env.KNOWLEDGE_SERVICE_URL}\u002Fapi\u002Fproducts\u002Fsearch`,\n    {\n      method: 'POST',\n      headers: {\n        'Content-Type': 'application\u002Fjson',\n        'X-Service-Key': process.env.KNOWLEDGE_SERVICE_KEY\n      },\n      body: JSON.stringify({\n        embedding: embedding,\n        top_k: topK,\n        filters: {\n          in_stock: true  \u002F\u002F Hanya produk yang ready\n        }\n      })\n    }\n  );\n  \n  const results = await response.json();\n  return results;\n}\n\n\u002F\u002F Embedding bisa pakai:\n\u002F\u002F - OpenAI text-embedding-3-small (murah, akurat)\n\u002F\u002F - Google embedding-gecko-001 (gratis tier)\n\u002F\u002F - Local: sentence-transformers (self-hosted, no API cost)\n",[137,2584,2585,2590,2595,2599,2604,2609,2614,2618,2623,2627,2632,2636,2640,2644,2648,2653,2657,2661,2666,2671,2676,2681,2686,2690,2694,2698,2702,2707,2712,2716,2720,2725,2730,2735],{"__ignoreMap":139},[17,2586,2587],{"class":258,"line":259},[17,2588,2589],{},"\u002F\u002F skills\u002Fproduct-knowledge\u002Fsearch.js\n",[17,2591,2592],{"class":258,"line":265},[17,2593,2594],{},"\u002F\u002F RAG implementation menggunakan PgVector (PostgreSQL extension)\n",[17,2596,2597],{"class":258,"line":271},[17,2598,335],{"emptyLinePlaceholder":334},[17,2600,2601],{"class":258,"line":277},[17,2602,2603],{},"async function searchProducts(query, topK = 5) {\n",[17,2605,2606],{"class":258,"line":283},[17,2607,2608],{},"  \u002F\u002F 1. Generate embedding dari query\n",[17,2610,2611],{"class":258,"line":289},[17,2612,2613],{},"  const embedding = await generateEmbedding(query);\n",[17,2615,2616],{"class":258,"line":295},[17,2617,909],{},[17,2619,2620],{"class":258,"line":301},[17,2621,2622],{},"  \u002F\u002F 2. Search di vector database\n",[17,2624,2625],{"class":258,"line":307},[17,2626,1570],{},[17,2628,2629],{"class":258,"line":313},[17,2630,2631],{},"    `${process.env.KNOWLEDGE_SERVICE_URL}\u002Fapi\u002Fproducts\u002Fsearch`,\n",[17,2633,2634],{"class":258,"line":319},[17,2635,1580],{},[17,2637,2638],{"class":258,"line":325},[17,2639,2294],{},[17,2641,2642],{"class":258,"line":331},[17,2643,1590],{},[17,2645,2646],{"class":258,"line":338},[17,2647,1595],{},[17,2649,2650],{"class":258,"line":344},[17,2651,2652],{},"        'X-Service-Key': process.env.KNOWLEDGE_SERVICE_KEY\n",[17,2654,2655],{"class":258,"line":350},[17,2656,1605],{},[17,2658,2659],{"class":258,"line":356},[17,2660,2316],{},[17,2662,2663],{"class":258,"line":362},[17,2664,2665],{},"        embedding: embedding,\n",[17,2667,2668],{"class":258,"line":368},[17,2669,2670],{},"        top_k: topK,\n",[17,2672,2673],{"class":258,"line":373},[17,2674,2675],{},"        filters: {\n",[17,2677,2678],{"class":258,"line":378},[17,2679,2680],{},"          in_stock: true  \u002F\u002F Hanya produk yang ready\n",[17,2682,2683],{"class":258,"line":384},[17,2684,2685],{},"        }\n",[17,2687,2688],{"class":258,"line":389},[17,2689,2331],{},[17,2691,2692],{"class":258,"line":395},[17,2693,1615],{},[17,2695,2696],{"class":258,"line":603},[17,2697,1620],{},[17,2699,2700],{"class":258,"line":609},[17,2701,909],{},[17,2703,2704],{"class":258,"line":615},[17,2705,2706],{},"  const results = await response.json();\n",[17,2708,2709],{"class":258,"line":621},[17,2710,2711],{},"  return results;\n",[17,2713,2714],{"class":258,"line":627},[17,2715,1147],{},[17,2717,2718],{"class":258,"line":633},[17,2719,335],{"emptyLinePlaceholder":334},[17,2721,2722],{"class":258,"line":639},[17,2723,2724],{},"\u002F\u002F Embedding bisa pakai:\n",[17,2726,2727],{"class":258,"line":645},[17,2728,2729],{},"\u002F\u002F - OpenAI text-embedding-3-small (murah, akurat)\n",[17,2731,2732],{"class":258,"line":651},[17,2733,2734],{},"\u002F\u002F - Google embedding-gecko-001 (gratis tier)\n",[17,2736,2737],{"class":258,"line":657},[17,2738,2739],{},"\u002F\u002F - Local: sentence-transformers (self-hosted, no API cost)\n",[155,2741,2743],{"id":2742},"product-data-di-database-bukan-di-ai-memory","Product Data di Database (BUKAN di AI Memory)",[130,2745,2747],{"className":1181,"code":2746,"language":1183,"meta":139,"style":139},"-- knowledge-service\u002Fschema.sql\n\nCREATE TABLE products (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  sku VARCHAR(20) UNIQUE NOT NULL,\n  name VARCHAR(200) NOT NULL,\n  category VARCHAR(50),\n  description TEXT,\n  \n  -- Variants\n  variants JSONB,\n  -- [{\"size\":\"S\",\"price\":75000,\"stock\":15},{\"size\":\"M\",\"price\":80000,\"stock\":23}]\n  \n  -- Media\n  image_url VARCHAR(500),\n  \n  -- Metadata\n  tags TEXT[],\n  material VARCHAR(100),\n  weight_gram INTEGER,\n  \n  -- Vector embedding (for RAG)\n  embedding vector(1536),\n  \n  in_stock BOOLEAN DEFAULT TRUE,\n  created_at TIMESTAMP DEFAULT NOW(),\n  updated_at TIMESTAMP DEFAULT NOW()\n);\n\n-- FAQ yang juga bisa di-RAG\nCREATE TABLE faqs (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\n  question TEXT NOT NULL,\n  answer TEXT NOT NULL,\n  category VARCHAR(50),\n  embedding vector(1536),\n  is_active BOOLEAN DEFAULT TRUE,\n  created_at TIMESTAMP DEFAULT NOW()\n);\n",[137,2748,2749,2754,2758,2763,2767,2772,2777,2782,2787,2791,2796,2801,2806,2810,2815,2820,2824,2828,2832,2837,2842,2846,2851,2856,2860,2865,2869,2873,2877,2881,2886,2891,2895,2900,2905,2909,2913,2918,2922],{"__ignoreMap":139},[17,2750,2751],{"class":258,"line":259},[17,2752,2753],{},"-- knowledge-service\u002Fschema.sql\n",[17,2755,2756],{"class":258,"line":265},[17,2757,335],{"emptyLinePlaceholder":334},[17,2759,2760],{"class":258,"line":271},[17,2761,2762],{},"CREATE TABLE products (\n",[17,2764,2765],{"class":258,"line":277},[17,2766,1204],{},[17,2768,2769],{"class":258,"line":283},[17,2770,2771],{},"  sku VARCHAR(20) UNIQUE NOT NULL,\n",[17,2773,2774],{"class":258,"line":289},[17,2775,2776],{},"  name VARCHAR(200) NOT NULL,\n",[17,2778,2779],{"class":258,"line":295},[17,2780,2781],{},"  category VARCHAR(50),\n",[17,2783,2784],{"class":258,"line":301},[17,2785,2786],{},"  description TEXT,\n",[17,2788,2789],{"class":258,"line":307},[17,2790,909],{},[17,2792,2793],{"class":258,"line":313},[17,2794,2795],{},"  -- Variants\n",[17,2797,2798],{"class":258,"line":319},[17,2799,2800],{},"  variants JSONB,\n",[17,2802,2803],{"class":258,"line":325},[17,2804,2805],{},"  -- [{\"size\":\"S\",\"price\":75000,\"stock\":15},{\"size\":\"M\",\"price\":80000,\"stock\":23}]\n",[17,2807,2808],{"class":258,"line":331},[17,2809,909],{},[17,2811,2812],{"class":258,"line":338},[17,2813,2814],{},"  -- Media\n",[17,2816,2817],{"class":258,"line":344},[17,2818,2819],{},"  image_url VARCHAR(500),\n",[17,2821,2822],{"class":258,"line":350},[17,2823,909],{},[17,2825,2826],{"class":258,"line":356},[17,2827,1258],{},[17,2829,2830],{"class":258,"line":362},[17,2831,1292],{},[17,2833,2834],{"class":258,"line":368},[17,2835,2836],{},"  material VARCHAR(100),\n",[17,2838,2839],{"class":258,"line":373},[17,2840,2841],{},"  weight_gram INTEGER,\n",[17,2843,2844],{"class":258,"line":378},[17,2845,909],{},[17,2847,2848],{"class":258,"line":384},[17,2849,2850],{},"  -- Vector embedding (for RAG)\n",[17,2852,2853],{"class":258,"line":389},[17,2854,2855],{},"  embedding vector(1536),\n",[17,2857,2858],{"class":258,"line":395},[17,2859,909],{},[17,2861,2862],{"class":258,"line":603},[17,2863,2864],{},"  in_stock BOOLEAN DEFAULT TRUE,\n",[17,2866,2867],{"class":258,"line":609},[17,2868,1311],{},[17,2870,2871],{"class":258,"line":615},[17,2872,1316],{},[17,2874,2875],{"class":258,"line":621},[17,2876,1321],{},[17,2878,2879],{"class":258,"line":627},[17,2880,335],{"emptyLinePlaceholder":334},[17,2882,2883],{"class":258,"line":633},[17,2884,2885],{},"-- FAQ yang juga bisa di-RAG\n",[17,2887,2888],{"class":258,"line":639},[17,2889,2890],{},"CREATE TABLE faqs (\n",[17,2892,2893],{"class":258,"line":645},[17,2894,1204],{},[17,2896,2897],{"class":258,"line":651},[17,2898,2899],{},"  question TEXT NOT NULL,\n",[17,2901,2902],{"class":258,"line":657},[17,2903,2904],{},"  answer TEXT NOT NULL,\n",[17,2906,2907],{"class":258,"line":663},[17,2908,2781],{},[17,2910,2911],{"class":258,"line":668},[17,2912,2855],{},[17,2914,2915],{"class":258,"line":674},[17,2916,2917],{},"  is_active BOOLEAN DEFAULT TRUE,\n",[17,2919,2920],{"class":258,"line":680},[17,2921,1383],{},[17,2923,2924],{"class":258,"line":1369},[17,2925,1321],{},[14,2927,2928],{},[37,2929],{"alt":2930,"src":2931},"RAG dan knowledge base architecture","\u002Fimages\u002Fposts\u002Fcs-rag-knowledge.jpg",[112,2933],{},[115,2935,2937],{"id":2936},"security-strict-database-access","🔒 Security: Strict Database Access",[14,2939,2940,2941,2944],{},"Ini bagian yang ",[22,2942,2943],{},"paling penting"," dan sering diabaikan. OpenClaw sebagai CS bot punya akses ke data sensitif customer — nama, alamat, nomor WA, riwayat belanja. Kalau ini bocor, masuk HUKUM.",[155,2946,2948],{"id":2947},"aturan-emas-data-hanya-dari-database","Aturan Emas: Data Hanya Dari Database",[130,2950,2953],{"className":2951,"code":2952,"language":135},[133],"🔒 SECURITY RULES (NON-NEGOTIABLE):\n\n1. Customer data → HANYA dari Contact Service DB\n   ❌ NEVER: \"From my memory, Kak Rina tinggal di...\"\n   ✅ ALWAYS: Query Contact DB → response → tulis jawaban\n\n2. Product info → HANYA dari Product Knowledge DB\n   ❌ NEVER: \"Sepertinya harganya Rp 85.000\"\n   ✅ ALWAYS: Query Product DB → \"Harga Rp 85.000\" (confirmed)\n\n3. Order history → HANYA dari Invoice Service DB\n   ❌ NEVER: \"Kayaknya kemarin pernah pesan...\"\n   ✅ ALWAYS: Query Order DB → \"Pesanan terakhir: #ORD-xxx\"\n\n4. AI Memory (MEMORY.md, workspace files) → ABSOLUTELY NO customer data\n   ❌ NEVER: Write customer names\u002Fphones\u002Faddresses to memory files\n   ✅ ALWAYS: Memory files hanya berisi system config & rules\n\n5. Cross-session → Customer A data NEVER visible to Customer B\n   ❌ NEVER: \"Oh Kak Budi juga pesan yang sama!\"\n   ✅ ALWAYS: Session isolation — setiap customer punya context terpisah\n",[137,2954,2952],{"__ignoreMap":139},[155,2956,2958],{"id":2957},"implementation-openclaw-skill-dengan-guard-rails","Implementation: OpenClaw Skill dengan Guard Rails",[130,2960,2962],{"className":876,"code":2961,"language":878,"meta":139,"style":139},"\u002F\u002F skills\u002Fcs-security\u002Fdata-guard.js\n\u002F\u002F Wrapper untuk semua database queries di OpenClaw CS\n\nconst ALLOWED_QUERIES = {\n  contact: ['lookup', 'update_address', 'get_order_history'],\n  product: ['search', 'get_stock', 'get_price'],\n  invoice: ['create', 'get_status', 'list_by_contact']\n};\n\nconst FORBIDDEN_PATTERNS = [\n  \u002FSELECT.*FROM\\s+contacts\\s+WHERE\u002Fi,\n  \u002FINSERT.*INTO\\s+(?!audit_log)\u002Fi,  \u002F\u002F Only audit_log inserts allowed\n  \u002FDELETE.*FROM\u002Fi,\n  \u002FDROP\\s+TABLE\u002Fi,\n  \u002Fcustomer.*phone\u002Fi,\n  \u002Fcustomer.*email\u002Fi,\n  \u002Fprivate.*key\u002Fi\n];\n\nfunction validateQuery(service, action, params) {\n  \u002F\u002F 1. Check service + action combo allowed\n  if (!ALLOWED_QUERIES[service]?.includes(action)) {\n    throw new SecurityError(\n      `Blocked: ${service}.${action} not in allowed list`\n    );\n  }\n  \n  \u002F\u002F 2. Check params for sensitive data leakage\n  const paramStr = JSON.stringify(params).toLowerCase();\n  for (const pattern of FORBIDDEN_PATTERNS) {\n    if (pattern.test(paramStr)) {\n      throw new SecurityError(\n        `Blocked: Query contains forbidden pattern`\n      );\n    }\n  }\n  \n  \u002F\u002F 3. Log access\n  auditLog.info({\n    service,\n    action,\n    params_hash: hashParams(params), \u002F\u002F Hash, don't log raw\n    timestamp: new Date().toISOString()\n  });\n  \n  return true;\n}\n\n\u002F\u002F Gunakan wrapper ini di semua skill\nasync function safeQuery(service, action, params) {\n  validateQuery(service, action, params);\n  return callService(service, action, params);\n}\n",[137,2963,2964,2969,2974,2978,2983,2988,2993,2998,3003,3007,3012,3017,3022,3027,3032,3037,3042,3047,3052,3056,3061,3066,3071,3076,3081,3086,3090,3094,3099,3104,3109,3114,3119,3124,3129,3133,3137,3141,3146,3151,3156,3161,3166,3171,3175,3179,3184,3188,3192,3197,3202,3207,3212],{"__ignoreMap":139},[17,2965,2966],{"class":258,"line":259},[17,2967,2968],{},"\u002F\u002F skills\u002Fcs-security\u002Fdata-guard.js\n",[17,2970,2971],{"class":258,"line":265},[17,2972,2973],{},"\u002F\u002F Wrapper untuk semua database queries di OpenClaw CS\n",[17,2975,2976],{"class":258,"line":271},[17,2977,335],{"emptyLinePlaceholder":334},[17,2979,2980],{"class":258,"line":277},[17,2981,2982],{},"const ALLOWED_QUERIES = {\n",[17,2984,2985],{"class":258,"line":283},[17,2986,2987],{},"  contact: ['lookup', 'update_address', 'get_order_history'],\n",[17,2989,2990],{"class":258,"line":289},[17,2991,2992],{},"  product: ['search', 'get_stock', 'get_price'],\n",[17,2994,2995],{"class":258,"line":295},[17,2996,2997],{},"  invoice: ['create', 'get_status', 'list_by_contact']\n",[17,2999,3000],{"class":258,"line":301},[17,3001,3002],{},"};\n",[17,3004,3005],{"class":258,"line":307},[17,3006,335],{"emptyLinePlaceholder":334},[17,3008,3009],{"class":258,"line":313},[17,3010,3011],{},"const FORBIDDEN_PATTERNS = [\n",[17,3013,3014],{"class":258,"line":319},[17,3015,3016],{},"  \u002FSELECT.*FROM\\s+contacts\\s+WHERE\u002Fi,\n",[17,3018,3019],{"class":258,"line":325},[17,3020,3021],{},"  \u002FINSERT.*INTO\\s+(?!audit_log)\u002Fi,  \u002F\u002F Only audit_log inserts allowed\n",[17,3023,3024],{"class":258,"line":331},[17,3025,3026],{},"  \u002FDELETE.*FROM\u002Fi,\n",[17,3028,3029],{"class":258,"line":338},[17,3030,3031],{},"  \u002FDROP\\s+TABLE\u002Fi,\n",[17,3033,3034],{"class":258,"line":344},[17,3035,3036],{},"  \u002Fcustomer.*phone\u002Fi,\n",[17,3038,3039],{"class":258,"line":350},[17,3040,3041],{},"  \u002Fcustomer.*email\u002Fi,\n",[17,3043,3044],{"class":258,"line":356},[17,3045,3046],{},"  \u002Fprivate.*key\u002Fi\n",[17,3048,3049],{"class":258,"line":362},[17,3050,3051],{},"];\n",[17,3053,3054],{"class":258,"line":368},[17,3055,335],{"emptyLinePlaceholder":334},[17,3057,3058],{"class":258,"line":373},[17,3059,3060],{},"function validateQuery(service, action, params) {\n",[17,3062,3063],{"class":258,"line":378},[17,3064,3065],{},"  \u002F\u002F 1. Check service + action combo allowed\n",[17,3067,3068],{"class":258,"line":384},[17,3069,3070],{},"  if (!ALLOWED_QUERIES[service]?.includes(action)) {\n",[17,3072,3073],{"class":258,"line":389},[17,3074,3075],{},"    throw new SecurityError(\n",[17,3077,3078],{"class":258,"line":395},[17,3079,3080],{},"      `Blocked: ${service}.${action} not in allowed list`\n",[17,3082,3083],{"class":258,"line":603},[17,3084,3085],{},"    );\n",[17,3087,3088],{"class":258,"line":609},[17,3089,1133],{},[17,3091,3092],{"class":258,"line":615},[17,3093,909],{},[17,3095,3096],{"class":258,"line":621},[17,3097,3098],{},"  \u002F\u002F 2. Check params for sensitive data leakage\n",[17,3100,3101],{"class":258,"line":627},[17,3102,3103],{},"  const paramStr = JSON.stringify(params).toLowerCase();\n",[17,3105,3106],{"class":258,"line":633},[17,3107,3108],{},"  for (const pattern of FORBIDDEN_PATTERNS) {\n",[17,3110,3111],{"class":258,"line":639},[17,3112,3113],{},"    if (pattern.test(paramStr)) {\n",[17,3115,3116],{"class":258,"line":645},[17,3117,3118],{},"      throw new SecurityError(\n",[17,3120,3121],{"class":258,"line":651},[17,3122,3123],{},"        `Blocked: Query contains forbidden pattern`\n",[17,3125,3126],{"class":258,"line":657},[17,3127,3128],{},"      );\n",[17,3130,3131],{"class":258,"line":663},[17,3132,1615],{},[17,3134,3135],{"class":258,"line":668},[17,3136,1133],{},[17,3138,3139],{"class":258,"line":674},[17,3140,909],{},[17,3142,3143],{"class":258,"line":680},[17,3144,3145],{},"  \u002F\u002F 3. Log access\n",[17,3147,3148],{"class":258,"line":1369},[17,3149,3150],{},"  auditLog.info({\n",[17,3152,3153],{"class":258,"line":1374},[17,3154,3155],{},"    service,\n",[17,3157,3158],{"class":258,"line":1380},[17,3159,3160],{},"    action,\n",[17,3162,3163],{"class":258,"line":1386},[17,3164,3165],{},"    params_hash: hashParams(params), \u002F\u002F Hash, don't log raw\n",[17,3167,3168],{"class":258,"line":1391},[17,3169,3170],{},"    timestamp: new Date().toISOString()\n",[17,3172,3173],{"class":258,"line":1396},[17,3174,968],{},[17,3176,3177],{"class":258,"line":1402},[17,3178,909],{},[17,3180,3181],{"class":258,"line":1408},[17,3182,3183],{},"  return true;\n",[17,3185,3186],{"class":258,"line":1413},[17,3187,1147],{},[17,3189,3190],{"class":258,"line":1418},[17,3191,335],{"emptyLinePlaceholder":334},[17,3193,3194],{"class":258,"line":1424},[17,3195,3196],{},"\u002F\u002F Gunakan wrapper ini di semua skill\n",[17,3198,3199],{"class":258,"line":1430},[17,3200,3201],{},"async function safeQuery(service, action, params) {\n",[17,3203,3204],{"class":258,"line":1436},[17,3205,3206],{},"  validateQuery(service, action, params);\n",[17,3208,3209],{"class":258,"line":1441},[17,3210,3211],{},"  return callService(service, action, params);\n",[17,3213,3215],{"class":258,"line":3214},53,[17,3216,1147],{},[155,3218,3220],{"id":3219},"data-flow-security","Data Flow Security",[130,3222,3224],{"className":251,"code":3223,"language":253,"meta":139,"style":139},"flowchart TD\n    subgraph Unsafe[\"❌ UNSAFE PATTERN\"]\n        U1[\"Customer data disimpan\\ndi AI memory files\"]\n        U2[\"AI bisa 'recall' data\\ncross-session\"]\n        U3[\"Data bocor ke\\nconversation lain\"]\n        U4[\"Compliance violation\\n(privacy law)\"]\n        U1 --> U2 --> U3 --> U4\n    end\n\n    subgraph Safe[\"✅ SAFE PATTERN\"]\n        S1[\"Customer data HANYA\\ndi database\"]\n        S2[\"Query real-time dari DB\\nper session\"]\n        S3[\"Session isolation\\ndata terpisah\"]\n        S4[\"Audit trail\\nsemua akses logged\"]\n        S1 --> S2 --> S3 --> S4\n    end\n\n    style Unsafe fill:#ff6b6b,color:#fff\n    style Safe fill:#51cf66,color:#fff\n",[137,3225,3226,3230,3235,3240,3245,3250,3255,3260,3264,3268,3273,3278,3283,3288,3293,3298,3302,3306,3311],{"__ignoreMap":139},[17,3227,3228],{"class":258,"line":259},[17,3229,262],{},[17,3231,3232],{"class":258,"line":265},[17,3233,3234],{},"    subgraph Unsafe[\"❌ UNSAFE PATTERN\"]\n",[17,3236,3237],{"class":258,"line":271},[17,3238,3239],{},"        U1[\"Customer data disimpan\\ndi AI memory files\"]\n",[17,3241,3242],{"class":258,"line":277},[17,3243,3244],{},"        U2[\"AI bisa 'recall' data\\ncross-session\"]\n",[17,3246,3247],{"class":258,"line":283},[17,3248,3249],{},"        U3[\"Data bocor ke\\nconversation lain\"]\n",[17,3251,3252],{"class":258,"line":289},[17,3253,3254],{},"        U4[\"Compliance violation\\n(privacy law)\"]\n",[17,3256,3257],{"class":258,"line":295},[17,3258,3259],{},"        U1 --> U2 --> U3 --> U4\n",[17,3261,3262],{"class":258,"line":301},[17,3263,328],{},[17,3265,3266],{"class":258,"line":307},[17,3267,335],{"emptyLinePlaceholder":334},[17,3269,3270],{"class":258,"line":313},[17,3271,3272],{},"    subgraph Safe[\"✅ SAFE PATTERN\"]\n",[17,3274,3275],{"class":258,"line":319},[17,3276,3277],{},"        S1[\"Customer data HANYA\\ndi database\"]\n",[17,3279,3280],{"class":258,"line":325},[17,3281,3282],{},"        S2[\"Query real-time dari DB\\nper session\"]\n",[17,3284,3285],{"class":258,"line":331},[17,3286,3287],{},"        S3[\"Session isolation\\ndata terpisah\"]\n",[17,3289,3290],{"class":258,"line":338},[17,3291,3292],{},"        S4[\"Audit trail\\nsemua akses logged\"]\n",[17,3294,3295],{"class":258,"line":344},[17,3296,3297],{},"        S1 --> S2 --> S3 --> S4\n",[17,3299,3300],{"class":258,"line":350},[17,3301,328],{},[17,3303,3304],{"class":258,"line":356},[17,3305,335],{"emptyLinePlaceholder":334},[17,3307,3308],{"class":258,"line":362},[17,3309,3310],{},"    style Unsafe fill:#ff6b6b,color:#fff\n",[17,3312,3313],{"class":258,"line":368},[17,3314,3315],{},"    style Safe fill:#51cf66,color:#fff\n",[14,3317,3318],{},[37,3319],{"alt":3320,"src":3321},"Security shield untuk data customer CS bot","\u002Fimages\u002Fposts\u002Fcs-security-shield.jpg",[155,3323,3325],{"id":3324},"audit-log","Audit Log",[14,3327,3328],{},"Setiap akses data customer HARUS di-log. Ini bukan optional — ini kebutuhan compliance.",[130,3330,3332],{"className":1181,"code":3331,"language":1183,"meta":139,"style":139},"CREATE TABLE access_audit (\n  id BIGSERIAL PRIMARY KEY,\n  timestamp TIMESTAMP DEFAULT NOW(),\n  service VARCHAR(50) NOT NULL,    -- 'contact', 'product', 'invoice'\n  action VARCHAR(50) NOT NULL,     -- 'lookup', 'update', 'create'\n  actor VARCHAR(50) NOT NULL,      -- 'openclaw-cs-agent', 'admin'\n  target_id VARCHAR(100),          -- Contact ID \u002F Order ID (hashed)\n  session_id VARCHAR(100),         -- WA session ID\n  ip_address INET,\n  user_agent TEXT,\n  result VARCHAR(20) DEFAULT 'success', -- 'success', 'blocked', 'error'\n  reason TEXT                      -- Jika blocked, alasan apa\n);\n",[137,3333,3334,3339,3344,3349,3354,3359,3364,3369,3374,3379,3384,3389,3394],{"__ignoreMap":139},[17,3335,3336],{"class":258,"line":259},[17,3337,3338],{},"CREATE TABLE access_audit (\n",[17,3340,3341],{"class":258,"line":265},[17,3342,3343],{},"  id BIGSERIAL PRIMARY KEY,\n",[17,3345,3346],{"class":258,"line":271},[17,3347,3348],{},"  timestamp TIMESTAMP DEFAULT NOW(),\n",[17,3350,3351],{"class":258,"line":277},[17,3352,3353],{},"  service VARCHAR(50) NOT NULL,    -- 'contact', 'product', 'invoice'\n",[17,3355,3356],{"class":258,"line":283},[17,3357,3358],{},"  action VARCHAR(50) NOT NULL,     -- 'lookup', 'update', 'create'\n",[17,3360,3361],{"class":258,"line":289},[17,3362,3363],{},"  actor VARCHAR(50) NOT NULL,      -- 'openclaw-cs-agent', 'admin'\n",[17,3365,3366],{"class":258,"line":295},[17,3367,3368],{},"  target_id VARCHAR(100),          -- Contact ID \u002F Order ID (hashed)\n",[17,3370,3371],{"class":258,"line":301},[17,3372,3373],{},"  session_id VARCHAR(100),         -- WA session ID\n",[17,3375,3376],{"class":258,"line":307},[17,3377,3378],{},"  ip_address INET,\n",[17,3380,3381],{"class":258,"line":313},[17,3382,3383],{},"  user_agent TEXT,\n",[17,3385,3386],{"class":258,"line":319},[17,3387,3388],{},"  result VARCHAR(20) DEFAULT 'success', -- 'success', 'blocked', 'error'\n",[17,3390,3391],{"class":258,"line":325},[17,3392,3393],{},"  reason TEXT                      -- Jika blocked, alasan apa\n",[17,3395,3396],{"class":258,"line":331},[17,3397,1321],{},[112,3399],{},[115,3401,3403],{"id":3402},"openclaw-workspace-setup","🤖 OpenClaw Workspace Setup",[14,3405,3406],{},"Sekarang, gimana setup OpenClaw-nya? Ini struktur workspace yang disarankan:",[130,3408,3411],{"className":3409,"code":3410,"language":135},[133],"openclaw-cs-workspace\u002F\n├── SOUL.md                    # Persona CS bot (tone, style)\n├── AGENTS.md                  # Rules & security policies\n├── HEARTBEAT.md               # Periodic checks\n│\n├── skills\u002F\n│   ├── cs-gateway\u002F            # Kirim\u002Fterima pesan via gateway\n│   │   ├── SKILL.md\n│   │   └── scripts\u002F\n│   │       ├── send-message.js\n│   │       └── check-session.js\n│   │\n│   ├── contact-service\u002F       # Customer data (DB only!)\n│   │   ├── SKILL.md\n│   │   └── scripts\u002F\n│   │       ├── lookup.js\n│   │       ├── update.js\n│   │       └── get-history.js\n│   │\n│   ├── invoice-service\u002F       # Invoice & payment\n│   │   ├── SKILL.md\n│   │   └── scripts\u002F\n│   │       ├── create-order.js\n│   │       ├── check-status.js\n│   │       └── generate-invoice.js\n│   │\n│   ├── product-knowledge\u002F     # RAG product search\n│   │   ├── SKILL.md\n│   │   └── scripts\u002F\n│   │       ├── search.js\n│   │       ├── get-stock.js\n│   │       └── get-price.js\n│   │\n│   └── cs-security\u002F           # Guard rails & audit\n│       ├── SKILL.md\n│       └── scripts\u002F\n│           ├── data-guard.js\n│           └── audit.js\n│\n└── config\u002F\n    ├── .env                   # API keys (chmod 600!)\n    ├── services.yaml          # Service URLs & keys\n    └── security-rules.yaml    # Guard rail rules\n",[137,3412,3410],{"__ignoreMap":139},[155,3414,3416],{"id":3415},"skillmd-contoh-cs-gateway","SKILL.md Contoh: CS Gateway",[130,3418,3422],{"className":3419,"code":3420,"language":3421,"meta":139,"style":139},"language-markdown shiki shiki-themes github-light github-dark","# CS Gateway Skill\n\n## Trigger\n- Incoming message from WhatsApp (via gateway webhook)\n- OpenClaw heartbeat (check pending messages)\n\n## Rules\n- ALWAYS validate session_id before sending response\n- NEVER store customer PII in workspace files\n- ALWAYS query Contact DB for existing customer data\n- NEVER guess product info — always query Product DB\n- Rate limit: max 10 messages per minute per session\n- If unsure about customer intent, ask clarifying question\n- If order involves payment > Rp 1.000.000, flag for human review\n\n## Workflow\n1. Receive message from gateway queue\n2. Identify customer (WA number → Contact DB lookup)\n3. Parse intent (order, question, complaint, etc.)\n4. Retrieve relevant data (products, order history, etc.)\n5. Generate response\n6. Send via gateway\n7. Log interaction\n","markdown",[137,3423,3424,3429,3433,3438,3443,3448,3452,3457,3462,3467,3472,3477,3482,3487,3492,3496,3501,3506,3511,3516,3521,3526,3531],{"__ignoreMap":139},[17,3425,3426],{"class":258,"line":259},[17,3427,3428],{},"# CS Gateway Skill\n",[17,3430,3431],{"class":258,"line":265},[17,3432,335],{"emptyLinePlaceholder":334},[17,3434,3435],{"class":258,"line":271},[17,3436,3437],{},"## Trigger\n",[17,3439,3440],{"class":258,"line":277},[17,3441,3442],{},"- Incoming message from WhatsApp (via gateway webhook)\n",[17,3444,3445],{"class":258,"line":283},[17,3446,3447],{},"- OpenClaw heartbeat (check pending messages)\n",[17,3449,3450],{"class":258,"line":289},[17,3451,335],{"emptyLinePlaceholder":334},[17,3453,3454],{"class":258,"line":295},[17,3455,3456],{},"## Rules\n",[17,3458,3459],{"class":258,"line":301},[17,3460,3461],{},"- ALWAYS validate session_id before sending response\n",[17,3463,3464],{"class":258,"line":307},[17,3465,3466],{},"- NEVER store customer PII in workspace files\n",[17,3468,3469],{"class":258,"line":313},[17,3470,3471],{},"- ALWAYS query Contact DB for existing customer data\n",[17,3473,3474],{"class":258,"line":319},[17,3475,3476],{},"- NEVER guess product info — always query Product DB\n",[17,3478,3479],{"class":258,"line":325},[17,3480,3481],{},"- Rate limit: max 10 messages per minute per session\n",[17,3483,3484],{"class":258,"line":331},[17,3485,3486],{},"- If unsure about customer intent, ask clarifying question\n",[17,3488,3489],{"class":258,"line":338},[17,3490,3491],{},"- If order involves payment > Rp 1.000.000, flag for human review\n",[17,3493,3494],{"class":258,"line":344},[17,3495,335],{"emptyLinePlaceholder":334},[17,3497,3498],{"class":258,"line":350},[17,3499,3500],{},"## Workflow\n",[17,3502,3503],{"class":258,"line":356},[17,3504,3505],{},"1. Receive message from gateway queue\n",[17,3507,3508],{"class":258,"line":362},[17,3509,3510],{},"2. Identify customer (WA number → Contact DB lookup)\n",[17,3512,3513],{"class":258,"line":368},[17,3514,3515],{},"3. Parse intent (order, question, complaint, etc.)\n",[17,3517,3518],{"class":258,"line":373},[17,3519,3520],{},"4. Retrieve relevant data (products, order history, etc.)\n",[17,3522,3523],{"class":258,"line":378},[17,3524,3525],{},"5. Generate response\n",[17,3527,3528],{"class":258,"line":384},[17,3529,3530],{},"6. Send via gateway\n",[17,3532,3533],{"class":258,"line":389},[17,3534,3535],{},"7. Log interaction\n",[112,3537],{},[115,3539,3541],{"id":3540},"cost-breakdown","💰 Cost Breakdown",[130,3543,3546],{"className":3544,"code":3545,"language":135},[133],"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n💰 Estimasi Biaya CS Bot (per bulan)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n📦 Infrastructure\n  VPS 2 vCPU\u002F4GB:       Rp 150.000\n  PostgreSQL managed:    Rp 100.000\n  Redis:                 Rp 50.000\n  Domain + SSL:           Rp 15.000\n  ─────────────────────────────────\n  Subtotal:             Rp 315.000\n\n🧠 AI\u002FLLM\n  Kimi 2.5 (Tier 1):    ~Rp 200.000\n  Embedding API:        ~Rp 50.000\n  ─────────────────────────────────\n  Subtotal:             Rp 250.000\n\n📱 WhatsApp\n  Baileys (free):            Rp 0\n  Evolution API (managed):  Rp 300.000\n  ─────────────────────────────────\n  Subtotal:              Rp 0-300.000\n\n💳 Payment Gateway\n  Midtrans\u002FXendit:      1.5-3% per transaksi\n  ─────────────────────────────────\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTOTAL (self-hosted):    ~Rp 565.000\u002Fbulan\nTOTAL (managed WA):     ~Rp 865.000\u002Fbulan\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nvs CS Manusia:          Rp 3.000.000-5.000.000\u002Fbulan\nSAVINGS:                70-85% 💰\n",[137,3547,3545],{"__ignoreMap":139},[112,3549],{},[115,3551,3553],{"id":3552},"use-case-rekomendasi-penggunaan","🎯 Use Case & Rekomendasi Penggunaan",[14,3555,3556],{},"Berdasarkan diskusi di komunitas, ini beberapa use case OpenClaw yang relevan:",[155,3558,3560],{"id":3559},"_1-e-commerce-cs-seperti-contoh-di-artikel","1. E-commerce CS (Seperti Contoh di Artikel)",[85,3562,3563,3566,3569,3572],{},[62,3564,3565],{},"Jualan baju, elektronik, makanan — apa saja",[62,3567,3568],{},"Auto-reply pertanyaan produk, stok, harga",[62,3570,3571],{},"Checkout otomatis + invoice + payment link",[62,3573,3574],{},"Order tracking",[155,3576,3578],{"id":3577},"_2-service-booking-salon-dokter-bengkel","2. Service Booking (Salon, Dokter, Bengkel)",[85,3580,3581,3584,3587],{},[62,3582,3583],{},"\"Mau booking jam 3 kak\" → cek jadwal → konfirmasi",[62,3585,3586],{},"Reminder otomatis H-1",[62,3588,3589],{},"Reschedule\u002Fcancel handling",[155,3591,3593],{"id":3592},"_3-lead-qualification-real-estate-saas","3. Lead Qualification (Real Estate, SaaS)",[85,3595,3596,3599,3602],{},[62,3597,3598],{},"Qualify leads berdasarkan budget, timeline, needs",[62,3600,3601],{},"Schedule demo\u002Fcall otomatis",[62,3603,3604],{},"CRM integration",[155,3606,3608],{"id":3607},"_4-support-ticket-system","4. Support Ticket System",[85,3610,3611,3614,3617],{},[62,3612,3613],{},"Auto-create ticket dari WA",[62,3615,3616],{},"Escalation ke human CS kalau AI stuck",[62,3618,3619],{},"FAQ auto-answer (RAG dari knowledge base)",[155,3621,3623],{"id":3622},"_5-order-tracking","5. Order Tracking",[85,3625,3626,3629],{},[62,3627,3628],{},"\"Pesanan saya mana kak?\" → query DB → response real-time",[62,3630,3631],{},"Notification otomatis (shipped, delivered)",[112,3633],{},[115,3635,3637],{"id":3636},"implementation-roadmap","📋 Implementation Roadmap",[130,3639,3642],{"className":3640,"code":3641,"language":135},[133],"Progress Setup CS Bot\n\n✅ Phase 1: Architecture & Gateway    ████████████████████ 100%\n🔄 Phase 2: Contact + Invoice Service ██████████████░░░░░░  60%\n⏳ Phase 3: RAG Product Knowledge     ██████░░░░░░░░░░░░░░  30%\n⏳ Phase 4: Payment Gateway            ░░░░░░░░░░░░░░░░░░░░   0%\n⏳ Phase 5: Testing & Go-Live          ░░░░░░░░░░░░░░░░░░░░   0%\n",[137,3643,3641],{"__ignoreMap":139},[689,3645,3646,3659],{},[692,3647,3648],{},[695,3649,3650,3653,3656],{},[698,3651,3652],{},"Phase",[698,3654,3655],{},"Durasi",[698,3657,3658],{},"Deliverable",[708,3660,3661,3674,3687,3699,3712],{},[695,3662,3663,3668,3671],{},[713,3664,3665],{},[22,3666,3667],{},"1. Gateway",[713,3669,3670],{},"1-2 minggu",[713,3672,3673],{},"WA connection, message routing, chat logging",[695,3675,3676,3681,3684],{},[713,3677,3678],{},[22,3679,3680],{},"2. Services",[713,3682,3683],{},"2-3 minggu",[713,3685,3686],{},"Contact DB, Invoice API, Order management",[695,3688,3689,3694,3696],{},[713,3690,3691],{},[22,3692,3693],{},"3. RAG",[713,3695,3670],{},[713,3697,3698],{},"Product knowledge base, vector search",[695,3700,3701,3706,3709],{},[713,3702,3703],{},[22,3704,3705],{},"4. Payment",[713,3707,3708],{},"1 minggu",[713,3710,3711],{},"Midtrans\u002FXendit integration, webhook handling",[695,3713,3714,3719,3721],{},[713,3715,3716],{},[22,3717,3718],{},"5. Testing",[713,3720,3670],{},[713,3722,3723],{},"Edge cases, security audit, load test",[112,3725],{},[115,3727,3729],{"id":3728},"model-selection-untuk-cs","🧠 Model Selection untuk CS",[14,3731,3732,3733,3736],{},"Untuk CS bot, ",[22,3734,3735],{},"nggak perlu model mahal",". Tier 1 sudah cukup:",[689,3738,3739,3755],{},[692,3740,3741],{},[695,3742,3743,3746,3749,3752],{},[698,3744,3745],{},"Model",[698,3747,3748],{},"Kecepatan",[698,3750,3751],{},"Cost per 1K msg",[698,3753,3754],{},"Cocok Untuk",[708,3756,3757,3773,3788,3804,3818],{},[695,3758,3759,3764,3767,3770],{},[713,3760,3761],{},[22,3762,3763],{},"Kimi 2.5",[713,3765,3766],{},"Cepat",[713,3768,3769],{},"~Rp 15.000",[713,3771,3772],{},"CS umum, FAQ, checkout",[695,3774,3775,3780,3782,3785],{},[713,3776,3777],{},[22,3778,3779],{},"DeepSeek V3",[713,3781,3766],{},[713,3783,3784],{},"~Rp 10.000",[713,3786,3787],{},"CS intensif, banyak produk",[695,3789,3790,3795,3798,3801],{},[713,3791,3792],{},[22,3793,3794],{},"Minimax M2.5",[713,3796,3797],{},"Sangat cepat",[713,3799,3800],{},"~Rp 8.000",[713,3802,3803],{},"High volume, simple queries",[695,3805,3806,3809,3812,3815],{},[713,3807,3808],{},"Claude Sonnet",[713,3810,3811],{},"Medium",[713,3813,3814],{},"~Rp 80.000",[713,3816,3817],{},"Complex negotiation, complaints",[695,3819,3820,3823,3825,3828],{},[713,3821,3822],{},"GPT-4o",[713,3824,3811],{},[713,3826,3827],{},"~Rp 100.000",[713,3829,3830],{},"Premium CS, VIP customers",[14,3832,3833,3836],{},[22,3834,3835],{},"Rekomendasi:"," Kimi 2.5 atau DeepSeek V3 untuk daily CS. Claude\u002FGPT hanya untuk escalation yang butuh reasoning lebih dalam.",[112,3838],{},[115,3840,3842],{"id":3841},"kesimpulan","✅ Kesimpulan",[14,3844,3845,3846,3849],{},"Bikin CS bot WhatsApp dengan OpenClaw itu ",[22,3847,3848],{},"bukan mimpi"," — tapi butuh arsitektur yang bener. Inti-nya:",[59,3851,3852,3858,3864,3870,3876,3882],{},[62,3853,3854,3857],{},[22,3855,3856],{},"Selalu pakai gateway"," — OpenClaw jangan langsung connect ke WA",[62,3859,3860,3863],{},[22,3861,3862],{},"3 service minimum",": WA-Gateway, Contact-Service, Invoice-Service",[62,3865,3866,3869],{},[22,3867,3868],{},"RAG untuk product knowledge"," — data dari database, bukan dari memory AI",[62,3871,3872,3875],{},[22,3873,3874],{},"Security non-negotiable"," — customer data HANYA dari DB, audit semua akses",[62,3877,3878,3881],{},[22,3879,3880],{},"Session isolation"," — data customer A nggak bocor ke customer B",[62,3883,3884,3887],{},[22,3885,3886],{},"Model Tier 1 cukup"," — Kimi\u002FDeepSeek untuk daily, Claude\u002FGPT untuk escalation",[130,3889,3892],{"className":3890,"code":3891,"language":135},[133],"━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n📊 Quick Recap\n\nArsitektur: Customer → WA → Gateway → OpenClaw → Services → DB\nSecurity: Data from DB only, audit logged, session isolated\nCost: ~Rp 565K\u002Fbulan (self-hosted) vs CS manusia Rp 3-5jt\nTimeline: 6-10 minggu dari nol sampai production\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n",[137,3893,3891],{"__ignoreMap":139},[11,3895,3896],{},[14,3897,3898,3899,3902],{},"Kalau mau mulai build CS bot dengan OpenClaw, langkah pertama: setup OpenClaw + VPS. Daftar di ",[27,3900,32],{"href":29,"rel":3901},[31]," buat mulai, lalu ikuti roadmap di artikel ini step by step.",[14,3904,3905],{},"━━━━━━━━━━━━",[14,3907,3908,3911,3914],{},[2410,3909,3910],{},"Tech stack: OpenClaw, Baileys.js, PostgreSQL + PgVector, Redis, BullMQ, Express.js",[2410,3912,3913],{},"Security: JWT auth, API key per service, audit logging, session isolation",[2410,3915,3916],{},"Last updated: April 2026",[3918,3919,3920],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":139,"searchDepth":265,"depth":265,"links":3922},[3923,3924,3929,3932,3938,3943,3948,3954,3960,3963,3964,3971,3972,3973],{"id":117,"depth":265,"text":118},{"id":149,"depth":265,"text":150,"children":3925},[3926,3927,3928],{"id":157,"depth":271,"text":158},{"id":200,"depth":271,"text":201},{"id":401,"depth":271,"text":402},{"id":475,"depth":265,"text":476,"children":3930},[3931],{"id":686,"depth":271,"text":687},{"id":828,"depth":265,"text":829,"children":3933},[3934,3935,3936,3937],{"id":852,"depth":271,"text":853},{"id":862,"depth":271,"text":863},{"id":872,"depth":271,"text":873},{"id":1027,"depth":271,"text":1028},{"id":1167,"depth":265,"text":1168,"children":3939},[3940,3941,3942],{"id":1177,"depth":271,"text":1178},{"id":1446,"depth":271,"text":1447},{"id":1658,"depth":271,"text":1659},{"id":1743,"depth":265,"text":1744,"children":3944},[3945,3946,3947],{"id":1920,"depth":271,"text":1921},{"id":2148,"depth":271,"text":2149},{"id":2386,"depth":271,"text":2387},{"id":2404,"depth":265,"text":2405,"children":3949},[3950,3951,3952,3953],{"id":2419,"depth":271,"text":2420},{"id":2429,"depth":271,"text":2430},{"id":2578,"depth":271,"text":2579},{"id":2742,"depth":271,"text":2743},{"id":2936,"depth":265,"text":2937,"children":3955},[3956,3957,3958,3959],{"id":2947,"depth":271,"text":2948},{"id":2957,"depth":271,"text":2958},{"id":3219,"depth":271,"text":3220},{"id":3324,"depth":271,"text":3325},{"id":3402,"depth":265,"text":3403,"children":3961},[3962],{"id":3415,"depth":271,"text":3416},{"id":3540,"depth":265,"text":3541},{"id":3552,"depth":265,"text":3553,"children":3965},[3966,3967,3968,3969,3970],{"id":3559,"depth":271,"text":3560},{"id":3577,"depth":271,"text":3578},{"id":3592,"depth":271,"text":3593},{"id":3607,"depth":271,"text":3608},{"id":3622,"depth":271,"text":3623},{"id":3636,"depth":265,"text":3637},{"id":3728,"depth":265,"text":3729},{"id":3841,"depth":265,"text":3842},"tech","2026-04-04 14:30:00","Bikin customer service bot WhatsApp dengan OpenClaw sebagai otak, gateway sebagai jembatan, dan database strict yang nggak bocor. Panduan lengkap dari arsitektur sampai security — termasuk RAG, invoice otomatis, dan payment gateway integration.","md",{},"\u002Ftech\u002Fopenclaw-cs-whatsapp-gateway",null,{"title":5,"description":3976},"tech\u002Fopenclaw-cs-whatsapp-gateway",[3984,3985,3986,3987,3988,3989,3990],"openclaw","whatsapp","customer-service","automation","gateway","rag","security","_J2JdKP-P_7goBRG-Ut-rVtPaonoJEy_zfvnbk8uvWc",1775317692339]