Boom Field Lab

รับเงินด้วย Stripe ทั้งบัตรและ PromptPay: ระบบจ่ายเงินจริงของ Boom Field Lab ตั้งแต่กดซื้อจนได้สิทธิ์

เบื้องหลังระบบรับเงินของเว็บ workshop นี้แบบ build in public — ทำไมเคยจงใจปิด PromptPay ใน Stripe แล้วกลับมาเปิด, ทำไมราคาต้องคิดฝั่ง server และล็อกไว้ 60 นาที, webhook ตัวเดียวเปิดสิทธิ์ยังไง และค่าธรรมเนียมจริงต่อการขาย 1 ครั้ง

โดย วรัญชัย ยิ่งคำนึง (Boom) · เผยแพร่ครั้งแรก · ตรวจทานล่าสุด

มือถือสมาร์ตโฟนยื่นไปที่จุดชำระเงินในตลาดกลางคืน พร้อมข้อความ รับเงินอัตโนมัติ บัตร + PromptPay

ระบบรับเงินเป็นส่วนที่คนทำ product เล็กๆ มักทำทีหลังสุด แล้วมาพังตอนมีลูกค้าจริง บทความนี้เปิดเบื้องหลังของ Boom Field Lab — เว็บ workshop ที่คุณกำลังอ่านอยู่ — ว่าเงินเดินทางยังไงตั้งแต่กดซื้อจนได้สิทธิ์ดูวิดีโอ พร้อมเหตุผลของแต่ละการตัดสินใจแบบคนทำงาน risk

ทั้งระบบนี้เขียนด้วย AI agent เป็นหลัก (เหตุผลที่เลือกเครื่องมืออยู่ใน ทำไมผมสร้าง Terminal ด้วย Claude Code) แต่ทุกกฎในบทความนี้เป็นการตัดสินใจของคน — agent แค่เขียนตามกฎ

ทำไมเคยจงใจปิด PromptPay ใน Stripe แล้วตอนนี้กลับมาเปิด?

เวอร์ชันแรก (มิถุนายน 2026) ผมรับเงิน 2 ทาง: PromptPay แบบโอนแล้วแนบสลิป (ค่าธรรมเนียม 0 บาท) กับ บัตรผ่าน Stripe และจงใจปิดปุ่ม PromptPay ใน Stripe ทิ้ง เพราะไม่อยากจ่ายค่าธรรมเนียมให้เงินที่รับฟรีได้อยู่แล้ว

ฟังดูประหยัด แต่ต้นทุนจริงของ "ฟรี" ซ่อนอยู่ที่อื่น:

PromptPay แนบสลิป (เดิม)PromptPay ผ่าน Stripe (ปัจจุบัน)
ค่าธรรมเนียม0 บาท1.65% ≈ ฿5.76 ต่อ ฿349
ใครยืนยันว่าเงินเข้าคน (เปิดดูสลิปทีละใบ)Stripe ยืนยันเอง
ลูกค้าได้สิทธิ์เมื่อไหร่เมื่อคนว่างมาอนุมัติทันทีที่เงินเข้า
ที่เก็บสลิป + หน้าอนุมัติต้องมี ต้องดูแลลบทิ้งทั้งหมด
ความเสี่ยงสลิปปลอมมีไม่มี (ไม่ได้ดูสลิป)

พอตัดสินใจทำเว็บ workshop ราคาเดียว ฿349 ที่ขาย 24 ชั่วโมง การมีคนเป็นคอขวดในเส้นทางเงินกลายเป็นจุดอ่อนที่แพงที่สุด ผมเลยยอมจ่าย ~฿5.76 ต่อรายการเพื่อซื้อ 3 อย่าง: ลูกค้าได้ของทันที, ไม่มีงานมือ, และโค้ดสั้นลงเพราะลบระบบสลิปทั้งชุดทิ้ง หลักคิดเดิมยังเหมือนเดิม — จ่ายค่าธรรมเนียมเฉพาะตอนที่มันซื้ออะไรกลับมา — แค่ของที่ได้กลับมาเปลี่ยนไป

เงินเดินทางยังไง ตั้งแต่กดซื้อจนได้สิทธิ์?

