~/VibeHandbook
PDF ฟรี

บท 17 · 01

กรณีศึกษาที่ 1: หน้า Landing Page หลายภาษา + ระบบชำระเงิน

ไอเดีย

เพื่อนคนหนึ่งขายคู่มือ PDF ราคา 29 ดอลลาร์ให้กับชาวต่างชาติที่ย้ายมาอยู่เกาหลี เธอต้องการหน้าเว็บเพจเดียวที่โหลดเร็วทั่วโลก พูดได้ทั้งภาษาอังกฤษ เกาหลี และจีน และรับชำระเงินด้วยบัตรได้โดยที่เธอไม่ต้องแตะต้องเซิร์ฟเวอร์เลย

สเปก

เราเขียนสเปกให้อยู่ในหนึ่งย่อหน้าก่อนจะเขียนโค้ดสักบรรทัด:

Build a single-page marketing site for a $29 digital product.
Three languages (en/ko/zh), auto-detected from the browser but
switchable with a toggle. One "Buy" button that opens Stripe
Checkout and redirects to a thank-you page on success. No backend
server I have to maintain. Must load fast from anywhere.

สังเกตสิ่งที่สเปกนี้ ไม่ได้ พูดถึง ไม่มีเฟรมเวิร์ก ไม่มีดีไซน์ระบบ ไม่มีระบบวิเคราะห์ข้อมูล ไม่มีการเก็บอีเมล แต่ละสิ่งเหล่านั้นล้วนเป็นฟีเจอร์ที่สมเหตุสมผลได้ แต่ทุกอันก็จะทำให้การเปิดตัวครั้งแรกช้าลงเช่นกัน วินัยของสเปกหนึ่งย่อหน้าส่วนใหญ่แล้วก็คือวินัยของการ "ตัดออก" นั่นเอง

สแต็กที่เลือกใช้

"ไม่มีแบ็กเอนด์ที่ต้องดูแลเอง" บวกกับ "เร็วทั่วโลก" ชี้ตรงไปที่การโฮสต์แบบสแตติกบน CDN (Content Delivery Network — เครือข่ายกระจายเนื้อหา คือสำเนาไฟล์ของคุณที่เก็บไว้ตามศูนย์ข้อมูลทั่วโลก เพื่อให้ผู้เข้าชมแต่ละคนโหลดจากศูนย์ที่ใกล้ที่สุด) พร้อมฟังก์ชัน เล็ก ๆ หนึ่งตัวสำหรับสิ่งเดียวที่ ต้องการ ความลับ นั่นคือการสร้าง Stripe เราเลือกเว็บไซต์สแตติกบน Cloudflare Pages พร้อม Pages Function เดียว สถาปัตยกรรมทั้งหมดอธิบายได้ในหนึ่งประโยค คือหน้าเว็บสแตติกที่เรียกใช้ฟังก์ชันเดียวที่คุยกับ Stripe และความเรียบง่ายนี่แหละที่ทำให้ดีบักได้ง่ายในภายหลัง

ทั้งระบบมีสามกล่อง และมีเพียงกล่องตรงกลางเท่านั้นที่แตะต้องความลับ:

 ┌─────────────┐  POST /api/checkout  ┌──────────────┐  secret key  ┌────────┐
 │ STATIC PAGE │─────────────────────▶│ PAGES        │─────────────▶│ STRIPE │
 │ (HTML/JS)   │◀─────────────────────┤ FUNCTION     │◀─────────────┤        │
 │ en/ko/zh    │   checkout URL        │ (the only    │  session URL └────────┘
 └─────────────┘                       │  secret-side)│
       served from CDN edge            └──────────────┘

พรอมต์สำคัญ

เราเริ่มแบบกว้าง ๆ แล้วให้ AI เสนอโครงสร้างเอง:

Scaffold a static landing page (plain HTML/CSS/JS, no framework)
with a language toggle for en/ko/zh. Store the copy in a single
JS object keyed by language. Detect the default from
navigator.language. Keep it to one index.html plus one main.js.

จากนั้นเป็นเส้นทางการชำระเงิน ซึ่งเป็นจุดที่ความลับอาศัยอยู่:

Add a Cloudflare Pages Function at /api/checkout that creates a
Stripe Checkout session for a single $29 product and returns the
redirect URL. Read STRIPE_SECRET_KEY from the environment, never
hardcode it. The Buy button should POST to this function and then
window.location to the returned URL.

