DEV Community

Cover image for วิธีการดักจับและตรวจสอบ Stripe Webhooks ใน CI ด้วย Apidog
Thanawat Wongchai
Thanawat Wongchai

Posted on • Originally published at apidog.com

วิธีการดักจับและตรวจสอบ Stripe Webhooks ใน CI ด้วย Apidog

เมื่อลูกค้าชำระเงิน Stripe จะส่งเหตุการณ์ payment_intent.succeeded ไปยังแบ็กเอนด์ของคุณ และเอนด์พอยต์ควรอัปเดตคำสั่งซื้อเป็นสถานะชำระเงินแล้ว ปัญหาคือ webhook อาจมาถึงแต่ตัวจัดการเกิดข้อผิดพลาดโดยไม่มีใครสังเกต จนกว่าลูกค้าจะแจ้งว่า “ชำระเงินแล้ว แต่บัญชียังแสดงว่ายังไม่ได้ชำระ” ดังนั้น CI ควรมีการทดสอบที่ยืนยันได้ว่าเหตุการณ์มาถึงและถูกจัดการถูกต้องทุกครั้งที่ปล่อยโค้ด

ลองใช้ Apidog วันนี้

ความท้าทายคือ webhook เป็น HTTP request ขาเข้าจาก Stripe ไม่ใช่ request ที่ชุดทดสอบสร้างเอง เครื่องมือทดสอบ API ส่วนใหญ่จึงไม่สามารถ “รอฟัง” เหตุการณ์จาก Stripe ได้โดยตรง คู่มือนี้ใช้แนวทางที่รองรับโดย Apidog: ให้แอปของคุณบันทึก webhook ลงฐานข้อมูล แล้วใช้ Apidog สอบถามและยืนยันข้อมูลที่บันทึกไว้

หากต้องการพื้นฐานเพิ่มเติม โปรดดู วิธีการทดสอบ webhooks และ เอกสาร webhooks ของ Stripe

ข้อจำกัดที่ต้องออกแบบให้รองรับ

ตามเอกสารของ Apidog ระบุว่า Apidog “ไม่รองรับการรับฟัง webhooks โดยกำเนิด”

กล่าวคือ คุณไม่สามารถชี้ Stripe ไปยัง URL ของ Apidog แล้วรอดู event ที่เข้ามาแบบเรียลไทม์ได้

แนวทางที่ใช้ได้ใน CI คือ:

  1. ให้แบ็กเอนด์รับและตรวจสอบ Stripe webhook
  2. บันทึก event ลงฐานข้อมูล
  3. ให้ Apidog สอบถามข้อมูล event ที่บันทึกไว้
  4. ยืนยันว่า event และผลลัพธ์ทางธุรกิจถูกต้อง

รูปแบบนี้ทำให้การทดสอบกำหนดผลลัพธ์ได้และทำซ้ำได้ ซึ่งเหมาะกับ CI มากกว่าการรอ event แบบเรียลไทม์

ภาพรวมเวิร์กโฟลว์: บันทึกแล้วสอบถาม

เวิร์กโฟลว์ประกอบด้วย 4 ส่วน:

  1. สร้าง webhook endpoint ในแบ็กเอนด์
  2. บันทึกข้อมูล Stripe event ลงตาราง stripe_event_logs
  3. เชื่อมต่อฐานข้อมูลเข้ากับ environment ของ Apidog
  4. ใช้ 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 });
  }
);
Enter fullscreen mode Exit fullscreen mode

มีจุดสำคัญ 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 ลำดับการทดสอบควรเป็นดังนี้:

  1. สร้างหรือยืนยัน Payment Intent ใน Stripe test mode หรือส่ง test event ที่กำหนดไว้ไปยัง endpoint
  2. Stripe ส่ง webhook ไปที่ /webhooks/stripe
  3. Endpoint ตรวจสอบลายเซ็นและบันทึก event ลง stripe_event_logs
  4. ขั้นตอนถัดไปใน scenario ใช้ Post-Request Processor สอบถาม event ที่บันทึกไว้
  5. ทำ 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;
Enter fullscreen mode Exit fullscreen mode

อย่างไรก็ตาม อย่าตรวจสอบเพียง “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;
Enter fullscreen mode Exit fullscreen mode

ตรวจสอบอย่างน้อยว่า:

  • 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 → รอช่วงสั้น ๆ แล้วลองใหม่
Enter fullscreen mode Exit fullscreen mode

กำหนด 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;
Enter fullscreen mode Exit fullscreen mode

จากนั้นยืนยันว่า:

status = 'paid'
paid_at IS NOT NULL
Enter fullscreen mode Exit fullscreen mode

การทดสอบนี้พิสูจน์ทั้ง chain:

Stripe event
→ webhook endpoint
→ signature verification
→ event log
→ อัปเดตคำสั่งซื้อ
Enter fullscreen mode Exit fullscreen mode

ไม่ใช่แค่พิสูจน์ว่าได้รับ 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
Enter fullscreen mode Exit fullscreen mode

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:

  1. คลิกไอคอน + ในแถบด้านข้าง
  2. เลือก New Other Protocol APIs
  3. เลือก Webhook
  4. ระบุ Request Method, Webhook Name, Debug URL และข้อมูล request
  5. คลิก Save

Debug URL ใช้สำหรับทดสอบเท่านั้น และจะไม่แสดงในเอกสารที่เผยแพร่หรือ OpenAPI export

อ่านเพิ่มเติมได้ที่ webhooks ในการออกแบบ API

รัน scenario ใน CI ด้วย Apidog CLI

เมื่อตั้งค่า scenario และ assertion เรียบร้อยแล้ว ให้รันแบบ headless ด้วย Apidog CLI

ติดตั้ง CLI:

npm install -g apidog-cli
Enter fullscreen mode Exit fullscreen mode

ยืนยันตัวตนด้วย access token:

apidog login --with-token <YOUR_ACCESS_TOKEN>
Enter fullscreen mode Exit fullscreen mode

จากนั้นรัน test scenario:

apidog run \
  --access-token $APIDOG_ACCESS_TOKEN \
  -t <SCENARIO_ID> \
  -e <ENV_ID> \
  -r cli
Enter fullscreen mode Exit fullscreen mode

พารามิเตอร์หลัก:

  • -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
Enter fullscreen mode Exit fullscreen mode

เมื่อ 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 ด้วยรูปแบบนี้:

  1. รับ Stripe webhook ใน endpoint ของคุณ
  2. ตรวจสอบลายเซ็น
  3. บันทึก event ลง stripe_event_logs
  4. อัปเดตสถานะทางธุรกิจ เช่น orders.status = 'paid'
  5. ใช้ Apidog Post-Request Processor สอบถามและ assert ข้อมูล
  6. รัน scenario ผ่าน apidog run ใน CI

วิธีนี้ทำให้ทุก pull request หรือ deployment ตรวจสอบได้ว่า payment_intent.succeeded ไม่ได้เพียงมาถึงระบบ แต่ทำให้คำสั่งซื้อเปลี่ยนเป็นสถานะชำระเงินแล้วจริง ๆ

Top comments (0)