03 พ.ค. 2568·อ่าน 2 นาที

การเรียกเก็บตามการใช้งานกับ Stripe: โมเดลข้อมูลเชิงปฏิบัติ

การเรียกเก็บตามการใช้งานกับ Stripe ต้องการการเก็บเหตุการณ์ที่สะอาดและการกระทบยอด เรียนรู้สคีมาง่าย ๆ, โฟลว์ webhook, การ backfill และการแก้ปัญหาการนับซ้ำ

การเรียกเก็บตามการใช้งานกับ Stripe: โมเดลข้อมูลเชิงปฏิบัติ

สิ่งที่คุณกำลังสร้างจริง ๆ (และทำไมมันถึงพัง)

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

ความล้มเหลวมักไม่เกิดขึ้นที่หน้าเช็คเอาต์หรือแดชบอร์ด แต่เกิดในโมเดลข้อมูลการวัด หากคุณตอบไม่ได้อย่างมั่นใจว่า “เหตุการณ์การใช้งานใดบ้างที่ถูกนับในใบแจ้งหนี้นี้ และเพราะเหตุใด?” คุณจะส่งผลให้เรียกเก็บเกิน เรียกเก็บน้อย หรือเสียความเชื่อมั่นในที่สุด

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

Stripe เยี่ยมเรื่องราคา ใบแจ้งหนี้ ภาษี และการเก็บเงิน แต่ Stripe ไม่รู้การใช้งานดิบของผลิตภัณฑ์คุณเว้นแต่คุณจะส่งไป นั่นบังคับให้ต้องตัดสินใจแหล่งความจริง: Stripe เป็นบัญชีแยกประเภทหรือฐานข้อมูลของคุณเป็นบัญชีแยกประเภทที่ Stripe แสดงให้เห็น?

สำหรับทีมส่วนใหญ่ การแยกที่ปลอดภัยคือ:

  • ฐานข้อมูลของคุณเป็นแหล่งความจริงสำหรับเหตุการณ์การใช้งานดิบและวงจรชีวิตของมัน
  • Stripe เป็นแหล่งความจริงสำหรับสิ่งที่ถูกเรียกเก็บจริงและชำระแล้ว

ตัวอย่าง: คุณติดตาม “การเรียก API” แต่ละครั้งสร้างเหตุการณ์การใช้งานที่มีคีย์เฉพาะคงที่ เมื่อถึงเวลาสร้างใบแจ้งหนี้ คุณรวมเฉพาะเหตุการณ์ที่มีสิทธิ์และยังไม่ถูกเรียกเก็บแล้วสร้างหรืออัปเดต invoice item ใน Stripe หากการรับส่งซ้ำหรือ webhook มาถึงสองครั้ง กฎ idempotency จะทำให้การซ้ำไม่เป็นอันตราย

การตัดสินใจก่อนออกแบบตาราง

ก่อนสร้างตาราง ให้ล็อกดาวน์คำนิยามที่ตัดสินว่าเมื่อไหร่การเรียกเก็บจะยังอธิบายได้ในภายหลัง ข้อบกพร่องในใบแจ้งหนี้ส่วนใหญ่เกิดจากกฎที่ไม่ชัดเจน ไม่ใช่ SQL ที่เขียนไม่ดี

เริ่มจากหน่วยที่คุณคิดเงิน เลือกสิ่งที่วัดง่ายและถกเถียงได้ยาก “การเรียก API” จะซับซ้อนกับการรีไทร คำขอแบบแบตช์ และความล้มเหลว “นาที” ยุ่งกับการทับซ้อน “GB” ต้องชัดเจนเรื่องฐาน (GB vs GiB) และวิธีวัด (เฉลี่ย vs พีค)

ถัดไป ให้กำหนดขอบเขต ระบบของคุณต้องรู้ชัดว่าเหตุการณ์อยู่ในหน้าต่างเวลาใด การใช้งานถูกนับเป็นรายชั่วโมง รายวัน รอบการเรียกเก็บ หรือการกระทำของลูกค้า หากลูกค้าอัปเกรดกลางเดือน คุณจะแบ่งหน้าต่างหรือใช้ราคารายเดือนทั้งหมด? ตัวเลือกเหล่านี้กำหนดวิธีการจัดกลุ่มเหตุการณ์และวิธีอธิบายยอดรวม

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

ชุดข้อที่ไม่เจรจาน้อย ๆ ช่วยให้สคีมาซื่อสัตย์:

  • การติดตามได้: ทุกหน่วยที่ถูกเรียกเก็บผูกกลับไปยังเหตุการณ์ที่เก็บไว้ได้
  • การตรวจสอบได้: คุณตอบได้ว่า “ทำไมถึงถูกเรียกเก็บนี้?” ได้เป็นเดือน
  • การย้อนกลับได้: ข้อผิดพลาดถูกแก้ด้วยการปรับปรุงที่ชัดเจน
  • Idempotency: อินพุตแบบเดียวกันไม่สามารถถูกนับสองครั้ง
  • ความเป็นเจ้าของชัดเจน: ระบบหนึ่งเป็นเจ้าของแต่ละข้อเท็จจริง (การใช้งาน ราคา การออกใบแจ้งหนี้)

