Bỏ qua để đến nội dung

Thứ tự và trùng lặp

~3 phút đọc

Dòng Tiền Pro giao webhook theo nguyên tắc ít nhất một lần: sự kiện không bị mất, nhưng có thể tới hai lần (ví dụ khi phản hồi 2xx của bạn bị mất giữa đường) và các sự kiện khác nhau có thể tới không đúng thứ tự phát sinh. Bộ nhận phải chịu được cả hai điều đó.

Mỗi lần giao mang header webhook-id dạng msg_…. Giá trị này giữ nguyên qua mọi lần thử lại và cả khi phát lại thủ công, nên nó là khoá khử trùng đúng. Cách làm chắc chắn nhất là ghi webhook-id vào một bảng có ràng buộc duy nhất, rồi chỉ xử lý nếu việc ghi thành công:

CREATE TABLE webhook_inbox (
webhook_id text PRIMARY KEY,
received_at timestamptz NOT NULL DEFAULT now()
);
-- Một hàng được thêm: lần đầu thấy, xử lý. Không thêm hàng nào: đã xử lý, trả 2xx và bỏ qua.
INSERT INTO webhook_inbox (webhook_id) VALUES ($1) ON CONFLICT DO NOTHING;

Thực hiện INSERT và việc cập nhật dữ liệu nghiệp vụ trong cùng một giao dịch cơ sở dữ liệu. Nếu xử lý lỗi, giao dịch quay lui, webhook_id không được lưu, và lần giao lại sau đó được xử lý bình thường.

Khoá khử trùng thay thế là data.object.id (ví dụ tx_…) kèm loại sự kiện, hữu ích khi bạn muốn một giao dịch chỉ kích hoạt một hành động duy nhất cho mỗi loại sự kiện.

Hai sự kiện của cùng một giao dịch, ví dụ transaction.created rồi transaction.confirmed, có thể tới ngược thứ tự, nhất là khi một lần giao phải thử lại. Cách xử lý:

  • Dựa vào dữ liệu trong sự kiện, không dựa vào thứ tự đến: timestamp của phong bì và các trường như status, occurred_at của đối tượng cho biết trạng thái nào mới hơn.
  • Ghi theo kiểu "ghi đè nếu mới hơn" (upsert theo data.object.id), không theo kiểu "thêm mới rồi cập nhật". Khi nhận transaction.confirmed mà chưa có bản ghi giao dịch, hãy tạo bản ghi từ chính sự kiện đó.
  • Khi nghi ngờ, đọc lại đối tượng bằng API (GET /v1/transactions/{id}) để lấy trạng thái hiện tại.

Chỉ coi giao dịch là tiền về khi nhận transaction.confirmed. Sự kiện transaction.created có thể mang giao dịch còn tạm tính (provisional), chưa đủ bằng chứng. confirmed là xác thực nguồn, không phải ngân hàng xác nhận quyết toán, nên các quy trình cần độ chắc chắn cao hơn (giao hàng, hạch toán) vẫn nên có bước đối chiếu của bạn.

Kết hợp với khử trùng ở trên: ghi nhận khoản thu trong cùng giao dịch cơ sở dữ liệu với việc lưu webhook_id, và đặt ràng buộc duy nhất trên tx_… nếu cần một khoản thu cho mỗi giao dịch.

Trả 2xx trong 10 giây. Nếu việc xử lý chậm, lưu thông điệp đã xác minh vào hàng đợi của bạn rồi trả lời ngay, và xử lý ở tiến trình khác. Khử trùng vẫn làm ở bước nhận, trước khi đưa vào hàng đợi.