Expense Buddy Source Code Setup Guide
เอกสารคู่มือสำหรับ ตั้งค่า และการ deploy สำหรับ Expense Buddy ด้วยตัวเอง
ใช้ AI ช่วย setup ได้เลย
ถ้าไม่ถนัด Terminal หรืออยากให้ AI พาทำทีละขั้นตอน copy prompt ด้านล่างไปวางได้เลย:
ภาษาไทย
อ่านไฟล์ SETUP.md แล้วช่วย setup และ deploy โปรเจกต์นี้ทีละขั้นตอนทำทีละ step — ถามหรือรอให้ฉันทำเสร็จก่อนไป step ถัดไปเสมอภาษาอังกฤษ
Read SETUP.md and help me set up and deploy this project step by step.Guide me one step at a time — ask for my input or confirmation before moving to the next step.0) สิ่งที่ต้องมีล่วงหน้า
- บัญชี Cloudflare (Workers + D1)
- LINE Official Account (OA) ที่ใช้งานอยู่
- บัญชี LINE Developers Console และ LINE OA Manager
- บัญชี Stripe (สำหรับ Pro/ชำระเงิน) (Optional: หากใช้คนเดียว ไม่ต้องเปิด feature pro ก็ได้)
- Bun หรือ Node.js
- โดเมน HTTPS สำหรับ Production (หรือใช้
*.workers.devที่ได้จาก Cloudflare ฟรี)
อธิบายเกี่ยวกับ link ของ LINE:
- LINE Developers Console - เอาไว้สร้าง Provider, Channel
- LINE OA Manager - จัดการ LINE OA ของคุณ เช่น Chat, Rich Menu เพื่อเปิด LIFF
1) โครงสร้างที่ต้องเข้าใจก่อน
ระบบนี้ใช้ LINE 2 ช่องทางแยกกัน:
- Messaging API channel (สร้างผ่าน https://entry.line.biz/form/entry/unverified)
- ใช้กับ OA, webhook, bot message
- LINE Login channel
- ใช้กับ LIFF และ token ของ LIFF
สรุป: OA/Bot กับ LIFF เป็นคนละ channel แต่ใช้งานร่วมกันได้
2) ติดตั้งโปรเจกต์
bun install3) ตั้งค่า Build-time env (ฝั่งหน้าเว็บ)
สร้าง/แก้ .env สำหรับ local หรือใส่ใน Cloudflare Build Variables:
PUBLIC_LIFF_ID=REPLACE_WITH_LIFF_IDPUBLIC_LINE_OA_URL=https://line.me/R/ti/p/@YOUR_OA_BASIC_IDหมายเหตุ:
PUBLIC_LIFF_IDใช้ค่า LIFF ID เท่านั้น เช่น200925xxxx-Udad3xxxไม่ใช่ URL เต็ม (วิธีการสร้าง ดู Step 5.2)PUBLIC_LINE_OA_URLเอาจาก OA Basic ID ที่ LINE OA Manager- ถ้ายังไม่มีค่าเหล่านี้ ให้ข้ามไป Step 5 ก่อน แล้วกลับมาเติมที่นี่ทีหลัง
4) ตั้งค่า Cloudflare D1, Queue และ Wrangler
ก่อนรัน wrangler command ใดๆ ต้อง login ก่อน:
wrangler login
# สามารถใช้ bun ได้ หากไม่ได้ตั้งค่า PATH# เช่น bun wrangler login4.1 สร้าง D1
wrangler d1 create expense_buddy
# สามารถใช้ bun ได้ หากไม่ได้ตั้งค่า PATH# เช่น bun wrangler d1 create expense_buddyนำ database_id ที่ได้ไปใส่ใน wrangler.jsonc:
"d1_databases": [ { "binding": "DB", "database_name": "expense_buddy", "database_id": "<REAL_DATABASE_ID>", "migrations_dir": "drizzle/migrations" }]4.2 รัน migration
bun run db:migrate:localbun run db:migrate:remote4.3 สร้าง Queue สำหรับวิเคราะห์รูป
ระบบใช้ Queue สำหรับงานวิเคราะห์รูป เพื่อให้ webhook ตอบกลับได้เร็ว และส่งผลลัพธ์ภายหลังด้วย pushMessage
wrangler queues create expense-buddy-image-analysisbinding ใน wrangler.jsonc มีอยู่แล้ว ไม่ต้องแก้ไฟล์ แค่สร้าง resource บน Cloudflare ด้วยคำสั่งข้างบนให้ตรงชื่อ
ตัวอย่าง config ที่อยู่ในไฟล์แล้ว:
"queues": { "producers": [ { "binding": "IMAGE_ANALYSIS_QUEUE", "queue": "expense-buddy-image-analysis" } ], "consumers": [ { "queue": "expense-buddy-image-analysis", "max_batch_size": 1, "max_batch_timeout": 5, "max_retries": 3 } ]}หมายเหตุ:
queueคือชื่อ resource จริงบน Cloudflarebindingคือชื่อที่โค้ดใช้ใน Worker (env.IMAGE_ANALYSIS_QUEUE)- ถ้าเปลี่ยนชื่อ binding ใน config ต้องแก้โค้ดให้ตรงกันด้วย
4.4 สร้าง Queue และ R2 สำหรับ export CSV แบบ background
ระบบ export CSV ใช้ Queue + R2 เพื่อไม่ให้ LIFF ค้างระหว่างสร้างไฟล์
wrangler queues create expense-buddy-export-csvwrangler r2 bucket create expense-buddy-export-csvbinding ใน wrangler.jsonc มีอยู่แล้ว ไม่ต้องแก้ไฟล์ แค่สร้าง resource บน Cloudflare ด้วยคำสั่งข้างบนให้ตรงชื่อ
ตัวอย่าง config ที่อยู่ในไฟล์แล้ว:
"r2_buckets": [ { "binding": "EXPORT_CSV_BUCKET", "bucket_name": "expense-buddy-export-csv" }],"queues": { "producers": [ { "binding": "IMAGE_ANALYSIS_QUEUE", "queue": "expense-buddy-image-analysis" }, { "binding": "EXPORT_CSV_QUEUE", "queue": "expense-buddy-export-csv" } ], "consumers": [ { "queue": "expense-buddy-image-analysis", "max_batch_size": 1, "max_batch_timeout": 5, "max_retries": 3 }, { "queue": "expense-buddy-export-csv", "max_batch_size": 1, "max_batch_timeout": 5, "max_retries": 3 } ]}หมายเหตุ:
EXPORT_CSV_QUEUEใช้สำหรับประมวลผล export CSV แบบ backgroundEXPORT_CSV_BUCKETใช้เก็บไฟล์ CSV ชั่วคราว ระบบตั้ง retention 24 ชั่วโมง
4.5 ตั้งค่า Stripe สำหรับ Pro (ถ้าเปิดใช้)
- เข้า Stripe Dashboard แล้วสร้างสินค้า/ราคาแบบ one-time เช่น THB 990
- คัดลอก
price_...มาใช้เป็นstripePriceId - สร้าง Webhook endpoint:
https://<APP_BASE_URL>/api/stripe/webhook- เลือก events อย่างน้อย:
checkout.session.completedcheckout.session.async_payment_succeeded
หมายเหตุ:
- ระบบ unlock Pro เมื่อ
payment_status = paidเท่านั้น - แนะนำเปิด event
checkout.session.async_payment_failedเพิ่มเพื่อแจ้งเตือนกรณีจ่ายไม่สำเร็จ
สร้าง runtime types (แนะนำ)
bun run cf-typegen5) เชื่อม LINE OA
ยังไม่มี LINE OA? ให้สร้างก่อนที่ https://entry.line.biz/form/entry/unverified แล้วกลับมาทำ Step นี้
5.1 ตั้ง Messaging API channel ให้ OA
- เข้า https://manager.line.biz/ เลือก Settings ด้านขวาบน -> Messaging API
- 1.1 กรณียังไม่เคยมี ให้กด Enable Messaging API
- 1.2 หากมี Messaging API อยู่แล้ว จะแสดง
Messaging APIMessaging API is an advanced feature for developers. It allows accounts to promote more interactive communication by sending and receiving messages and actions via the API.What is Messaging API?LINE Developers API documentation
Status EnabledChannel info Channel ID xxxxxxxxxxChannel secret xxxxxxxxxxxxxxxxxxxxxxxxxxxxWebhook URL https://{DOMAIN_NAME}/api/line/webhookค่าที่ต้อง copy จาก Messaging API channel:
LINE_CHANNEL_SECRET — copy จากหน้านี้ที่ช่อง Channel secret
LINE_CHANNEL_ACCESS_TOKEN — ค่านี้อยู่ใน LINE Developers Console (ไม่ใช่ OA Manager):
- เข้า https://developers.line.biz/console/
- เลือก Provider → เลือก Messaging API channel (ไม่ใช่ LINE Login)
- ไปที่แท็ป Messaging API
- เลื่อนลงหาหัวข้อ “Channel access token (long-lived)”
- กด Issue → copy token ที่ได้
หมายเหตุ: Channel ID ของ Messaging API ไม่ได้ใช้ในโปรเจกต์นี้ ไม่ต้อง copy
การตั้ง Webhook URL — ข้ามไปก่อน ยังไม่มี domain ตอนนี้ กลับมาตั้งหลัง deploy เสร็จใน Step 9 รูปแบบที่ต้องใส่คือ:
https://<APP_BASE_URL>/api/line/webhook5.2 สร้าง LINE Login channel (แยกจาก Messaging API)
- เข้า
developers.line.biz - เลือก Provider เดียวกับ OA
- สร้าง LINE Login channel ใหม่ (Create a new channel)
- เลือกเป็น LINE Login 4.1 เลือก Region to provide the service เป็น Thailand 4.2 เลือก Company or owner’s country or region ใส่ Thailand 4.3 ทำการตั้งชื่อ Channel กำหนด icon 4.4 App types เลือกเป็น Web Apps
- เมื่อสร้างเสร็จ ที่แท็ป Basic Settings จะได้
Channel ID- copy ค่านี้ → ใช้เป็น
LIFF_CHANNEL_ID - (
Channel secretของ LINE Login ไม่ได้ใช้ในโปรเจกต์นี้)
- copy ค่านี้ → ใช้เป็น
- ไปที่แท็ป LIFF กด Add เพื่อสร้าง LIFF ใหม่
- 6.1 กำหนด LIFF Name ของเรา เลือกขนาด (Size) เป็น Full หรือใครชอบแบบ ไม่เต็ม หรือครึ่งจอ ก็เลือก Tall หรือ Compact ก็ได้
- 6.2 ตั้ง Endpoint URL เป็น
https://<APP_BASE_URL>/liff - 6.3 Scope เลือก
profileและopenid(ต้องเลือกทั้งสองตัว)
- เมื่อสร้าง LIFF เสร็จ จะได้
LIFF IDเช่น2009250992-Udad3StM- copy ค่านี้ → ใช้เป็นทั้ง
PUBLIC_LIFF_ID(ใน.env) และLIFF_ID(runtime var)
- copy ค่านี้ → ใช้เป็นทั้ง
สรุปค่าที่ได้จาก Step 5 ทั้งหมด:
| ค่าที่ได้ | มาจากไหน | ใช้เป็น variable |
|---|---|---|
| Channel Secret (Messaging API) | OA Manager → Messaging API | LINE_CHANNEL_SECRET |
| Channel Access Token (Messaging API) | LINE Developers → Messaging API tab → Issue | LINE_CHANNEL_ACCESS_TOKEN |
| Channel ID (LINE Login) | LINE Developers → LINE Login channel → Basic Settings | LIFF_CHANNEL_ID |
| LIFF ID | LINE Developers → LINE Login channel → LIFF tab | LIFF_ID และ PUBLIC_LIFF_ID |
6) ตั้งค่า runtime vars และ secrets
ใน Worker ต้องมีค่าหลักดังนี้:
vars (ไม่ลับ):
AI_MODELLIFF_CHANNEL_IDเป็นค่าที่ไม่ลับ สามารถเก็บเป็น vars ได้LIFF_IDค่าเดียวกับPUBLIC_LIFF_IDเช่น2009250992-Udad3StM
secrets (ลับ):
LINE_CHANNEL_SECRET— มาจาก Messaging API channel (Step 5.1)LINE_CHANNEL_ACCESS_TOKEN— มาจาก Messaging API channel → Issue access token (Step 5.1)SESSION_SECRET— random string สำหรับ sign session cookie สร้างได้ด้วย:openssl rand -base64 32STRIPE_SECRET_KEYจำเป็นเมื่อเปิด ProSTRIPE_WEBHOOK_SECRETจำเป็นเมื่อเปิด Pro
6.1 ตั้ง secret แบบปกติ
wrangler secret put LINE_CHANNEL_SECRETwrangler secret put LINE_CHANNEL_ACCESS_TOKENwrangler secret put SESSION_SECRETwrangler secret put STRIPE_SECRET_KEYwrangler secret put STRIPE_WEBHOOK_SECRET6.2 ถ้าใช้ Workers Versions/Deployments flow
ถ้าเจอข้อความว่าห้าม secret put ตรงๆ ให้ใช้:
wrangler versions secret put LINE_CHANNEL_SECRETwrangler versions secret put LINE_CHANNEL_ACCESS_TOKENwrangler versions secret put SESSION_SECRETwrangler versions secret put STRIPE_SECRET_KEYwrangler versions secret put STRIPE_WEBHOOK_SECRETwrangler versions deploy7) Local dev flow
bun run db:generatebun run db:migrate:localbun run dev:seed -- --count 20bun run devหน้า dev login:
http://127.0.0.1:4321/dev/login8) เพิ่ม LIFF URL เข้า LINE OA
เมื่อ Endpoint URL เป็น https://<APP_BASE_URL>/liff ให้ใช้ลิงก์แบบ endpoint-relative:
https://liff.line.me/<LIFF_ID>/dashboardโดย <LIFF_ID> คือรหัสอย่างเดียว เช่น 2009250000-Udad3StM
ไม่ต้องใส่ https://liff.line.me/ ลงในตัวแปร PUBLIC_LIFF_ID หรือ LIFF_ID
ตัวอย่างลิงก์ที่ใช้ใน Rich Menu / Flex:
https://liff.line.me/<LIFF_ID>/dashboardhttps://liff.line.me/<LIFF_ID>/settingshttps://liff.line.me/<LIFF_ID>/historyhttps://liff.line.me/<LIFF_ID>/addใส่ลิงก์นี้ใน:
- Rich Menu
- ปุ่มในข้อความ
- เมนู OA
ไม่จำเป็นต้องสร้างหลาย LIFF app สำหรับหน้า add/history/dashboard/settings เพราะในแอปมี navigation อยู่แล้ว
ตัวอย่าง การตั้งค่า
ไปที่ LINE OA Manager -> Chat Screen -> Rich Menu -> Create New
// เลือก template และ link เช่นLink - https://liff.line.me/{LIFF_ID}/dashboardLink - https://liff.line.me/{LIFF_ID}/settings9) การ Deploy
มี 2 วิธี เลือกอย่างใดอย่างหนึ่ง:
วิธีที่ 1 — Deploy จากเครื่องตัวเองด้วย Wrangler
ก่อน deploy ต้องเตรียม 3 ส่วน:
ส่วนที่ 1 — Build-time env (อ่านจาก .env ตอน bun run build)
ไฟล์ .env ในโปรเจกต์:
PUBLIC_LIFF_ID=2009250992-Udad3StMPUBLIC_LINE_OA_URL=https://line.me/R/ti/p/@YOUR_OA_BASIC_IDส่วนที่ 2 — Runtime vars (อยู่ใน wrangler.jsonc แล้ว deploy ขึ้นไปพร้อมกัน)
แก้ค่าใน wrangler.jsonc:
"vars": { "AI_MODEL": "@cf/google/gemma-4-26b-a4b-it", "LIFF_ID": "YOUR_LIFF_ID", "LIFF_CHANNEL_ID": "YOUR_LINE_LOGIN_CHANNEL_ID"}ค่าเหล่านี้ไม่ใช่ความลับ เก็บใน wrangler.jsonc ได้เลย
ส่วนที่ 3 — Secrets (ต้อง wrangler secret put แยกต่างหาก)
.env และ .dev.vars ไม่ได้ ถูก upload ขึ้น Cloudflare — secrets ต้องตั้งด้วยคำสั่งนี้ (ทำครั้งเดียว):
wrangler secret put LINE_CHANNEL_SECRETwrangler secret put LINE_CHANNEL_ACCESS_TOKENwrangler secret put SESSION_SECRET# ถ้าเปิด Pro:wrangler secret put STRIPE_SECRET_KEYwrangler secret put STRIPE_WEBHOOK_SECRETDeploy:
bun run buildbun run deployวิธีที่ 2 — Deploy ผ่าน Cloudflare Git Integration (PR merge → auto deploy)
เตรียม GitHub repo ก่อน (ถ้ายังไม่มี)
โค้ดต้องอยู่บน GitHub ก่อน — ตั้งเป็น private repo ได้:
git initgit add .git commit -m "initial commit"# สร้าง repo ที่ github.com แล้ว:git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.gitgit push -u origin mainสร้าง Worker ใน Cloudflare Dashboard
- เข้า Cloudflare Dashboard → Workers & Pages
- กด Create application
- เลือก Pages แท็ป → กด Connect to Git
- เชื่อม GitHub account → เลือก repo ของคุณ
- ตั้งค่า build:
- Framework preset: None (หรือ Astro ถ้ามีให้เลือก)
- Build command:
bun run build - Build output directory:
dist
- กด Save and Deploy
ส่วนที่ 1 — Build-time env ตั้งใน Cloudflare Dashboard:
Workers & Pages → โปรเจกต์ → Settings → Environment Variables → Add variable (ไม่ต้อง encrypt):
PUBLIC_LIFF_IDPUBLIC_LINE_OA_URL
ส่วนที่ 2 — Runtime vars อยู่ใน wrangler.jsonc แล้ว deploy ขึ้นไปพร้อม commit อัตโนมัติ ไม่ต้องตั้งใน Dashboard
ส่วนที่ 3 — Secrets ตั้งผ่าน wrangler secret put จากเครื่องของคุณ (ทำครั้งเดียว):
wrangler secret put LINE_CHANNEL_SECRETwrangler secret put LINE_CHANNEL_ACCESS_TOKENwrangler secret put SESSION_SECRET# ถ้าเปิด Pro:wrangler secret put STRIPE_SECRET_KEYwrangler secret put STRIPE_WEBHOOK_SECRETหรือตั้งใน Dashboard → Settings → Environment Variables → Add variable → เลือก Encrypt
checklist ก่อน deploy (ทั้ง 2 วิธี):
- Queue
expense-buddy-image-analysisสร้างแล้ว - Queue
expense-buddy-export-csvสร้างแล้ว - R2 bucket
expense-buddy-export-csvสร้างแล้ว -
wrangler.jsoncมี bindingIMAGE_ANALYSIS_QUEUE,EXPORT_CSV_QUEUE,EXPORT_CSV_BUCKET - ถ้าเปิด Pro: ตั้ง
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRETและ Stripe webhook endpoint/api/stripe/webhook
หลัง deploy เสร็จ — กลับไปตั้งค่าที่ข้ามไว้:
Deploy สำเร็จแล้วจะได้ domain จาก Cloudflare เช่น https://your-worker.workers.dev (หรือ custom domain ถ้าตั้งไว้)
-
ตั้ง Webhook URL ใน LINE OA Manager (Step 5.1 ที่ข้ามไว้):
- เข้า https://manager.line.biz/ → Settings → Messaging API
- ใส่ Webhook URL:
https://<APP_BASE_URL>/api/line/webhook - กด Verify เพื่อทดสอบ และเปิด Use webhook
-
ตั้ง LIFF Endpoint URL (ถ้ายังไม่ได้ตั้งตอนสร้าง LIFF ใน Step 5.2):
- LINE Developers Console → LINE Login channel → LIFF → แก้ Endpoint URL เป็น
https://<APP_BASE_URL>/liff
- LINE Developers Console → LINE Login channel → LIFF → แก้ Endpoint URL เป็น
10 ตั้งค่า Pro Feature (Optional)
กรณีต้องการเปิด Pro feature ต้องปรับค่า src/config.json เพื่อกำหนดว่า Free และ Pro ใช้อะไรได้บ้าง
ตัวอย่างเช่น user ทุกคน สามารถใช้ image AI ได้ 10 ครั้ง และ transaction ได้ 100 ครั้ง (ต่อเดือน) แต่ถ้าเป็น Pro (จ่ายเงินผ่าน Stripe แบบ one-time) เราก็จะตั้งค่า โดยเปิด pro.enabled เป็น true และกำหนด stripePriceId และ price
// 1. default pro.enabled=false คือทุก account ไม่จำกัด image ai และ transaction{ "imageAi": 10, "transaction": 100, "pro": { "enabled": false, "stripePriceId": "", "price": 0 }}
// 2. ตั่งค่าใช้ pro, user ฟรีใช้งานได้จำกัด (imageAi และ transaction จะไม่ถูกอ่าน){ "imageAi": 10, "transaction": 100, "pro": { "enabled": true, "stripePriceId": "YOUR_STRIPE_PRICE_ID", "price": 99000 // price ในหน่วยสตางค์ (1000 satang = 1 บาท) }}หมายเหตุ:
pro.enabled = falseจะไม่เปิด gatepro.enabled = trueจะเริ่มจำกัด free user ตามimageAiและtransactionpriceเป็น satang สำหรับแสดงผล UI เช่น99000= 990 บาท
Local dev
ใช้สำหรับรัน local เพื่อเปิดดู web ไม่ผ่าน LINE (bypass AUTH) พร้อมทั้งทำการ seed database เพื่อให้มี transaction
เริ่มต้น ให้เราทำการ สร้างไฟล์ .dev.vars ใน root ของ project เนื่องจาก Wrangler อ่าน secrets จากไฟล์นี้ตอนรัน local (ไม่ใช่จาก .env):
# ไฟล์ .dev.varsDEV_AUTH_BYPASS=1SESSION_SECRET=<random string เช่น openssl rand -base64 32>LINE_CHANNEL_SECRET=...LINE_CHANNEL_ACCESS_TOKEN=...LIFF_CHANNEL_ID=...APP_BASE_URL=http://127.0.0.1:4321AI_VISION_MODEL=@cf/google/gemma-4-26b-a4b-itและไฟล์ .env ให้เพิ่ม เพื่อให้หน้า /dev/login แสดงผล (by pass)
# ไฟล์ .envPUBLIC_DEV_AUTH_BYPASS=1รัน (ครั้งแรก)
bun run db:migrate:localbun run dev:seed -- --count 100bun run devdb:migrate:local— ทำครั้งเดียว สร้าง local DB ที่.wrangler/state/v3/d1/ถ้ามีแล้วไม่ต้องรันซ้ำ เว้นแต่มี migration ใหม่dev:seed— inject ข้อมูลทดสอบเข้า local DB ใส่ตัวเลขได้ตามต้องการ รันซ้ำได้เพื่อเพิ่มข้อมูล รองรับ--months-back <1-24>(default: 6) เพื่อ mock transaction ย้อนหลังdb:generate— ไม่ต้องรันถ้า clone repo มาแล้ว รันเฉพาะตอนแก้ Drizzle schema เพื่อ generate migration files ใหม่
รัน (ครั้งต่อไป)
bun run devDev login (ไม่ต้องเปิด LINE)
วิธีทดสอบ เปิดเว็บ และลองเล่นดูได้ http://localhost:4321/dev/login -> กด เข้าสู่ระบบโหมดพัฒนา
ใส่ LINE User ID (default: U_LOCAL_DEV_001) และ Display Name ได้ตามต้องการ — ระบบ redirect ไป dashboard โดยไม่ผ่าน LINE OAuth