ตัวอย่าง: หากคุณคิดเงินตาม “ข้อความที่ส่ง” ให้ตัดสินใจว่าการรีไทรนับหรือไม่ การส่งล้มเหลวนับหรือไม่ และเวลาของฝั่งใดมีน้ำหนัก (เวลาไคลเอนต์ vs เวลาเซิร์ฟเวอร์) เขียนมันลงแล้วเข้ารหัสลงในฟิลด์เหตุการณ์และการตรวจสอบ ไม่ใช่เก็บไว้ในความทรงจำของใคร

แบบจำลองข้อมูลง่าย ๆ สำหรับเหตุการณ์การใช้งาน

การเรียกเก็บตามการใช้งานง่ายที่สุดเมื่อคุณปฏิบัติต่อการใช้งานเหมือนการบัญชี: ข้อเท็จจริงดิบเป็น append-only และผลรวมเป็นข้อมูลอนุพันธ์ การเลือกนี้ป้องกันข้อพิพาทส่วนใหญ่เพราะคุณสามารถอธิบายเสมอว่ายอดมาจากไหน

จุดเริ่มต้นที่ใช้งานได้จริงใช้ห้าตารางหลัก (ชื่ออาจต่างกัน):

  • customer: id ลูกค้าภายใน, Stripe customer id, สถานะ, เมทาดาต้าพื้นฐาน
  • subscription: id การสมัครภายใน, Stripe subscription id, แผน/ราคาที่คาดไว้, timestamps เริ่ม/สิ้นสุด
  • meter: สิ่งที่คุณวัด (การเรียก API, ที่นั่ง, GB-hours) ใส่คีย์ meter คงที่ หน่วย และวิธีการรวม (sum, max, unique)
  • usage_event: แถวหนึ่งต่อการกระทำที่วัดได้ เก็บ customer_id, subscription_id (ถ้ารู้), meter_id, quantity, occurred_at (เมื่อเกิด), received_at (เมื่อรับเข้า), source (app, batch import, partner), และคีย์ภายนอกคงที่สำหรับ dedupe
  • usage_aggregate: ผลรวมที่ได้โดยทั่วไปแยกตาม customer + meter + time bucket (วันหรือชั่วโมง) และรอบการเรียกเก็บ เก็บปริมาณรวมและเวอร์ชันหรือ last_event_received_at เพื่อรองรับการคำนวณซ้ำ

เก็บ usage_event แบบ immutable หากคุณค้นพบข้อผิดพลาด ให้เขียนเหตุการณ์ชดเชย (เช่น -3 ที่นั่งสำหรับการยกเลิก) แทนการแก้ประวัติ

เก็บเหตุการณ์ดิบเพื่อการตรวจสอบและข้อพิพาท ถ้าเก็บไม่ไหวตลอดไป ให้เก็บอย่างน้อยนานพอเท่ากับหน้าต่างย้อนกลับการเรียกเก็บบวกหน้าต่างการคืนเงิน/ข้อพิพาท

เก็บผลรวมแยกไว้ ผลรวมเร็วสำหรับใบแจ้งหนี้และแดชบอร์ด แต่ทิ้งได้ คุณควรสามารถสร้าง usage_aggregate ใหม่จาก usage_event ได้ทุกเมื่อ รวมถึงหลังการ backfill

Idempotency และสถานะวงจรชีวิตของเหตุการณ์

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

ให้เหตุการณ์การใช้งานทุกเหตุการณ์มี event_id ที่คงที่และกำหนดได้ และบังคับความไม่ซ้ำบนมัน อย่าอาศัย id ออโต้อินเครเมนต์เป็นตัวระบุเดียว event_id ที่ดีได้มาจากการกระทำทางธุรกิจ เช่น customer_id + meter + source_record_id (หรือ customer_id + meter + timestamp_bucket + sequence) หากการกระทำเดียวกันถูกส่งอีกครั้ง มันจะให้ event_id เดิมและการแทรกจะกลายเป็น no-op ที่ปลอดภัย

Idempotency ต้องครอบคลุมทุกเส้นทางการรับเข้า ไม่ใช่แค่ public API ของคุณ เรียกใช้กฎหนึ่งข้อ: ถ้าอินพุตอาจถูกรีไทร มันต้องมีคีย์ idempotency เก็บในฐานข้อมูลของคุณและตรวจสอบก่อนที่ยอดรวมจะเปลี่ยน