แผนภาพ 5 ขั้นของการรับเงิน: ตะกร้าส่งแค่ slug, server คิดราคา, Stripe Checkout บัตรและ PromptPay หมดอายุ 60 นาที, webhook ตรวจลายเซ็น, เปิดสิทธิ์แบบกดซ้ำไม่ซ้ำซ้อน
เส้นทางเงินของ Boom Field Lab · อ่านจากโค้ดจริงของเว็บนี้ ณ 27 ก.ย. 2026 · ขั้น webhook คือจุดเดียวที่เปิดสิทธิ์ได้
  1. ตะกร้า — ผู้ซื้อกดซื้อ ฟอร์มส่งไปแค่รายชื่อ slug ของ workshop ไม่มีราคาติดไปด้วย
  2. Server คิดราคา — ตัด slug ที่ไม่มีจริงหรือยังเป็น draft ทิ้ง, ตัดตัวที่ผู้ซื้อ มีสิทธิ์อยู่แล้ว ทิ้ง (ไม่ให้จ่ายซ้ำ), แล้วคำนวณราคาจากกฎกลาง: ฿349 ปกติ หรือ ฿249 ถ้ายังอยู่ใน 7 วันแรกหลังเปิดตัว
  3. Stripe Checkout — สร้างหน้าชำระเงินของ Stripe (เราไม่เก็บข้อมูลบัตรเองเลย) เปิดบัตร + PromptPay, บังคับติ๊กยอมรับข้อกำหนดและนโยบายคืนเงิน, หมดอายุใน 60 นาที
  4. Webhook — Stripe ยิงเหตุการณ์กลับมา เราตรวจลายเซ็นก่อนเชื่อทุกครั้ง
  5. เปิดสิทธิ์ — บันทึกสิทธิ์ + คำสั่งซื้อ แล้วส่งอีเมลยืนยัน ครั้งเดียวเท่านั้นไม่ว่าจะถูกเรียกกี่รอบ

ทำไมราคาต้องคิดฝั่ง server และล็อกไว้ 60 นาที?

เพราะอะไรก็ตามที่มาจากเบราว์เซอร์ แก้ได้ ถ้าฟอร์มส่งราคามาด้วย ใครก็เปิด DevTools เปลี่ยน 349 เป็น 1 ได้ ระบบนี้เลยรับจากผู้ซื้อแค่ "อยากได้ตัวไหน" ส่วน "ต้องจ่ายเท่าไหร่" คำนวณที่ server ทุกครั้ง หน้าตาโค้ดโดยย่อ:

// ฟอร์มส่งมาแค่ slug — ราคาคำนวณใหม่ที่ server ทุกครั้ง
const { lines } = priceCart(items, activeCount);

form.set("payment_method_types[0]", "card");
form.set("payment_method_types[1]", "promptpay");
// ล็อกราคาไว้ใน session นี้ 60 นาที
form.set("expires_at", String(nowSec + 60 * 60));

ทำไม 60 นาที? ราคา early-bird ฿249 มีวันหมดอายุ ถ้าผู้ซื้อเปิดหน้าชำระเงินตอน 23:50 ของวันสุดท้ายแล้วจ่ายตอน 00:10 เขาควรได้ราคาที่เห็นตอนกดซื้อ Stripe กำหนดอายุ session ขั้นต่ำไว้ 30 นาที ผมเลือก 60 นาทีเพื่อให้พอสำหรับคนที่ต้องสลับไปเปิดแอปธนาคาร และจำกัด "ความเสียหายสูงสุด" ไว้ชัดๆ: early-bird ยืดได้เกินกำหนดอย่างมาก 1 ชั่วโมง — ความเสี่ยงที่รู้ขนาดและยอมรับได้

PromptPay ต่างจากบัตรตรงไหนในสายตาของ webhook?

นี่คือกับดักที่ทำให้หลายระบบเปิดสิทธิ์ผิด บัตรจ่ายเสร็จในหน้าเดียว แต่ PromptPay ผู้ซื้อต้องไปสแกน QR ในแอปธนาคาร เงินจึง มาทีหลัง