วลี "never hardcode it" (อย่าฝังค่าตายตัวลงในโค้ด) นั้นคุ้มค่าที่จะพูดออกไปตรง ๆ ถ้าปล่อยให้เป็นไปตามค่าเริ่มต้นของมันเอง AI จะเผลอใส่คีย์ตัวอย่างลงในโค้ดโดยตรงเพื่อให้ตัวอย่าง "ใช้งานได้" และคีย์ตัวอย่างก็มีวิธีกลายเป็นคีย์จริงที่หลุดออกไปพร้อมกับการเปิดตัว การพูดออกมาให้ชัดเจนในพรอมต์เพียงครั้งเดียว ถูกกว่าการมาจับผิดตอนรีวิวโค้ดทีหลังมาก

อุปสรรค

การทดสอบใช้งานจริงครั้งแรกล้มเหลว ปุ่ม Buy ไม่ทำอะไรเลย และคอนโซลของเบราว์เซอร์แสดงข้อผิดพลาด CORS (Cross-Origin Resource Sharing — กฎของเบราว์เซอร์ว่าเว็บไซต์ไหนได้รับอนุญาตให้เรียกเว็บไซต์ไหนได้บ้าง) เราไม่ได้เดา แต่วางข้อความ error ตัวจริงกลับไปให้ AI ดู:

Clicking Buy logs: "Access to fetch at '/api/checkout' blocked
by CORS policy." The function and page are on the same domain.
Here's the function code: [pasted]. What's actually wrong?

AI จับจุดได้ทันที ฟังก์ชันกำลังส่ง Stripe กลับมาเป็น (JavaScript Object Notation — รูปแบบข้อความง่าย ๆ ที่อ่านได้ ซึ่งโปรแกรมใช้แลกเปลี่ยนข้อมูลแบบมีโครงสร้างกัน) แต่ตัว fetch ถูกเรียกก่อนที่ preflight แบบ OPTIONS ของฟังก์ชันจะถูกจัดการ เพราะเราดันดีพลอยหน้าเว็บสแตติกกับฟังก์ชันไปคนละโปรเจกต์ Pages โดยไม่ตั้งใจ ทางแก้ที่แท้จริงคือเรื่องโครงสร้างการดีพลอย ไม่ใช่โค้ด เราย้ายฟังก์ชันเข้าไปอยู่ในโฟลเดอร์ /functions ของโปรเจกต์เดียวกัน แล้ว error ก็หายไป

บทเรียนตรงนี้ควรค่าแก่การครุ่นคิด อาการ ที่เห็นอยู่ในโค้ด (error CORS จาก fetch) แต่ สาเหตุ อยู่ที่โครงสร้างพื้นฐาน (สองโปรเจกต์แทนที่จะเป็นหนึ่ง) ถ้าเราปล่อยให้ AI "แก้ error CORS" โดยไม่วางบริบทการตั้งค่าให้ดู มันคงจะแปะเฮดเดอร์ Access-Control-Allow-Origin เข้าไปแทน ซึ่งเป็นการแก้ที่คอมไพล์ผ่านแต่กลบอาการไว้เฉย ๆ ทิ้งบั๊กตัวจริงไว้เหมือนเดิม วางข้อความ error ตัวจริง พร้อมกับ บริบทรอบข้าง และอย่ารับคำอธิบายแรกที่ฟังดูสมเหตุสมผลถ้ามันไม่ตรงกับสถานการณ์จริงของคุณ

การเปิดตัว

เราเพิ่มคีย์ Stripe ตัวจริงเป็นตัวแปรสภาพแวดล้อมแบบเข้ารหัสในแดชบอร์ดของ Pages (ไม่เคยอยู่ใน repo) ชี้ ( Name System สมุดที่อยู่ของอินเทอร์เน็ตที่แปลงชื่อโดเมนให้กลายเป็นเซิร์ฟเวอร์) ของโดเมนเธอไปที่ Pages และทำการซื้อจริงหนึ่งครั้งด้วยบัตรจริงมูลค่า 29 ดอลลาร์ แล้วคืนเงินทีหลัง เรายังลองคลิกผ่านเส้นทางที่ล้มเหลวโดยตั้งใจ ด้วยบัตรทดสอบที่ถูกปฏิเสธ เพื่อยืนยันว่าเธอไม่ถูกเรียกเก็บเงิน และเห็นข้อความที่สมเหตุสมผลแทนที่จะเป็นหน้าจอว่างเปล่า ขึ้นใช้งานจริงได้ภายในบ่ายวันเดียว เธอขายได้ครั้งแรกในเย็นวันนั้นเอง

อยากได้แบบออฟไลน์?

ดาวน์โหลดหนังสือทั้งเล่มเป็น PDF หรือ EPUB — ฟรี