โมเดลสถานะวงจรง่าย ๆ ทำให้การรีไทรปลอดภัยและการสนับสนองง่ายขึ้น เก็บให้ชัดเจนและบันทึกเหตุผลเมื่อบางอย่างล้มเหลว:

  • received: เก็บแล้ว ยังไม่ได้ตรวจสอบ
  • validated: ผ่านสคีมา ลูกค้า meter และกฎหน้าต่างเวลา
  • posted: ถูกนับเข้าผลรวมรอบการเรียกเก็บ
  • rejected: ถูกละเลยถาวร (พร้อมรหัสเหตุผล)

ตัวอย่าง: worker ของคุณล้มหลังจาก validate แต่ก่อน post เมื่อรีไทร มันพบ event_id เดิมในสถานะ validated แล้วดำเนินการต่อไปสู่ posted โดยไม่สร้างเหตุการณ์ที่สอง

สำหรับ webhook ของ Stripe ใช้แบบเดียวกัน: เก็บ event.id ของ Stripe และทำเครื่องหมายว่า processed เพียงครั้งเดียวเพื่อให้การส่งซ้ำไม่เป็นอันตราย

ขั้นตอนทีละข้อ: การรับเหตุการณ์การวัดตั้งแต่ต้นจนจบ

เพิ่มสถานะวงจรชีวิตเหตุการณ์
ใช้งานสถานะ received-validated-posted เพื่อให้ทีมซัพพอร์ตอธิบายแต่ละบรรทัดในใบแจ้งหนี้ได้
สร้าง Workflow

ปฏิบัติต่อเหตุการณ์การวัดทุกเหตุการณ์เหมือนเงิน: ตรวจสอบ เก็บต้นฉบับ แล้วสกัดผลรวมจากแหล่งความจริง วิธีนี้ทำให้การเรียกเก็บคาดเดาได้เมื่อระบบรีไทรหรือส่งข้อมูลมาช้า

กระบวนการรับข้อมูลที่เชื่อถือได้

ตรวจสอบเหตุการณ์แต่ละรายการก่อนเปลี่ยนยอดรวม ขั้นต่ำต้องการ: ตัวระบุลูกค้าที่เสถียร ชื่อ meter ปริมาณตัวเลข timestamp และคีย์เหตุการณ์ไม่ซ้ำสำหรับ idempotency

เขียนเหตุการณ์ดิบก่อน แม้ว่าจะวางแผนจะรวมยอดภายหลัง บันทึกดิบนี้คือสิ่งที่คุณจะ reprocess, ตรวจสอบ, และใช้แก้ไขข้อผิดพลาดโดยไม่ต้องเดา

กระบวนการที่เชื่อถือได้มีลักษณะดังนี้:

  • รับเหตุการณ์ ตรวจสอบฟิลด์ที่ต้องการ และปรับหน่วยให้เป็นมาตรฐาน (เช่น วินาที vs นาที)
  • แทรกแถวเหตุการณ์การใช้งานดิบโดยใช้ event key เป็นข้อจำกัดไม่ซ้ำ
  • รวมเข้า bucket (รายวันหรือรอบการเรียกเก็บ) โดยนำปริมาณของเหตุการณ์มาบวก
  • หากรายงานการใช้งานไปยัง Stripe ให้บันทึกสิ่งที่คุณส่ง (meter, quantity, period, และตัวระบุการตอบกลับของ Stripe)
  • บันทึกความผิดปกติ (เหตุการณ์ที่ถูกปฏิเสธ การแปลงหน่วย การมาช้า) เพื่อการตรวจสอบ

ทำให้การรวมทำซ้ำได้ แนวทางทั่วไปคือ: แทรกเหตุการณ์ดิบในธุรกรรมเดียว แล้ว enqueue งานเพื่ออัปเดตบัคเก็ต หากงานรันสองครั้ง มันควรตรวจพบว่าเหตุการณ์ดิบถูกใช้แล้ว

เมื่อมีลูกค้าถามว่าทำไมถูกเรียกเก็บ 12,430 การเรียก API คุณควรแสดงชุดเหตุการณ์ดิบที่รวมในหน้าต่างการเรียกเก็บนั้นได้อย่างชัดเจน

กระทบยอด webhook ของ Stripe กับฐานข้อมูลของคุณ

หลีกเลี่ยงการล็อกอินระยะยาว
สร้างแบบไม่ล็อกอินระยะยาว: สร้างด้วย no-code แล้วส่งออกซอร์สโค้ดเพื่อโฮสต์เอง
สร้างโค้ด

Webhook เป็นใบเสร็จสำหรับสิ่งที่ Stripe ทำจริง แอปของคุณอาจสร้างร่างและส่งการใช้งาน แต่สถานะใบแจ้งหนี้กลายเป็นของจริงเมื่อ Stripe แจ้ง

ทีมส่วนใหญ่โฟกัสที่ webhook ประเภทเล็ก ๆ ที่มีผลต่อผลลัพธ์การเรียกเก็บ:

  • invoice.created, invoice.finalized, invoice.paid, invoice.payment_failed
  • customer.subscription.created, customer.subscription.updated, customer.subscription.deleted
  • checkout.session.completed (ถ้าคุณเริ่มสมัครผ่าน Checkout)