แผนภาพลำดับเหตุการณ์ของ PromptPay: completed มาก่อนแต่ยังไม่จ่าย สถานะ pending ห้ามเปิดสิทธิ์ แล้วจึงมี async_payment_succeeded ซึ่งเป็นจุดที่เปิดสิทธิ์
ลำดับเหตุการณ์ Checkout ของ PromptPay ตามเอกสาร Stripe และโค้ดของเว็บนี้ · บัตรจบในเหตุการณ์แรก PromptPay ต้องรอเหตุการณ์ที่สอง
วิธีจ่ายเหตุการณ์แรกสถานะที่เราตีความเปิดสิทธิ์เมื่อ
บัตรcheckout.session.completed (paid)paidทันที
PromptPaycheckout.session.completed (unpaid)pending — ห้ามเปิดสิทธิ์checkout.session.async_payment_succeeded
PromptPay ไม่สำเร็จcheckout.session.async_payment_failedfailedไม่เปิด

คอมเมนต์ในโค้ดจริงเขียนกฎนี้ไว้ตรงบรรทัดที่เปิด PromptPay:

การ์ดข้อความคอมเมนต์จริงในโค้ดของเว็บ: PromptPay เป็นแบบ async ผู้ซื้อสแกน QR ยอดขายจึงยืนยันทีหลังผ่าน webhook async_payment_succeeded ไม่ใช่ตอนกลับมาที่หน้าเว็บ
คอมเมนต์จริงในโค้ดสร้าง Checkout session ของ Boom Field Lab · ยกมาคำต่อคำ

PromptPay: async — the buyer scans a QR, so the sale is confirmed later via the checkout.session.async_payment_succeeded webhook, not on redirect back.

ถ้าเขียนแบบมักง่ายว่า "เห็น completed = เปิดสิทธิ์" ผู้ซื้อ PromptPay ทุกคนจะได้ของ ก่อน จ่ายเงิน และถ้าเขาไม่สแกน QR เลย เราก็แจกฟรี

ค่าธรรมเนียมจริงต่อการขาย 1 ครั้งเท่าไหร่?

กราฟแท่งค่าธรรมเนียม Stripe ต่อการขาย 349 บาท: PromptPay 5.76 บาท บัตรในประเทศ 22.74 บาท บัตรต่างประเทศ 26.58 บาท
คำนวณจากราคามาตรฐาน Stripe ประเทศไทย (stripe.com/en-th/pricing) ตรวจเมื่อ 27 ก.ย. 2026 · บัตรต่างประเทศอาจบวกอีก 2% ถ้ามีการแปลงสกุลเงิน

ตามหน้าราคามาตรฐานของ Stripe ประเทศไทย (ตรวจเมื่อ 27 ก.ย. 2026): บัตรในประเทศ 3.65% + ฿10 · บัตรต่างประเทศ 4.75% + ฿10 (+2% ถ้าต้องแปลงสกุลเงิน) · PromptPay 1.65% (+฿10 ต่อการคืนเงิน) · ไม่มีค่าแรกเข้าหรือค่ารายเดือน

ราคาขายPromptPayบัตรในประเทศบัตรต่างประเทศ
฿349 (ปกติ)≈ ฿5.76≈ ฿22.74≈ ฿26.58
฿249 (early-bird)≈ ฿4.11≈ ฿19.09—

สองข้อที่ตารางนี้บอก:

  • ค่าธรรมเนียมคงที่ ฿10 ของบัตรกินหนักกับของราคาถูก — ที่ ฿249 บัตรในประเทศกินไปราว 7.7% ของยอดขาย ขณะที่ PromptPay ยังอยู่ที่ 1.65% สำหรับตลาดไทยที่คนมีแอปธนาคารแทบทุกคน การเปิด PromptPay จึงไม่ใช่แค่ความสะดวกของลูกค้า แต่ลดต้นทุนเราด้วย
  • ลูกค้าจ่ายราคาเดียวกันทุกช่องทาง — ผมไม่บวกค่าธรรมเนียมบัตรเข้าไปในราคา ให้ลูกค้าเลือกตามความสะดวก ส่วนต่างเป็นต้นทุนที่เรารับเอง และเพราะไม่มีค่ารายเดือน เดือนที่ไม่มีคนซื้อเลยต้นทุนก็เป็นศูนย์

