Câu hỏi đầu tiên lập trình viên thường hỏi khi xem mã nguồn ParkViet: “Sao không tự viết?”. Một phần mềm bán hàng nhìn từ ngoài vào khá đơn giản: sản phẩm, đơn hàng, thanh toán, tồn kho. Viết từ đầu nghe có vẻ gọn hơn, ít phụ thuộc hơn.
Chúng tôi đã cân nhắc nghiêm túc lựa chọn đó, và quyết định không làm vậy. Bài này giải thích vì sao.
Phần “đơn giản” không hề đơn giản
Lõi thương mại là nơi chứa rất nhiều trường hợp biên mà chỉ khi chạy thật mới lộ ra:
- Một đơn có ba sản phẩm, áp hai mã giảm giá, giao làm hai lần, khách trả lại một món ở lần giao thứ hai. Hoàn bao nhiêu tiền? Khuyến mãi tính lại thế nào?
- Hai khách cùng bấm thanh toán món hàng cuối cùng trong kho. Ai được mua?
- Sản phẩm có size và màu, mỗi lựa chọn có giá, mã hàng và tồn kho riêng, ở nhiều kho khác nhau.
- Thuế theo khu vực, giá theo loại tiền, nội dung theo ngôn ngữ.
Mỗi điều trên đều viết được. Nhưng viết cho đúng, có test, rồi giữ cho đúng qua nhiều năm thay đổi là công việc của cả một đội trong thời gian dài. Sai ở đây nghĩa là sai tiền, sai kho, những lỗi mà chủ cửa hàng không tha thứ.
Spree Commerce mang lại gì
Spree Commerce là nền tảng thương mại điện tử mã nguồn mở viết bằng Ruby on Rails, giấy phép BSD-3-Clause. ParkViet dùng Spree 6. Từ lõi này, chúng tôi nhận được:
- Mô hình dữ liệu thương mại hoàn chỉnh: sản phẩm, biến thể, danh mục, đơn hàng, thanh toán, giao hàng, đổi trả.
- Tồn kho nhiều địa điểm: theo dõi số lượng theo sản phẩm và kho, giữ hàng trong lúc khách thanh toán, cho phép đặt trước khi hết hàng.
- Khuyến mãi: giảm theo sản phẩm, theo đơn, miễn phí giao hàng, thẻ quà tặng, số dư tài khoản.
- Đa ngôn ngữ, đa tiền tệ, thuế theo khu vực.
- API REST có OpenAPI và webhook: Store API cho website, Admin API cho hệ thống nội bộ.
- Ứng dụng có sẵn: website bán hàng viết bằng Next.js, bảng quản trị React có cơ chế plugin, bảng người bán cho mô hình sàn.
- Bảo mật kế thừa từ Rails và từ quy trình của Spree: chống SQL injection, CSRF, XSS mặc định; không lưu số thẻ; khoá và token nhạy cảm được mã hoá; lỗ hổng được báo cáo kín và công bố bản vá qua GitHub Security Advisory.
Giấy phép BSD-3-Clause rất thoáng: được dùng, sửa và phân phối lại, kể cả cho mục đích thương mại. Với một dự án muốn ai cũng tự cài và tự sửa được, đó là điều kiện quan trọng.
ParkViet tự làm phần nào
Đứng trên lõi có sẵn cho phép chúng tôi dồn sức vào đúng phần mà một nền tảng quốc tế không có, và cũng sẽ không có, vì đó là phần riêng của Việt Nam:
- POS bán tại quầy (
apps/pos): ứng dụng React riêng, nhiều chi nhánh, nhiều quầy, nhiều đơn song song, màn hình khách, quét tem cân EAN-13. - VietQR tự đối soát (
spree_vietqr): một gem viết theo cơ chế payment method của Spree, nhận webhook từ SePay và PayOS, xác minh chữ ký, chống trùng giao dịch. - Hồ sơ ngành: 13 ngành bán lẻ, mỗi ngành bật đúng module quy đổi đơn vị, hàng cân, hay serial, lô, hạn dùng.
- Truy xuất serial, lô, hạn dùng theo FEFO, cách ly và thu hồi theo lô.
- Ca, sổ quỹ, đếm mù, thu chi két có xác nhận.
- Bán chịu có hạn mức và người duyệt, báo cáo tuổi nợ.
Sơ đồ đơn giản nhất: Spree lo phần lõi thương mại, ParkViet lo phần hợp với cách bán hàng của người Việt, và cả hai cùng chạy trên Rails, PostgreSQL, Redis, Docker.
Vì sao không tự viết lõi mà chỉ “mượn ý tưởng”?
Có một con đường ở giữa: đọc mã Spree để học, rồi tự viết lại theo ý mình. Chúng tôi không chọn con đường đó vì một lý do: bản vá.
Khi lõi thương mại có lỗi, nhất là lỗi bảo mật, chúng tôi muốn nhận bản vá từ upstream và cập nhật, thay vì tự phát hiện và tự sửa một mình. ParkViet bám sát cách tổ chức của Spree chính là để việc cập nhật này khả thi. Cũng vì vậy, tài liệu của Spree áp dụng được phần lớn cho ParkViet: API, webhook, plugin bảng quản trị, cách viết một cổng thanh toán mới.
Cái giá phải trả
Không có lựa chọn nào miễn phí. Xây trên Spree, chúng tôi chấp nhận:
- Phải theo sát upstream. Spree ra phiên bản mới thì ParkViet phải cập nhật, không giữ phiên bản đã hết hỗ trợ. Đó là cam kết, cũng là công việc thường xuyên.
- Đi theo mô hình dữ liệu của Spree. Có những chỗ chúng tôi muốn làm khác, nhưng phải tìm cách mở rộng thay vì sửa thẳng vào lõi, để không tự cắt đường cập nhật.
- Chọn Ruby on Rails. Ai muốn đóng góp vào phần máy chủ cần biết Rails. Phần giao diện (POS, bảng quản trị, website) thì dùng React và Next.js, quen thuộc với nhiều người hơn.
Chúng tôi thấy những cái giá này đáng trả, so với việc mất vài năm viết lại những gì đã có.
ParkViet và Spree có quan hệ gì?
ParkViet được xây dựng trên Spree Commerce. ParkViet là dự án độc lập, không phải sản phẩm của Spree, và không được Spree bảo trợ. Chúng tôi dùng Spree theo đúng giấy phép, theo sát các bản cập nhật của Spree, và nếu sửa được lỗi nào ở lõi thì sẽ gửi ngược lại cho cộng đồng Spree.
Muốn tìm hiểu sâu hơn
- Kiến trúc và cấu trúc repo: Dành cho nhà phát triển
- Các lớp bảo vệ kế thừa và bổ sung: Bảo mật
- Mã nguồn: parkviet/parkviet trên GitHub
- Tài liệu lõi: spreecommerce.org/docs