เก็บ webhook ทุกอันที่คุณรับไว้ เก็บเพย์โหลดดิบและสิ่งที่คุณสังเกตตอนรับ: Stripe event.id, event.created, ผลการตรวจสอบลายเซ็นของคุณ, และ timestamp ที่เซิร์ฟเวอร์คุณรับมา ประวัตินั้นสำคัญเมื่อดีบักความไม่ตรงกันหรือเมื่อมีคำถามว่า “ทำไมฉันถูกเรียกเก็บ?”

รูปแบบการกระทบยอดที่แข็งแกร่งและ idempotent มีลักษณะดังนี้:

  1. แทรก webhook ลงตาราง stripe_webhook_events โดยมีข้อจำกัดไม่ซ้ำบน event_id.
  2. หากการแทรกล้มเหลว แสดงว่าเป็นการรีไทร หยุดการทำงาน
  3. ตรวจสอบลายเซ็นและบันทึกผลผ่าน/ไม่ผ่าน
  4. ประมวลผลเหตุการณ์โดยหาเรคอร์ดภายในของคุณโดยใช้ Stripe IDs (customer, subscription, invoice)
  5. ใช้การเปลี่ยนแปลงสถานะเฉพาะเมื่อมันก้าวหน้า

การส่งนอกลำดับเป็นเรื่องปกติ ใช้กฎ “สถานะมากสุดชนะ” พร้อม timestamp: อย่าย้ายเรคอร์ดถอยหลัง

ตัวอย่าง: คุณได้รับ invoice.paid สำหรับ invoice in_123 แต่แถว invoice ภายในของคุณยังไม่มีเลย ให้สร้างแถวที่ทำเครื่องหมายว่า “เห็นมาจาก Stripe” แล้วแนบเข้าบัญชีที่ถูกต้องทีหลัง วิธีนี้ทำให้บัญชีของคุณสอดคล้องโดยไม่ประมวลผลซ้ำ

จากยอดการใช้งานสู่บรรทัดสินค้าในใบแจ้งหนี้

การเปลี่ยนการใช้งานดิบเป็นบรรทัดในใบแจ้งหนี้เกี่ยวกับเวลาและขอบเขตมากกว่า ตัดสินใจว่าคุณต้องการยอดแบบเรียลไทม์ (แดชบอร์ด การแจ้งเตือนค่าใช้จ่าย) หรือเฉพาะเวลาสร้างใบแจ้งหนี้ ทีมหลายทีมทำทั้งสองอย่าง: เขียนเหตุการณ์อย่างต่อเนื่องและคำนวณยอดพร้อมใช้ใบแจ้งหนี้ในงานที่กำหนดเวลา

จัดหน้าต่างการใช้งานให้ตรงกับรอบการเรียกเก็บของ Stripe อย่าเดาเดือนปฏิทิน ใช้ subscription item’s current billing period start และ end แล้วรวมเฉพาะเหตุการณ์ที่มี timestamp อยู่ในหน้าต่างนั้น เก็บ timestamp เป็น UTC และทำให้หน้าต่างการเรียกเก็บเป็น UTC ด้วย

เก็บประวัติไว้แบบ immutable หากพบข้อผิดพลาดทีหลัง อย่าแก้เหตุการณ์เก่า ให้สร้างบันทึกการปรับปรุงที่ชี้ไปยังหน้าต่างเดิมและเพิ่มหรือลดปริมาณ วิธีนี้ตรวจสอบได้ง่ายกว่าและอธิบายได้ง่ายกว่า

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

กระบวนการปฏิบัติ:

  • ดึงหน้าต่างใบแจ้งหนี้จาก Stripe period start และ end
  • รวมเหตุการณ์การใช้งานที่มีสิทธิ์เป็นยอดรวมสำหรับหน้าต่างและราคานั้น
  • สร้าง invoice line items จากยอดรวมการใช้งานและการปรับปรุงที่มี
  • เก็บ calculation run id เพื่อให้สามารถสร้างตัวเลขซ้ำได้ในภายหลัง

Backfill และข้อมูลมาช้าตรงไปโดยไม่ทำลายความเชื่อมั่น

ทดสอบการเรียกเก็บก่อนขึ้นผลิตจริง
จำลองตรรกะการเรียกเก็บแบบใช้งานของ Stripe ตั้งแต่ต้นจนจบโดยใช้โมเดลข้อมูลและกระบวนการแบบภาพ
ลองเดโม

ข้อมูลการใช้งานมาช้าปกติ อุปกรณ์ออฟไลน์ งานแบตช์ล่าช้า พาร์ทเนอร์ส่งไฟล์ซ้ำ ไฟล์ล็อกถูก replay หลัง outage กุญแจคือปฏิบัติต่อการ backfill เป็นงานแก้ไข ไม่ใช่วิธีทำให้ตัวเลขพอดี