Webhook ตัวเดียวต้องกันอะไรบ้าง?

Webhook คือประตูเดียวที่เปิดสิทธิ์ได้ จึงต้องแข็งที่สุดในระบบ:

  • ตรวจลายเซ็นทุกครั้ง — คำนวณ HMAC-SHA256 จาก body ดิบที่ Stripe ส่งมา เทียบกับ header ลายเซ็น ไม่ตรง = ตอบ 400 ทิ้ง ใครยิง request ปลอมมาที่ endpoint นี้ก็เปิดสิทธิ์ไม่ได้
  • กัน replay — ถ้า timestamp ในลายเซ็นเก่ากว่า 5 นาที ปฏิเสธ แม้ลายเซ็นจะถูก
  • กดซ้ำกี่ครั้งก็ได้ผลเดิม (idempotent) — Stripe ส่งเหตุการณ์ซ้ำได้เสมอ สิทธิ์จึงไม่ซ้ำตามคู่ (ผู้ใช้, workshop) และคำสั่งซื้อไม่ซ้ำตาม session อีเมลยืนยันจึงออกครั้งเดียว
  • มีทางสำรองที่ใช้กฎเดียวกัน — ถ้าผู้ซื้อกลับมาถึงหน้า "ชำระเงินสำเร็จ" ก่อน webhook มาถึง หน้านั้นเรียกฟังก์ชันเปิดสิทธิ์ ตัวเดียวกัน ด้วยกฎตัดสินว่า "จ่ายแล้ว" ชุดเดียวกัน — ไม่มีเส้นทางที่สองที่เขียนกฎแยกแล้วเพี้ยน
  • ตอบ 200 กับเหตุการณ์ที่ไม่สนใจ — ไม่งั้น Stripe จะพยายามส่งซ้ำไปเรื่อยๆ

มีอีกกรณีที่เจอจริงตอนต่อระบบ: บัญชี Stripe ใช้ร่วมกับเว็บอื่นในเครือ webhook ของเว็บอื่นจึงได้รับเหตุการณ์ซื้อ workshop ไปด้วย วิธีแก้คือให้ทุกคำสั่งซื้อของ Field Lab ติดป้ายสินค้าขึ้นต้นด้วย lab: แล้วให้ webhook ของเว็บอื่นเห็นป้ายนี้แล้วตอบ 200 ผ่านไปเฉยๆ ไม่แตะอะไร

ยังมีอะไรที่ยังไม่ได้พิสูจน์?

ตรงไปตรงมา: ในโหมดทดสอบของ Stripe การจ่าย PromptPay settle ทันที ในเหตุการณ์แรก เส้นทาง async_payment_succeeded ของจริงจึงยังไม่เคยถูกทดสอบด้วยเงินจริง แผนคือทดสอบซื้อจริงยอดต่ำก่อนเปิดขาย (ปัจจุบันปุ่มซื้อยังปิดอยู่) บันทึกไว้ตรงนี้เพราะระบบเงินที่ "น่าจะใช้ได้" กับ "พิสูจน์แล้วว่าใช้ได้" ต่างกันมาก

สรุป

ระบบรับเงินที่ดีสำหรับ product เล็กไม่ต้องซับซ้อน แต่ต้องถูกในไม่กี่จุด: ไม่เชื่อราคาจากเบราว์เซอร์ · ล็อกราคาในเวลาที่จำกัด · ให้ webhook ที่ตรวจลายเซ็นเป็นประตูเดียวที่เปิดสิทธิ์ · เข้าใจว่า PromptPay มาทีหลัง และ ทำให้ทุกอย่างกดซ้ำได้โดยไม่พัง ค่าธรรมเนียม ~฿5.76 ต่อรายการของ PromptPay ถูกกว่าค่าคนนั่งอนุมัติสลิปเสมอ

BOOM FIELD LAB

อยากเห็นว่าเครื่องมือพวกนี้ประกอบร่างเป็น Terminal จริงได้ยังไง?

Workshop 60 นาที — ดูการทำงานจริงบนหน้าจอ พร้อม workbook ให้ลงมือตาม ไม่ใช่สไลด์ทฤษฎี