เมื่อลูกค้าชำระเงิน Stripe จะส่งเหตุการณ์ payment_intent.succeeded ไปยังแบ็กเอนด์ของคุณ และเอนด์พอยต์ควรอัปเดตคำสั่งซื้อเป็นสถานะชำระเงินแล้ว ปัญหาคือ webhook อาจมาถึงแต่ตัวจัดการเกิดข้อผิดพลาดโดยไม่มีใครสังเกต จนกว่าลูกค้าจะแจ้งว่า “ชำระเงินแล้ว แต่บัญชียังแสดงว่ายังไม่ได้ชำระ” ดังนั้น CI ควรมีการทดสอบที่ยืนยันได้ว่าเหตุการณ์มาถึงและถูกจัดการถูกต้องทุกครั้งที่ปล่อยโค้ด
ความท้าทายคือ webhook เป็น HTTP request ขาเข้าจาก Stripe ไม่ใช่ request ที่ชุดทดสอบสร้างเอง เครื่องมือทดสอบ API ส่วนใหญ่จึงไม่สามารถ “รอฟัง” เหตุการณ์จาก Stripe ได้โดยตรง คู่มือนี้ใช้แนวทางที่รองรับโดย Apidog: ให้แอปของคุณบันทึก webhook ลงฐานข้อมูล แล้วใช้ Apidog สอบถามและยืนยันข้อมูลที่บันทึกไว้
หากต้องการพื้นฐานเพิ่มเติม โปรดดู วิธีการทดสอบ webhooks และ เอกสาร webhooks ของ Stripe
ข้อจำกัดที่ต้องออกแบบให้รองรับ
ตามเอกสารของ Apidog ระบุว่า Apidog “ไม่รองรับการรับฟัง webhooks โดยกำเนิด”
กล่าวคือ คุณไม่สามารถชี้ Stripe ไปยัง URL ของ Apidog แล้วรอดู event ที่เข้ามาแบบเรียลไทม์ได้
แนวทางที่ใช้ได้ใน CI คือ:
- ให้แบ็กเอนด์รับและตรวจสอบ Stripe webhook
- บันทึก event ลงฐานข้อมูล
- ให้ Apidog สอบถามข้อมูล event ที่บันทึกไว้
- ยืนยันว่า event และผลลัพธ์ทางธุรกิจถูกต้อง
รูปแบบนี้ทำให้การทดสอบกำหนดผลลัพธ์ได้และทำซ้ำได้ ซึ่งเหมาะกับ CI มากกว่าการรอ event แบบเรียลไทม์
ภาพรวมเวิร์กโฟลว์: บันทึกแล้วสอบถาม
เวิร์กโฟลว์ประกอบด้วย 4 ส่วน:
- สร้าง webhook endpoint ในแบ็กเอนด์
- บันทึกข้อมูล Stripe event ลงตาราง
stripe_event_logs - เชื่อมต่อฐานข้อมูลเข้ากับ environment ของ Apidog
- ใช้
Post-Request Processorเพื่อรัน SQL และตรวจสอบผลลัพธ์
ส่วนรับ webhook และบันทึกข้อมูลเป็นความรับผิดชอบของแอปพลิเคชันคุณ ส่วน Apidog ใช้สำหรับอ่านข้อมูลกลับมาและทำ assertion ในชุดทดสอบ
ขั้นตอนที่ 1: สร้าง endpoint สำหรับรับและบันทึก Stripe webhook
Stripe ต้องเรียก HTTP POST มายัง endpoint ของคุณ ตัวอย่างด้านล่างใช้ Express และ Stripe SDK:
import express from "express";
import Stripe from "stripe";
const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
app.post(
"/webhooks/stripe",
express.raw({ type: "application/json" }),
async (req, res) => {
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
req.headers["stripe-signature"],
endpointSecret
);
} catch (err) {
return res.status(400).send(`Signature check failed: ${err.message}`);
}
// บันทึก event เพื่อให้ชุดทดสอบอ่านกลับมาตรวจสอบได้
await db.query(
`INSERT INTO stripe_event_logs (event_id, type, payload, handled_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (event_id) DO NOTHING`,
[event.id, event.type, JSON.stringify(event.data.object)]
);
if (event.type === "payment_intent.succeeded") {
const intent = event.data.object;
await markOrderPaid(intent.metadata.order_id);
}
res.json({ received: true });
}
);
มีจุดสำคัญ 2 ข้อ:
- ใช้
stripe.webhooks.constructEvent()เพื่อตรวจสอบลายเซ็นก่อนประมวลผลข้อมูลใด ๆ - ใช้
ON CONFLICT (event_id) DO NOTHINGเพื่อรองรับการส่ง event ซ้ำ เพราะ Stripe สามารถ retry webhook เดิมได้
การตรวจสอบลายเซ็นเป็นขั้นตอนด้านความปลอดภัยที่ไม่ควรข้าม ดูรายละเอียดเพิ่มได้ที่ การยืนยันลายเซ็น webhook
ขั้นตอนที่ 2: เชื่อมต่อฐานข้อมูลใน Apidog Environment
ตั้งค่า database connection ใน Apidog สำหรับ environment ที่ CI ใช้งาน เช่น:
- ฐานข้อมูล staging เมื่อทดสอบ staging
- ฐานข้อมูลทดสอบเฉพาะสำหรับ pipeline
- ฐานข้อมูลชั่วคราวสำหรับแต่ละ test run
สิ่งสำคัญคือ environment ของ Apidog ต้องชี้ไปยังฐานข้อมูลเดียวกับที่ webhook endpoint เขียนข้อมูลลงไป
หาก CI ส่ง event ไปยัง staging แต่ Apidog ไปอ่านฐานข้อมูล test คนละชุด การทดสอบจะล้มเหลวแม้ตัว handler ทำงานถูกต้อง
ขั้นตอนที่ 3: ใช้ Post-Request Processor เพื่อสอบถาม event log
Post-Request Processor ของ Apidog ใช้รัน SQL หลังจาก request ใน test scenario ทำงานแล้ว
สำหรับกรณี payment_intent.succeeded ลำดับการทดสอบควรเป็นดังนี้:
- สร้างหรือยืนยัน Payment Intent ใน Stripe test mode หรือส่ง test event ที่กำหนดไว้ไปยัง endpoint
- Stripe ส่ง webhook ไปที่
/webhooks/stripe - Endpoint ตรวจสอบลายเซ็นและบันทึก event ลง
stripe_event_logs - ขั้นตอนถัดไปใน scenario ใช้
Post-Request Processorสอบถาม event ที่บันทึกไว้ - ทำ assertion กับข้อมูลที่ได้จากฐานข้อมูล
ตัวอย่าง SQL:
SELECT event_id, type, payload, handled_at
FROM stripe_event_logs
WHERE type = 'payment_intent.succeeded'
ORDER BY handled_at DESC
LIMIT 1;
อย่างไรก็ตาม อย่าตรวจสอบเพียง “event ล่าสุด” หากมีหลาย test run พร้อมกัน ควรผูกการตรวจสอบกับ event_id ที่ test สร้างขึ้น:
SELECT event_id, type, payload, handled_at
FROM stripe_event_logs
WHERE event_id = :expected_event_id
LIMIT 1;
ตรวจสอบอย่างน้อยว่า:
-
event_idตรงกับ event ที่ test เรียกใช้ -
typeเป็นpayment_intent.succeeded - ข้อมูลใน
payloadมีค่าที่คาดหวัง เช่น จำนวนเงินหรือorder_id -
handled_atมีค่า แสดงว่า handler ได้บันทึก event แล้ว
รับมือกับความหน่วงของ webhook
Webhook เป็น asynchronous flow จึงไม่ควร query ฐานข้อมูลทันทีหลังสร้าง Payment Intent เพราะ event อาจยังส่งมาไม่ถึง
ใช้หนึ่งในวิธีต่อไปนี้:
- หน่วงเวลาสั้น ๆ ก่อน query
- ทำ polling โดย retry query หลายครั้ง
- ล้มเหลวเมื่อเกิน timeout ที่กำหนด
แนวคิดของ polling:
ลอง query event log
├─ พบ event → ทำ assertion ต่อ
└─ ไม่พบ event → รอช่วงสั้น ๆ แล้วลองใหม่
กำหนด retry window ให้เหมาะสมกับ environment ของคุณ เพื่อป้องกัน flaky test ที่เกิดจาก test แข่งกับเวลาส่ง webhook ของ Stripe
ตรวจสอบผลลัพธ์ทางธุรกิจ ไม่ใช่แค่ event log
การตรวจสอบว่า event ถูกบันทึกแล้วมีประโยชน์ แต่ยังไม่เพียงพอสำหรับ flow การชำระเงินจริง
หาก handler เรียก markOrderPaid() ควรสอบถามตาราง orders เพิ่มเติม:
SELECT id, status, paid_at
FROM orders
WHERE id = :order_id;
จากนั้นยืนยันว่า:
status = 'paid'
paid_at IS NOT NULL
การทดสอบนี้พิสูจน์ทั้ง chain:
Stripe event
→ webhook endpoint
→ signature verification
→ event log
→ อัปเดตคำสั่งซื้อ
ไม่ใช่แค่พิสูจน์ว่าได้รับ HTTP request
ทดสอบกรณีล้มเหลวด้วย
อย่าทดสอบเฉพาะ happy path เพิ่มกรณีต่อไปนี้ใน scenario ของคุณ:
- ลายเซ็น
stripe-signatureไม่ถูกต้อง - event type ที่ handler ไม่รองรับ
- event ซ้ำที่มี
event_idเดิม - payload ที่ไม่มี
metadata.order_id - ข้อมูลคำสั่งซื้อไม่พบในฐานข้อมูล
ตัวอย่าง assertion สำหรับลายเซ็นไม่ถูกต้อง:
- Endpoint ตอบกลับ
400 - ไม่มีการเพิ่มแถวใหม่ใน
stripe_event_logs - ไม่มีการเปลี่ยนสถานะคำสั่งซื้อ
สำหรับแนวทางเรื่อง retry และ idempotency ดู แนวทางปฏิบัติที่ดีที่สุดสำหรับ payment webhook
การพัฒนาในเครื่อง: ใช้ Stripe CLI หรือ Ngrok
รูปแบบบันทึกแล้วสอบถามเหมาะกับ CI แต่ระหว่างพัฒนาในเครื่อง Stripe ไม่สามารถเรียก localhost ของคุณได้โดยตรง
ใช้ Stripe CLI เพื่อส่งต่อ event มายัง local server:
stripe listen --forward-to localhost:3000/webhooks/stripe
Stripe CLI จะแสดง webhook signing secret สำหรับใช้เป็น STRIPE_WEBHOOK_SECRET ใน environment ของคุณ
คุณสามารถใช้ Ngrok เพื่อเปิด local port เป็น public URL แล้วลงทะเบียน URL นั้นใน Stripe ได้เช่นกัน
สรุปการเลือกใช้:
| สถานการณ์ | วิธีที่เหมาะสม |
|---|---|
| พัฒนา handler บนเครื่อง | Stripe CLI หรือ Ngrok |
| ตรวจสอบใน CI/CD | บันทึก event ลงฐานข้อมูล + Post-Request Processor |
| ตรวจสอบผลธุรกิจ | Query ตาราง orders เพิ่มเติม |
อย่าสับสนกับ Webhook แบบดั้งเดิมของ Apidog
Apidog มีฟีเจอร์ชื่อ Webhook แต่ฟีเจอร์นี้ใช้สำหรับกำหนดและจัดทำเอกสาร webhook ขาออก ของระบบคุณ
กล่าวคือ ใช้เมื่อระบบของคุณต้องเรียก HTTP endpoint ภายนอกเมื่อเกิด event เช่น:
- แจ้ง partner เมื่อคำสั่งซื้อเปลี่ยนสถานะ
- แจ้งผลการประมวลผลงาน asynchronous
- ส่ง event ไปยังระบบภายนอก
ไม่ใช่ฟีเจอร์สำหรับรับ Stripe webhook ขาเข้า
หากต้องการสร้างเอกสาร webhook ขาออกใน Apidog:
- คลิกไอคอน
+ในแถบด้านข้าง - เลือก
New Other Protocol APIs - เลือก
Webhook - ระบุ
Request Method,Webhook Name,Debug URLและข้อมูล request - คลิก
Save
Debug URL ใช้สำหรับทดสอบเท่านั้น และจะไม่แสดงในเอกสารที่เผยแพร่หรือ OpenAPI export
อ่านเพิ่มเติมได้ที่ webhooks ในการออกแบบ API
รัน scenario ใน CI ด้วย Apidog CLI
เมื่อตั้งค่า scenario และ assertion เรียบร้อยแล้ว ให้รันแบบ headless ด้วย Apidog CLI
ติดตั้ง CLI:
npm install -g apidog-cli
ยืนยันตัวตนด้วย access token:
apidog login --with-token <YOUR_ACCESS_TOKEN>
จากนั้นรัน test scenario:
apidog run \
--access-token $APIDOG_ACCESS_TOKEN \
-t <SCENARIO_ID> \
-e <ENV_ID> \
-r cli
พารามิเตอร์หลัก:
-
-tคือรหัส test scenario -
-eคือรหัส environment -
-rคือ reporter
หากต้องการผลลัพธ์ใน console และรายงาน HTML สำหรับ CI artifact:
apidog run \
--access-token $APIDOG_ACCESS_TOKEN \
-t <SCENARIO_ID> \
-e <ENV_ID> \
-r html,cli
เมื่อ assertion ล้มเหลว คำสั่งควรคืนค่า exit code ที่ไม่ใช่ศูนย์ เพื่อให้ pipeline หยุดการ merge หรือ deployment ได้
ดูรายละเอียดการตั้งค่าได้ที่:
คำถามที่พบบ่อย
Apidog รับ Stripe webhook โดยตรงได้หรือไม่?
ไม่ได้ Apidog ไม่รองรับการรับฟัง webhook ขาเข้าโดยกำเนิด ให้แอปของคุณรับและบันทึก event ลงฐานข้อมูล แล้วใช้ Post-Request Processor อ่านกลับมาตรวจสอบ
การยืนยันข้อมูลเกิดขึ้นที่ไหน?
เกิดใน Post-Request Processor ของ test scenario โดย processor จะเชื่อมต่อฐานข้อมูล รัน SQL ดึง event log และตรวจสอบกับค่าที่คาดหวัง
ต้องใช้แผนแบบเสียเงินหรือไม่?
เอกสารสำหรับเวิร์กโฟลว์นี้ไม่ได้ระบุข้อจำกัดของแผนโดยตรง ควรตรวจสอบรายละเอียดล่าสุดจากหน้าราคา หรือดาวน์โหลด Apidogเพื่อลองตั้งค่า project และ environment ด้วยตัวเอง
ควรจัดการ webhook delay อย่างไร?
เพิ่ม delay สั้น ๆ หรือใช้ polling พร้อม retry ก่อน query ฐานข้อมูล เพื่อหลีกเลี่ยงการทดสอบที่ล้มเหลวเพราะ Stripe ยังส่ง event มาไม่ถึง
ฟีเจอร์ Webhook ของ Apidog ช่วยรับ Stripe event ได้หรือไม่?
ไม่ได้ ฟีเจอร์ Webhook ดั้งเดิมของ Apidog ใช้กำหนดและจัดทำเอกสาร webhook ขาออกของระบบคุณ ไม่ใช่ตัวรับ event ขาเข้าจาก Stripe
สรุป
คุณไม่สามารถชี้ Stripe ไปที่ Apidog เพื่อดักจับ event สดได้โดยตรง แต่สามารถสร้างการตรวจสอบที่เชื่อถือได้ใน CI ด้วยรูปแบบนี้:
- รับ Stripe webhook ใน endpoint ของคุณ
- ตรวจสอบลายเซ็น
- บันทึก event ลง
stripe_event_logs - อัปเดตสถานะทางธุรกิจ เช่น
orders.status = 'paid' - ใช้ Apidog
Post-Request Processorสอบถามและ assert ข้อมูล - รัน scenario ผ่าน
apidog runใน CI
วิธีนี้ทำให้ทุก pull request หรือ deployment ตรวจสอบได้ว่า payment_intent.succeeded ไม่ได้เพียงมาถึงระบบ แต่ทำให้คำสั่งซื้อเปลี่ยนเป็นสถานะชำระเงินแล้วจริง ๆ
Top comments (0)