ระบุอย่างชัดเจนว่าการ backfill มาจากที่ไหน (ล็อกแอป, การส่งออกคลังข้อมูล, ระบบพาร์ทเนอร์) บันทึกแหล่งที่มาบนเหตุการณ์ทุกอันเพื่อให้คุณอธิบายว่าทำไมมันมาช้า

เมื่อ backfill ให้เก็บสอง timestamp: เมื่อการใช้งานเกิดขึ้น (เวลาที่คุณอยากคิดเงิน) และเมื่อคุณ ingest มัน ทำเครื่องหมายเหตุการณ์ว่าเป็น backfilled แต่ไม่เขียนทับประวัติ

แนะนำให้สร้างยอดจากเหตุการณ์ดิบใหม่มากกว่าการนำเดลต้าไปเพิ่มกับตารางผลรวมปัจจุบัน การ replay คือวิธีกู้จากบั๊กโดยไม่ต้องเดา หาก pipeline ของคุณ idempotent คุณสามารถรันซ้ำวัน สัปดาห์ หรือรอบการเรียกเก็บเต็มและได้ยอดเดียวกัน

เมื่อใบแจ้งหนี้มีอยู่แล้ว การแก้ไขควรตามนโยบายชัดเจน:

  • หากใบแจ้งหนี้ยังไม่สรุป ให้คำนวณใหม่และอัปเดตก่อนการสรุป
  • หากสรุปแล้วและเรียกเก็บน้อย ให้ออกใบแจ้งหนี้เพิ่มเติม (หรือเพิ่ม invoice item) พร้อมคำอธิบายชัดเจน
  • หากสรุปแล้วและเรียกเก็บเกิน ให้ออก credit note อ้างอิงใบแจ้งหนี้เดิม
  • อย่าย้ายการใช้งานไปยังช่วงเวลาอื่นเพื่อตัดปัญหา
  • เก็บเหตุผลสั้น ๆ สำหรับการแก้ไข (พาร์ทเนอร์ส่งซ้ำ, การส่งล็อกล่าช้า, แก้บั๊ก)

ตัวอย่าง: พาร์ทเนอร์ส่งเหตุการณ์ที่หายไปของ Jan 28-29 มาใน Feb 3 คุณแทรกเหตุการณ์ด้วย occurred_at ในมกราคม ingested_at ในกุมภาพันธ์ และแหล่ง backfill เป็น “partner” ใบแจ้งหนี้มกราคมถูกจ่ายแล้ว ดังนั้นคุณสร้างใบแจ้งหนี้เล็ก ๆ สำหรับหน่วยที่หายไป พร้อมเหตุผลเก็บไว้กับบันทึกการกระทบยอด

ข้อผิดพลาดทั่วไปที่ทำให้เกิดการนับซ้ำ

การนับซ้ำเกิดเมื่อระบบปฏิบัติว่า “ข้อความมาถึง” เป็น “การกระทำเกิดขึ้น” ด้วยการรีไทร webhook ที่มาช้า และ backfill คุณต้องแยกการกระทำของลูกค้าออกจากการประมวลผลของคุณ

ต้นเหตุที่พบบ่อย:

  • การรีไทรถูกปฏิบัติเหมือนการใช้งานใหม่ หากแต่ละเหตุการณ์ไม่มี action id ที่คงที่ (request_id, message_id) และฐานข้อมูลไม่บังคับความไม่ซ้ำ คุณจะนับสองครั้ง
  • เวลาเหตุการณ์ผสมกับเวลาในการประมวลผล รายงานตามเวลา ingest แทน occurred ทำให้เหตุการณ์มาช้าตกในช่วงเวลาผิด แล้วถูกนับอีกครั้งระหว่างการ replay
  • เหตุการณ์ดิบถูกลบหรือเขียนทับ หากเก็บแค่ผลรวมวิ่ง คุณพิสูจน์ไม่ได้ว่าเกิดอะไรขึ้น และการประมวลผลใหม่อาจเพิ่มยอด
  • สมมติลำดับ webhook Webhook อาจซ้ำ มานอกลำดับ หรือแสดงสถานะบางส่วน กระทบยอดตาม Stripe object IDs และเก็บการ์ดป้องกันว่า “ประมวลผลแล้ว”
  • การยกเลิก การคืนเงิน และเครดิตไม่ได้ถูกจำลองอย่างชัดเจน หากคุณแค่เพิ่มการใช้งานและไม่เคยบันทึกการปรับลบ คุณจะจบลงด้วยการ “แก้” ยอดด้วยการนำเข้าและนับซ้ำ

ตัวอย่าง: คุณล็อก “10 การเรียก API” แล้วต่อมาจ่ายเครดิต 2 การเรียกเนื่องจาก outage หากคุณ backfill โดยการส่งทั้งวันอีกครั้งแล้วก็ใช้เครดิตด้วย ลูกค้าอาจเห็น 18 การเรียก (10 + 10 - 2) แทนที่จะเป็น 8

