Thanh toán theo sử dụng với Stripe: mô hình dữ liệu thực tiễn
Thanh toán theo sử dụng với Stripe cần lưu trữ sự kiện sạch sẽ và đối chiếu. Tìm hiểu schema đơn giản, luồng webhook, backfill và cách sửa đếm đôi.

Những gì bạn thực sự đang xây (và tại sao nó hỏng)
Thanh toán theo sử dụng nghe có vẻ đơn giản: đo những gì khách hàng đã dùng, nhân với giá, và tính phí vào cuối kỳ. Thực tế, bạn đang xây một hệ thống kế toán nhỏ. Nó phải luôn đúng ngay cả khi dữ liệu đến muộn, đến hai lần, hoặc không bao giờ đến.
Hầu hết lỗi không xảy ra ở phần checkout hay dashboard. Chúng xảy ra trong mô hình dữ liệu metering. Nếu bạn không thể trả lời một cách tự tin “Những sự kiện usage nào đã được tính vào hoá đơn này, và vì sao?”, cuối cùng bạn sẽ tính thừa, tính thiếu, hoặc mất niềm tin của khách hàng.
Thanh toán theo sử dụng thường hỏng theo vài cách có thể dự đoán: sự kiện bị mất sau sự cố, retry tạo bản sao, dữ liệu đến muộn xuất hiện sau khi tổng đã được tính, hoặc các hệ thống khác nhau bất đồng và bạn không thể đối chiếu được khác biệt.
Stripe rất tốt ở giá cả, hoá đơn, thuế và thu tiền. Nhưng Stripe không biết usage thô của sản phẩm bạn trừ khi bạn gửi cho nó. Điều đó buộc bạn phải quyết định nguồn chân lý: Stripe là sổ cái, hay cơ sở dữ liệu của bạn là sổ cái mà Stripe phản ánh?
Với hầu hết đội, phân chia an toàn nhất là:
- Cơ sở dữ liệu của bạn là nguồn chân lý cho các sự kiện usage thô và vòng đời của chúng.
- Stripe là nguồn chân lý cho những gì thực sự được lập hoá đơn và đã thanh toán.
Ví dụ: bạn theo dõi “API calls.” Mỗi lần gọi tạo một usage event với một khóa duy nhất ổn định. Khi đến thời điểm lập hoá đơn, bạn chỉ tổng các sự kiện hợp lệ chưa được tính phí, rồi tạo hoặc cập nhật invoice item trên Stripe. Nếu ingestion retry hoặc webhook đến hai lần, quy tắc idempotency khiến bản sao trở nên vô hại.
Quyết định cần làm trước khi thiết kế bảng
Trước khi tạo bảng, hãy cố định các định nghĩa quyết định liệu việc tính phí có thể giải thích được sau này hay không. Hầu hết “bug hoá đơn bí ẩn” đến từ các quy tắc không rõ ràng, không phải SQL tệ.
Bắt đầu với đơn vị bạn tính phí. Chọn thứ gì dễ đo và khó tranh cãi. “API calls” có thể phức tạp với retry, request theo lô, và thất bại. “Phút” khó xử khi có chồng lấp. “GB” cần nền tảng rõ (GB vs GiB) và phương pháp đo rõ (trung bình hay đỉnh).
Tiếp theo, xác định ranh giới. Hệ thống của bạn cần biết chính xác event thuộc cửa sổ nào. Usage được tính theo giờ, ngày, kỳ thanh toán hay theo hành động khách hàng? Nếu khách hàng nâng cấp giữa tháng, bạn chia cửa sổ hay áp giá cho cả tháng? Những lựa chọn này quyết định cách nhóm sự kiện và cách bạn giải thích tổng.
Cũng quyết định hệ thống nào sở hữu dữ kiện nào. Một mô hình phổ biến với Stripe là: app của bạn sở hữu sự kiện thô và tổng dẫn xuất, trong khi Stripe sở hữu hoá đơn và trạng thái thanh toán. Cách này hiệu quả nhất khi bạn không âm thầm sửa lịch sử. Bạn ghi nhận sửa sai như các mục mới và giữ bản gốc.
Một bộ nguyên tắc không thể thay đổi giúp schema trung thực:
- Truy xuất nguồn gốc: mỗi đơn vị tính được có thể truy về các sự kiện lưu trữ.
- Kiểm toán: bạn có thể trả lời “tại sao tính phí này?” sau nhiều tháng.
- Hoàn nguyên: lỗi được sửa bằng điều chỉnh rõ ràng.
- Idempotency: cùng một input không thể bị tính hai lần.
- Quyền sở hữu rõ ràng: mỗi sự thật thuộc về một hệ thống (usage, giá, hoá đơn).
Ví dụ: nếu bạn tính cho “tin nhắn đã gửi”, quyết định liệu retry có được tính, liệu giao hàng thất bại có được tính, và mốc thời gian nào là thước đo (thời gian client hay server). Ghi lại, rồi mã hoá vào trường sự kiện và validation, chứ đừng để trong đầu ai đó.
Mô hình dữ liệu đơn giản cho các sự kiện usage
Thanh toán theo sử dụng dễ nhất khi bạn coi usage như kế toán: dữ kiện thô là append-only, và tổng là dẫn xuất. Lựa chọn đơn giản này ngăn hầu hết tranh chấp vì bạn luôn có thể giải thích một con số đến từ đâu.
Một khởi điểm thực tế dùng năm bảng cốt lõi (tên có thể khác nhau):
- customer: id khách hàng nội bộ, Stripe customer id, trạng thái, metadata cơ bản.
- subscription: id subscription nội bộ, Stripe subscription id, plan/giá mong đợi, thời điểm bắt đầu/kết thúc.
- meter: thứ bạn đo (API calls, seats, GB-hours). Bao gồm một meter key ổn định, đơn vị, và cách tổng hợp (sum, max, unique).
- usage_event: một hàng cho mỗi hành động đo được. Lưu
customer_id,subscription_id(nếu biết),meter_id,quantity,occurred_at(khi nó xảy ra),received_at(khi bạn ingest),source(app, batch import, partner), và một khóa ngoài ổn định để dedupe. - usage_aggregate: tổng dẫn xuất, thường theo customer + meter + time bucket (ngày hoặc giờ) và kỳ thanh toán. Lưu tổng quantity cộng với một version hoặc
last_event_received_atđể hỗ trợ tính lại.
Giữ usage_event không thay đổi. Nếu sau này bạn phát hiện lỗi, ghi một event bù đắp (ví dụ -3 seats cho huỷ) thay vì chỉnh sửa lịch sử.
Lưu sự kiện thô cho kiểm toán và tranh chấp. Nếu bạn không thể lưu vĩnh viễn, ít nhất giữ chúng trong cửa sổ lookback billing cộng với cửa sổ hoàn tiền/tranh chấp.
Giữ các tổng dẫn xuất riêng. Aggregates nhanh cho hoá đơn và dashboard, nhưng chúng dùng được và có thể xây lại. Bạn nên có khả năng rebuild usage_aggregate từ usage_event bất cứ lúc nào, kể cả sau backfill.
Idempotency và trạng thái vòng đời sự kiện
Dữ liệu usage nhiều nhiễu. Client retry yêu cầu, queue gửi bản sao, và webhook Stripe có thể đến lệch thứ tự. Nếu database của bạn không thể chứng minh “sự kiện này đã được tính rồi”, cuối cùng bạn sẽ tính hai lần.
Cho mỗi usage event một event_id ổn định, có thể xác định và cưỡng chế uniqueness trên nó. Đừng chỉ dựa vào id auto-increment làm định danh duy nhất. Một event_id tốt sinh ra từ hành động nghiệp vụ, chẳng hạn customer_id + meter + source_record_id (hoặc customer_id + meter + timestamp_bucket + sequence). Nếu cùng hành động được gửi lại, nó tạo cùng event_id và insert trở thành no-op an toàn.
Idempotency phải bao phủ mọi đường ingest, không chỉ API công khai của bạn. Gọi SDK, import theo lô, job worker và processor webhook đều có thể retry. Dùng một quy tắc: nếu input có thể retry, nó cần một idempotency key lưu trong database và kiểm tra trước khi thay đổi totals.
Một mô hình trạng thái vòng đời đơn giản làm cho retry an toàn và hỗ trợ dễ hơn. Giữ nó rõ ràng và lưu lý do khi có lỗi:
received: đã lưu, chưa kiểm travalidated: hợp lệ schema, customer, meter và quy tắc cửa sổ thời gianposted: đã được tính vào tổng kỳ thanh toánrejected: bị bác bỏ vĩnh viễn (kèm mã lý do)
Ví dụ: worker của bạn crash sau khi validate nhưng trước khi post. Khi retry, nó tìm thấy cùng event_id ở trạng thái validated, rồi tiếp tục sang posted mà không tạo sự kiện thứ hai.
Với webhook Stripe, dùng cùng mẫu: lưu event.id của Stripe và đánh dấu đã xử lý chỉ một lần, nên việc gửi lại trùng lặp là vô hại.
Từng bước: ingest sự kiện metering đầu cuối
Xử lý mỗi metering event như tiền: xác thực, lưu bản gốc, rồi dẫn xuất tổng từ nguồn chân lý. Điều đó giữ việc tính phí dự đoán được khi hệ thống retry hoặc gửi dữ liệu muộn.
Một luồng ingest đáng tin cậy
Xác thực mỗi event đến trước khi chạm vào bất kỳ tổng nào. Tối thiểu yêu cầu: một định danh khách hàng ổn định, tên meter, quantity số, dấu thời gian, và một khóa sự kiện duy nhất để idempotency.
Ghi bản raw event trước, ngay cả khi bạn định tổng hợp sau. Bản raw này là thứ bạn sẽ tái xử lý, kiểm toán, và dùng để sửa lỗi mà không phải đoán mò.
Một luồng đáng tin cậy như sau:
- Chấp nhận event, kiểm tra các trường bắt buộc, chuẩn hoá đơn vị (ví dụ giây vs phút).
- Chèn một hàng usage event thô dùng khóa event như ràng buộc duy nhất.
- Tổng vào một bucket (hàng ngày hoặc theo kỳ) bằng cách áp dụng quantity của event.
- Nếu bạn gửi usage tới Stripe, ghi lại những gì bạn đã gửi (meter, quantity, period, và các id phản hồi của Stripe).
- Ghi lại các bất thường (event bị từ chối, chuyển đổi đơn vị, arrival muộn) cho kiểm toán.
Giữ việc tổng hợp có thể chạy lại. Một cách phổ biến: chèn raw event trong một transaction, rồi enqueue job để cập nhật buckets. Nếu job chạy hai lần, nó phải phát hiện raw event đã được áp dụng.
Khi khách hàng hỏi tại sao họ bị tính 12.430 API calls, bạn nên có khả năng hiển thị chính xác tập raw events được bao gồm trong cửa sổ thanh toán đó.
Đối chiếu webhook Stripe với database của bạn
Webhooks là biên lai cho những gì Stripe thực sự làm. App của bạn có thể tạo draft và đẩy usage, nhưng trạng thái hoá đơn chỉ trở nên thực khi Stripe xác nhận.
Hầu hết đội tập trung vào một tập nhỏ loại webhook ảnh hưởng đến kết quả thanh toán:
invoice.created,invoice.finalized,invoice.paid,invoice.payment_failedcustomer.subscription.created,customer.subscription.updated,customer.subscription.deletedcheckout.session.completed(nếu bạn bắt đầu subscription qua Checkout)
Lưu mọi webhook bạn nhận được. Giữ payload thô cùng những gì bạn quan sát khi nó đến: Stripe event.id, event.created, kết quả kiểm tra chữ ký, và thời điểm server bạn nhận. Lịch sử đó quan trọng khi debug khác biệt hoặc trả lời “tại sao tôi bị tính tiền?”.
Một mẫu đối chiếu idempotent tốt trông như sau:
- Chèn webhook vào bảng
stripe_webhook_eventsvới ràng buộc duy nhất trênevent_id. - Nếu insert thất bại, đó là retry. Dừng lại.
- Kiểm tra chữ ký và ghi pass/fail.
- Xử lý event bằng cách tra cứu bản ghi nội bộ theo các Stripe ID (customer, subscription, invoice).
- Áp dụng thay đổi trạng thái chỉ khi nó tiến lên.
Giao hàng lệch thứ tự là bình thường. Dùng quy tắc “trạng thái lớn nhất thắng” kèm dấu thời gian: đừng bao giờ chuyển một bản ghi lùi lại.
Ví dụ: bạn nhận invoice.paid cho invoice in_123, nhưng hàng invoice nội bộ chưa tồn tại. Tạo một hàng đánh dấu “thấy từ Stripe”, rồi gắn nó vào tài khoản đúng sau bằng Stripe customer ID. Điều đó giữ sổ cái nhất quán mà không xử lý đôi.
Từ tổng usage đến dòng mục hoá đơn
Biến usage thô thành các dòng hoá đơn chủ yếu là về thời điểm và ranh giới. Quyết định bạn có cần tổng theo thời gian thực (dashboard, cảnh báo chi tiêu) hay chỉ khi lập hoá đơn (invoices). Nhiều đội làm cả hai: ghi sự kiện liên tục, tính totals sẵn sàng cho hoá đơn trong một job theo lịch.
Căn chỉnh cửa sổ usage với kỳ thanh toán của Stripe. Đừng đoán tháng lịch. Dùng billing period start và end của subscription item hiện tại, rồi chỉ tổng các event có timestamp rơi trong cửa sổ đó. Lưu timestamp theo UTC và dùng cửa sổ tính theo UTC.
Giữ lịch sử không đổi. Nếu sau này phát hiện lỗi, đừng sửa các event cũ hay ghi đè tổng trước đó. Tạo một record điều chỉnh trỏ tới cửa sổ gốc và cộng hoặc trừ quantity. Dễ kiểm toán hơn và dễ giải thích hơn.
Thay đổi kế hoạch và proration là nơi dễ mất tính truy xuất. Nếu khách hàng đổi plan giữa kỳ, tách usage thành các phân-cửa-sổ khớp với mỗi khoảng giá có hiệu lực. Hoá đơn của bạn có thể bao gồm hai dòng usage (hoặc một dòng cộng điều chỉnh), mỗi dòng liên kết tới một giá và khoảng thời gian cụ thể.
Một luồng thực tế:
- Lấy cửa sổ hoá đơn từ Stripe period start và end.
- Tổng các sự kiện usage hợp lệ vào một tổng cho cửa sổ đó và cho giá.
- Sinh invoice line items từ tổng usage cộng các điều chỉnh.
- Lưu một calculation run id để bạn có thể tái tạo số liệu sau này.
Backfill và dữ liệu đến muộn mà không mất niềm tin
Dữ liệu đến muộn là bình thường. Thiết bị offline, job theo lô chậm, partner gửi lại file, và log được replay sau sự cố. Chìa khoá là xử lý backfill như công việc chỉnh sửa, không phải cách “ép cho số khớp”.
Rõ ràng nguồn backfill có thể đến từ đâu (application logs, export kho dữ liệu, hệ thống partner). Ghi nguồn trên mỗi event để bạn giải thích vì sao nó đến muộn.
Khi backfill, giữ hai timestamp: khi usage xảy ra (thời điểm bạn muốn tính) và khi bạn ingest nó. Gắn tag event là backfilled, nhưng đừng ghi đè lịch sử.
Ưu tiên xây lại totals từ raw events hơn là áp deltas lên aggregate hiện tại. Replay là cách bạn phục hồi từ bug mà không đoán. Nếu pipeline idempotent, bạn có thể chạy lại một ngày, một tuần, hoặc toàn bộ kỳ và có cùng totals.
Khi hoá đơn đã tồn tại, chỉnh sửa phải theo chính sách rõ ràng:
- Nếu hoá đơn chưa finalize, tính lại và cập nhật trước khi finalize.
- Nếu đã finalize và bị tính thiếu, phát hoá đơn bổ sung (hoặc thêm invoice item) với mô tả rõ ràng.
- Nếu đã finalize và tính thừa, phát credit note và tham chiếu hoá đơn gốc.
- Đừng di chuyển usage sang kỳ khác để tránh correction.
- Lưu lý do ngắn cho chỉnh sửa (partner resend, delayed log delivery, bug fix).
Ví dụ: partner gửi thiếu events cho Jan 28-29 vào Feb 3. Bạn chèn chúng với occurred_at vào tháng Một, ingested_at vào tháng Hai, và nguồn backfill là “partner.” Hoá đơn tháng Một đã thanh toán, vậy bạn tạo một hoá đơn phụ nhỏ cho các đơn vị thiếu, với lý do lưu kèm bản ghi đối chiếu.
Những sai lầm phổ biến gây tính đôi
Tính đôi xảy ra khi hệ thống coi “một message đến” là “hành động đã xảy ra.” Với retry, webhook đến muộn và backfill, bạn cần tách hành động khách hàng khỏi việc xử lý của bạn.
Thủ phạm thường gặp:
- Retry bị tính như usage mới. Nếu mỗi event không mang một action id ổn định (request_id, message_id) và database không cưỡng chế uniqueness, bạn sẽ tính hai lần.
- Thời gian event bị trộn với thời gian xử lý. Báo cáo theo thời gian ingest thay vì occurred khiến events đến muộn rơi vào kỳ sai, rồi bị tính lại khi replay.
- Xoá hoặc ghi đè raw events. Nếu bạn chỉ giữ một running total, bạn không thể chứng minh điều gì đã xảy ra, và reprocessing có thể làm tăng totals.
- Giả định thứ tự webhook. Webhook có thể duplicate, lệch thứ tự, hoặc thể hiện trạng thái chưa hoàn tất. Đối chiếu theo Stripe object IDs và giữ cơ chế “đã xử lý” để tránh lặp.
- Huỷ, hoàn tiền và credit không được mô hình hoá rõ. Nếu bạn chỉ cộng usage và không ghi điều chỉnh âm, cuối cùng bạn sẽ “sửa” totals bằng import và lại tính đôi.
Ví dụ: bạn ghi “10 API calls” và sau đó phát một credit 2 calls do outage. Nếu bạn backfill bằng cách gửi lại cả ngày dùng và cũng áp credit, khách hàng có thể thấy 18 calls (10 + 10 - 2) thay vì 8.
Danh sách kiểm tra nhanh trước khi chạy live
Trước khi bật thanh toán theo sử dụng cho khách hàng thật, rà soát lần cuối những cơ bản ngăn các lỗi hoá đơn tốn kém. Hầu hết lỗi không phải “vấn đề Stripe.” Chúng là vấn đề dữ liệu: duplicate, mất ngày, và retry im lặng.
Giữ danh sách ngắn và có thể thực thi:
- Cưỡng chế uniqueness trên usage events (ví dụ constraint duy nhất trên
event_id) và cam kết một chiến lược id. - Lưu mọi webhook, kiểm tra chữ ký, và xử lý idempotently.
- Xử lý raw usage là immutable. Sửa bằng adjustments (dương hoặc âm), không chỉnh sửa.
- Chạy job đối chiếu hàng ngày so sánh totals nội bộ (theo customer, meter, ngày) với trạng thái billing của Stripe.
- Thêm cảnh báo cho lỗ hổng và bất thường: ngày thiếu, totals âm, spike đột ngột, hoặc khác biệt lớn giữa “events ingested” và “events billed.”
Một bài kiểm tra đơn giản: chọn một khách hàng, chạy lại ingestion cho 7 ngày gần nhất, và xác nhận totals không đổi. Nếu đổi, bạn vẫn có vấn đề idempotency hoặc backfill.
Ví dụ kịch bản: một tháng thực tế của usage và hoá đơn
Một nhóm support nhỏ dùng portal tính $0.10 cho mỗi cuộc hội thoại xử lý. Họ bán theo usage với Stripe, nhưng niềm tin đến từ cách xử lý khi dữ liệu rối.
Ngày 1 tháng 3, khách hàng bắt đầu kỳ thanh toán mới. Mỗi khi agent đóng cuộc hội thoại, app của bạn phát một usage event:
event_id: UUID ổn định từ appcustomer_idvàsubscription_item_idquantity: 1 conversationoccurred_at: thời gian đóngingested_at: khi bạn thấy nó lần đầu
Ngày 3 tháng 3, worker retry sau timeout và gửi cùng một conversation lần nữa. Vì event_id là duy nhất, lần chèn thứ hai là no-op và totals không đổi.
Giữa tháng, Stripe gửi webhook cho preview invoice và sau đó hoá đơn được finalize. Handler webhook lưu stripe_event_id, type, và received_at, và đánh dấu đã xử lý chỉ sau khi transaction database commit. Nếu webhook gửi lại, lần gửi thứ hai bị bỏ vì stripe_event_id đã tồn tại.
Ngày 18 tháng 3, bạn import một batch muộn từ client mobile offline. Nó chứa 35 conversations từ 17 tháng 3. Những event đó có occurred_at cũ hơn, nhưng vẫn hợp lệ. Hệ thống chèn chúng, tính lại totals hàng ngày cho 17 tháng 3, và usage bổ sung được lấy vào hoá đơn tiếp theo vì vẫn nằm trong kỳ thanh toán mở.
Ngày 22 tháng 3, bạn phát hiện một conversation được ghi hai lần do bug sinh hai event_id khác nhau. Thay vì xoá lịch sử, bạn ghi một event điều chỉnh với quantity = -1 và lý do như “duplicate detected.” Điều đó giữ lịch sử kiểm toán nguyên vẹn và làm cho thay đổi hoá đơn rõ ràng.
Bước tiếp theo: triển khai, giám sát và lặp an toàn
Bắt đầu nhỏ: một meter, một plan, một phân khúc khách hàng bạn hiểu rõ. Mục tiêu là nhất quán đơn giản - số liệu của bạn khớp Stripe tháng này qua tháng khác, không có bất ngờ.
Xây nhỏ rồi tăng cường
Một bản rollout thực tế ban đầu:
- Định nghĩa một event shape (đếm gì, bằng đơn vị nào, theo thời gian nào).
- Lưu mọi event với idempotency key duy nhất và trạng thái rõ ràng.
- Tổng thành totals hàng ngày (hoặc hàng giờ) để hoá đơn có thể giải thích.
- Đối chiếu với webhook Stripe theo lịch, không chỉ thời gian thực.
- Sau khi lập hoá đơn, coi kỳ đó là đóng và chuyển các event muộn vào đường chỉnh sửa (adjustment).
Ngay cả với no-code, bạn có thể giữ tính toàn vẹn dữ liệu mạnh nếu làm cho trạng thái không hợp lệ trở nên không thể: cưỡng chế khóa duy nhất cho idempotency, yêu cầu foreign keys tới customer và subscription, và tránh cập nhật raw events đã chấp nhận.
Giám sát cứu bạn về sau
Thêm màn hình kiểm toán sớm. Chúng trả giá ngay lần đầu có ai đó hỏi “Tại sao hoá đơn tôi cao hơn tháng này?”. Các chế độ xem hữu ích: tìm event theo khách hàng và kỳ, xem totals theo ngày, theo dõi trạng thái xử lý webhook, và xem lại backfills cùng adjustments với thông tin ai/lúc/nào/vì sao.
Nếu bạn triển khai bằng AppMaster (appmaster.io), mô hình này phù hợp tự nhiên: định nghĩa raw events, aggregates, và adjustments trong Data Designer, rồi dùng Business Processes cho ingest idempotent, aggregation theo lịch, và đối chiếu webhook. Bạn vẫn có một sổ cái thực và lịch sử kiểm toán, mà không phải tự viết mọi plumbing.
Khi meter đầu tiên ổn định, thêm meter tiếp theo. Giữ cùng quy tắc vòng đời, cùng công cụ kiểm toán, và cùng thói quen: thay đổi từng thứ một, rồi kiểm tra end-to-end.
Câu hỏi thường gặp
Xem nó như một sổ cái nhỏ. Phần khó không phải là trừ tiền thẻ; mà là giữ một bản ghi chính xác và dễ giải thích về những gì đã được tính, ngay cả khi sự kiện đến muộn, đến hai lần, hoặc cần sửa lỗi.
Mặc định an toàn là: cơ sở dữ liệu của bạn là nguồn chân lý cho các sự kiện sử dụng thô và trạng thái của chúng, còn Stripe là nguồn chân lý cho hoá đơn và kết quả thanh toán. Phân chia này giữ tính truy xuất được trong khi để Stripe xử lý giá, thuế và thu tiền.
Hãy làm cho nó ổn định và có tính xác định để retry tạo cùng một identifier. Thông thường nó được tạo từ hành động nghiệp vụ thực sự, ví dụ customer id + meter key + source record id, để gửi lại không tạo thêm usage.
Đừng sửa hoặc xoá các sự kiện sử dụng đã được chấp nhận. Ghi một sự kiện điều chỉnh bù đắp (có thể là số lượng âm khi cần) và giữ nguyên bản gốc, như vậy bạn có thể giải thích lịch sử sau này mà không phải đoán.
Giữ các sự kiện sử dụng thô là append-only, và lưu các tổng hợp ở nơi khác như dữ liệu dẫn xuất có thể xây lại. Aggregates để tăng tốc báo cáo; raw events để kiểm toán, tranh chấp và xây lại totals khi có lỗi hoặc backfill.
Lưu ít nhất hai dấu thời gian: khi nó xảy ra và khi bạn ingest, và lưu nguồn. Nếu hoá đơn chưa được finalize, tính lại trước khi finalize; nếu đã finalize, xử lý như một correction rõ ràng (thu thêm hoặc credit) thay vì âm thầm dời usage sang kỳ khác.
Lưu mọi payload webhook bạn nhận được và cưỡng chế xử lý idempotent bằng cách dùng Stripe’s event id làm khoá duy nhất. Webhook thường bị duplicate hoặc đến lệch thứ tự, nên handler chỉ áp dụng các thay đổi trạng thái khi chúng tiến lên.
Dùng period start và end của subscription từ Stripe làm window, và tách usage khi giá thay đổi. Mục tiêu là mỗi dòng hoá đơn có thể liên kết tới khoảng thời gian và giá cụ thể để tổng số dễ giải thích.
Làm cho logic aggregation chứng minh được những raw events nào đã được bao gồm, và lưu một calculation run id hoặc metadata tương đương để bạn có thể tái tạo totals sau này. Nếu chạy lại ingestion cho cùng window làm totals thay đổi, bạn có vấn đề về idempotency hoặc trạng thái vòng đời.
Mô hình các sự kiện sử dụng thô, aggregates, adjustments và bảng hộp thư webhook trong Data Designer, sau đó triển khai ingestion và reconciliation bằng Business Processes với các ràng buộc duy nhất cho idempotency. Bạn có thể xây sổ cái kiểm toán và đối chiếu định kỳ mà không cần viết mọi phần plumbing bằng tay.


