Obiz Solutions

MannaHarvest Order Profit Tracker — đồng bộ Shopify để tính lợi nhuận theo đơn

Xây dựng cho MannaHarvest.com, một cửa hàng thương mại điện tử cần biết lợi nhuận thật theo từng đơn hàng, chứ không chỉ doanh thu. Đơn hàng tự động đồng bộ từ Shopify, nhân viên nhập chi phí sản phẩm và vận chuyển qua một web app nhỏ, và lợi nhuận theo đơn được tính ra từ một database view — không cần spreadsheet, không tính tay.

Kiến trúc

Kiến trúc Order Profit Tracker: webhook Shopify → Caddy → Fastify → PostgreSQL, Fastify định kỳ gọi Shopify Admin API, nhân viên truy cập web app qua HTTPS

Ba service chạy trong Docker Compose trên một VPS duy nhất:

  1. Caddy — reverse proxy, tự động HTTPS.
  2. Fastify (Node.js) — API và một frontend vanilla-JS nhỏ, không cần build step.
  3. PostgreSQL — bảng orders, line items, và một view order_financials tự động cascade chi phí/doanh thu từ line item → sản phẩm → đơn hàng → lợi nhuận.

Giữ đơn hàng luôn đồng bộ

Webhook của Shopify (orders/create, orders/updated, orders/cancelled) đẩy thay đổi về theo thời gian thực, mỗi webhook được xác thực bằng chữ ký HMAC trước khi xử lý. Webhook đôi khi bị rớt — lỗi gửi, downtime — nên một job đối soát định kỳ quét lại các đơn gần đây mỗi 4 giờ như lớp dự phòng. Kết hợp cả hai, toàn bộ lịch sử đơn hàng luôn được đồng bộ: webhook lo phần thời gian thực, đối soát bắt lại những gì lọt lưới.

Đồng bộ phí: không có webhook, nên phải retry ba lớp

Shopify không phát webhook cho phí xử lý thanh toán — dữ liệu này chỉ xuất hiện sau khi giao dịch được settle bên phía Shopify, không cùng lúc với lúc đơn hàng được tạo. Một lần tra cứu là không đủ, nên việc đồng bộ phí được thiết kế ba lớp:

  1. Tra cứu ngay lập tức, cố gắng tốt nhất, chạy ngay khi orders/create bắn ra.
  2. Nếu giao dịch chưa settle, đơn hàng được đánh dấu và một job retry có mục tiêu sẽ kiểm tra lại mỗi giờ, tối đa 5 lần.
  3. Một lượt quét không giới hạn chạy mỗi 6 giờ như lớp dự phòng cuối cùng, không bao giờ bỏ cuộc với một đơn hàng.

Cả ba job đều chạy một lần ngay khi process khởi động, không chỉ theo interval — vì setInterval bị reset mỗi lần deploy, nếu không có bước này thì việc deploy liên tục trong giai đoạn phát triển có thể khiến đơn hàng thiếu phí lâu hơn dự tính. (Một lỗi thật, đã phát hiện và sửa.)

Nhập chi phí với carry-forward

Shopify không lưu chi phí sản phẩm thật cho cửa hàng này, nên chi phí được nhập tay qua màn hình Order Detail. Để tránh phải gõ lại cùng một chi phí ở mọi đơn hàng, một khi chi phí của một SKU đã được xác nhận, nó sẽ tự điền (carry-forward) sang đơn tiếp theo có cùng SKU — nhưng vẫn được đánh dấu chờ xác nhận lại, vì chi phí có thể thay đổi, không nên tin tưởng ngầm định.

Bảo mật

Vì sao chọn stack này