เช็คลิสต์ด่วนก่อนใช้งานจริง

สร้างหน้าตรวจสอบการเรียกเก็บเงิน
ให้ทีมของคุณมีมุมมองตรวจสอบเพื่อให้ตอบคำถาม “ทำไมถึงถูกเรียกเก็บ?” ได้เร็วขึ้น
สร้าง Dashboard

ก่อนเปิดใช้งานการเรียกเก็บตามการใช้งานให้ลูกค้าจริง ทำการตรวจสอบครั้งสุดท้ายในพื้นฐานที่ป้องกันบั๊กการเรียกเก็บที่มีค่าใช้จ่ายสูง ข้อผิดพลาดส่วนใหญ่ไม่ใช่ “ปัญหา Stripe” แต่เป็นปัญหาข้อมูล: ซ้ำ วันหาย และรีไทรเงียบ

เก็บเช็คลิสต์สั้นและบังคับใช้:

  • บังคับความไม่ซ้ำบนเหตุการณ์การใช้งาน (เช่น unique constraint บน event_id) และยึดมั่นกับกลยุทธ์ id เดียว
  • เก็บ webhook ทุกอัน ตรวจสอบลายเซ็น และประมวลผลแบบ idempotent
  • ปฏิบัติเหตุการณ์การใช้งานดิบเป็น immutable แก้ด้วยการปรับ (บวกหรือลบ) ไม่ใช่การแก้ไข
  • รันงานกระทบยอดรายวันที่เปรียบเทียบยอดภายใน (ต่อ customer, ต่อ meter, ต่อวัน) กับสถานะการเรียกเก็บของ Stripe
  • เพิ่มการแจ้งเตือนสำหรับช่องว่างและความผิดปกติ: วันหาย ยอดรวมติดลบ พุ่งกระทันหัน หรือความต่างใหญ่ระหว่าง “เหตุการณ์ที่ ingest” กับ “เหตุการณ์ที่ถูกเรียกเก็บ”

การทดสอบง่าย ๆ: เลือกลูกค้าหนึ่งราย รัน ingestion ใหม่สำหรับ 7 วันที่ผ่านมา และยืนยันว่ายอดไม่เปลี่ยน หากเปลี่ยน แสดงว่าคุณยังมีปัญหา idempotency หรือวงจรชีวิตเหตุการณ์

ตัวอย่างสถานการณ์: เดือนการใช้งานและใบแจ้งหนี้จริงจัง

ไปใช้งานจริงด้วยความมั่นใจ
ปรับใช้บริการเรียกเก็บเงินของคุณบน AppMaster Cloud หรือบนคลาวด์ของคุณเมื่อพร้อม
ปรับใช้ตอนนี้

ทีมซัพพอร์ตขนาดเล็กใช้พอร์ทัลลูกค้าที่คิด $0.10 ต่อบทสนทนาที่ปิด พวกเขาขายแบบเรียกเก็บตามการใช้งานกับ Stripe แต่ความเชื่อใจขึ้นอยู่กับว่าสิ่งที่เกิดขึ้นเมื่อข้อมูลยุ่งเหยิง

วันที่ 1 มีนาคม ลูกค้าเริ่มรอบการเรียกเก็บ ทุกครั้งที่เอเยนต์ปิดบทสนทนา แอปของคุณจะส่งเหตุการณ์การใช้งาน:

  • event_id: UUID คงที่จากแอปของคุณ
  • customer_id และ subscription_item_id
  • quantity: 1 บทสนทนา
  • occurred_at: เวลาเมื่อปิด
  • ingested_at: เมื่อคุณเห็นมันครั้งแรก

วันที่ 3 มีนาคม worker พื้นหลังรีไทรหลัง timeout และส่งบทสนทนาเดียวกันอีกครั้ง เพราะ event_id เป็นเอกลักษณ์ การแทรกครั้งที่สองกลายเป็น no-op และยอดรวมไม่เปลี่ยน

กลางเดือน Stripe ส่ง webhook สำหรับการพรีวิวใบแจ้งหนี้และต่อมาสำหรับใบแจ้งหนี้ที่สรุปแล้ว handler ของคุณเก็บ stripe_event_id, type, และ received_at และทำเครื่องหมาย processed หลังธุรกรรมฐานข้อมูลของคุณ commit หาก webhook ถูกส่งซ้ำ การส่งครั้งที่สองถูกละเลยเพราะ stripe_event_id มีอยู่แล้ว

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

วันที่ 22 มีนาคม คุณค้นพบว่ามีบทสนทนาหนึ่งถูกบันทึกสองครั้งเนื่องจากบั๊กที่สร้าง event_id ต่างกัน แทนที่จะลบประวัติ คุณเขียนเหตุการณ์ปรับด้วย quantity = -1 และเหตุผลเช่น “ตรวจพบซ้ำ” วิธีนี้เก็บบันทึกตรวจสอบไว้และทำให้การเปลี่ยนแปลงใบแจ้งหนี้อธิบายได้

ขั้นตอนถัดไป: ลงมือทำ มอนิเตอร์ และปรับปรุงอย่างปลอดภัย

เริ่มจากเล็ก ๆ: หนึ่งเมตร หนึ่งแผน หนึ่งกลุ่มลูกค้าที่คุณเข้าใจดี เป้าหมายคือความสอดคล้องง่าย ๆ — ตัวเลขของคุณตรงกับ Stripe เดือนแล้วเดือนเล่า โดยไม่มีความประหลาดใจ

สร้างเล็ก ๆ แล้วเสริมความแข็งแกร่ง

การเปิดตัวเชิงปฏิบัติ:

  • กำหนดรูปแบบเหตุการณ์เดียว (อะไรนับ เป็นหน่วยใด เวลาใด)
  • เก็บเหตุการณ์ทุกเหตุการณ์ด้วย idempotency key ที่ไม่ซ้ำและสถานะชัดเจน
  • รวมเป็นผลรวมรายวัน (หรือรายชั่วโมง) เพื่อให้สามารถอธิบายใบแจ้งหนี้ได้
  • กระทบยอดกับ webhook ของ Stripe ตามกำหนด ไม่ใช่แค่เรียลไทม์
  • หลังการออกใบแจ้งหนี้ ให้ปิดรอบและให้เหตุการณ์มาช้าทางเส้นทางการปรับปรุง

แม้ใช้ no-code คุณยังรักษาความสมบูรณ์ของข้อมูลได้ดีหากทำให้สถานะไม่ถูกต้องเป็นไปไม่ได้: บังคับ unique constraints สำหรับคีย์ idempotency, ต้องมี foreign keys ไปยัง customer และ subscription, และหลีกเลี่ยงการอัปเดตเหตุการณ์ดิบที่ยอมรับแล้ว

มอนิเตอร์ที่ช่วยคุณได้ในภายหลัง

เพิ่มหน้าจอตรวจสอบง่าย ๆ ตั้งแต่ต้น มันคืนทุนเมื่อมีคนถามครั้งแรกว่า “ทำไมบิลฉันเดือนนี้สูงขึ้น?” มุมมองที่มีประโยชน์รวมถึง: ค้นหาเหตุการณ์ตามลูกค้าและช่วงเวลา ดูยอดรวมต่อช่วงเวลาตามวัน ติดตามสถานะการประมวลผล webhook และตรวจสอบ backfills กับการปรับปรุงว่าใคร/เมื่อ/ทำไม

ถ้าคุณกำลังทำงานนี้กับ AppMaster (appmaster.io) โมเดลนี้พอดี: กำหนดเหตุการณ์ดิบ ผลรวม และการปรับปรุงใน Data Designer แล้วใช้ Business Processes สำหรับการรับข้อมูลแบบ idempotent การรวมตามกำหนดเวลา และการกระทบยอด webhook คุณยังได้บัญชีจริงและบันทึกตรวจสอบโดยไม่ต้องเขียน plumbing ทั้งหมดเอง

เมื่อเมตรแรกของคุณเสถียร เพิ่มเมตรถัดไป รักษากฎวงจรชีวิต เดียวกัน เครื่องมือการตรวจสอบเดียวกัน และนิสัยเดียว: เปลี่ยนทีละอย่าง แล้วยืนยันจากต้นจนจบ。

คำถามที่พบบ่อย

ทำไมการเรียกเก็บตามการใช้งานกับ Stripe ถึงรู้สึกยากกว่าที่คิด?

ปฏิบัติเหมือนระบบบัญชีเล็ก ๆ ความยากไม่ได้อยู่ที่การเก็บเงินจากบัตร แต่เป็นการรักษาบันทึกที่แม่นยำและอธิบายได้ว่าอะไรถูกนับบ้าง แม้เหตุการณ์จะมาช้า ถูกส่งซ้ำ หรือจำเป็นต้องแก้ไขก็ตาม。

ควรให้ Stripe หรือฐานข้อมูลของฉันเป็นแหล่งความจริงสำหรับการใช้งาน?

ค่าเริ่มต้นที่ปลอดภัยคือ: ฐานข้อมูลของคุณเป็นแหล่งความจริงสำหรับเหตุการณ์การใช้งานดิบและสถานะของมัน ในขณะที่ Stripe เป็นแหล่งความจริงสำหรับใบแจ้งหนี้และผลการชำระเงิน การแยกแบบนี้ทำให้การเรียกเก็บตรวจสอบได้ในขณะที่ปล่อยให้ Stripe จัดการราคาภาษีและการเก็บเงิน。

อะไรที่ทำให้คีย์ idempotency (event_id) ดีสำหรับเหตุการณ์การใช้งาน?

ทำให้คีย์มีความคงตัวและกำหนดได้อย่างแน่นอนเพื่อให้การส่งซ้ำให้ค่าเดียวกัน โดยปกติจะได้จากการกระทำทางธุรกิจจริง เช่น customer id บวก meter key บวก source record id เพื่อให้การส่งซ้ำกลายเป็น no-op ที่ไม่เป็นอันตราย。

ฉันจะแก้ไขการใช้งานที่ไม่ถูกต้องโดยไม่เขียนประวัติใหม่ได้อย่างไร?

อย่าแก้หรือลบเหตุการณ์การใช้งานที่ยอมรับแล้ว ให้บันทึกเหตุการณ์ปรับชดเชยแทน (รวมทั้งจำนวนติดลบเมื่อจำเป็น) และเก็บต้นฉบับไว้ เพื่อที่คุณจะสามารถอธิบายประวัติได้ทีหลังโดยไม่ต้องเดาว่ามีอะไรเปลี่ยนแปลง。

ฉันจำเป็นต้องมีทั้งเหตุการณ์ดิบและผลรวมรวมกันจริงหรือ?

เก็บเหตุการณ์การใช้งานดิบเป็นแบบ append-only และเก็บผลรวมแยกเป็นข้อมูลอนุพันธ์ที่สามารถสร้างใหม่ได้ ผลรวมใช้สำหรับความเร็วและรายงาน ส่วนเหตุการณ์ดิบใช้สำหรับการตรวจสอบ ข้อพิพาท และการสร้างผลรวมใหม่หลังจากบั๊กหรือการส่งซ้ำ。

ฉันควรจัดการข้อมูลการใช้งานที่มาช้าหรือการ backfill อย่างไร?

เก็บอย่างน้อยสองตัวบันทึกเวลา: เวลาเกิดเหตุ (occurred_at) และเวลา ingest (ingested_at) พร้อมบันทึกแหล่งที่มา ถ้าใบแจ้งหนี้ยังไม่สิ้นสุด ให้คำนวณใหม่ก่อนสรุป หากสิ้นสุดแล้ว ให้จัดการเป็นการแก้ไขชัดเจน (คิดเพิ่มหรือออกเครดิต) แทนการย้ายการใช้งานอย่างเงียบ ๆ ไปยังช่วงเวลาอื่น。

วิธีที่ปลอดภัยที่สุดในการประมวลผล Stripe webhooks โดยไม่ให้เกิดซ้ำคืออะไร?

เก็บเพย์โหลด webhook ทุกอันและบังคับการประมวลผลแบบ idempotent โดยใช้ event.id ของ Stripe เป็นคีย์ไม่ซ้ำ Webhook มักถูกส่งซ้ำหรือมานอกลำดับ ดังนั้น handler ควรใช้เฉพาะการเปลี่ยนสถานะที่ก้าวหน้าเท่านั้น。

ฉันจะจัดการการอัปเกรด ดาวน์เกรด และการคำนวณ prorate กับเมตรการใช้งานอย่างไร?

ใช้ช่วงเวลาการเรียกเก็บจาก subscription (billing period start และ end) แล้วแยกการใช้งานเมื่อราคาที่ใช้งานเปลี่ยน เป้าหมายคือแต่ละบรรทัดในใบแจ้งหนี้สามารถผูกกับช่วงเวลาและราคาที่ชัดเจนได้ ทำให้ยอดรวมตรวจสอบได้。

ฉันจะรู้ได้อย่างไรว่าท่อการวัดของฉันปลอดภัยก่อนจะใช้งานจริง?

เก็บรหัสรันการคำนวณหรือเมตาดาต้าเทียบเท่า เพื่อให้คุณสามารถสร้างยอดรวมซ้ำได้ หากการรันซ้ำในช่วงเดียวกันเปลี่ยนยอดรวมแสดงว่าคุณมีบั๊กเรื่อง idempotency หรือลำดับสถานะที่ผิดพลาด。

ฉันสามารถสร้างโมเดลข้อมูลและเวิร์กโฟลว์นี้ใน AppMaster โดยไม่ต้องเขียนโค้ดหรือไม่?

ใน Data Designer ให้จำลองเหตุการณ์การใช้งานดิบ ผลรวม การปรับปรุง และตารางกล่องจดหมาย webhook แล้วใช้ Business Processes สำหรับการรับข้อมูลและการกระทบยอดโดยมีคีย์ไม่ซ้ำสำหรับ idempotency คุณสามารถสร้างสมุดบัญชีที่ตรวจสอบได้และงานกระทบยอดตามกำหนดได้โดยไม่ต้องเขียนโค้ดทั้งหมดเอง。

ง่ายต่อการเริ่มต้น
สร้างบางสิ่งที่ น่าทึ่ง

ทดลองกับ AppMaster ด้วยแผนฟรี
เมื่อคุณพร้อม คุณสามารถเลือกการสมัครที่เหมาะสมได้

เริ่ม