Nhảy đến nội dung

Tại sao tôi không dùng Drupal Commerce chỉ để làm tính năng Donation?

Submitted by Ẩn danh (chưa được kiểm chứng) on

Khi cần gắn một nút "Ủng hộ tôi một ly cà phê" hay xây dựng trang quyên góp trên Drupal 10/11, phản xạ tự nhiên của nhiều developer là gõ lệnh: composer require drupal/commerce.

Drupal Commerce là một cỗ máy tuyệt vời. Nhưng trong project gần đây của mình, tôi đã quyết định dừng lại và tự viết một module riêng mang tên Simple Stripe Donation. Dưới đây là những quyết định kiến trúc đằng sau lựa chọn đó.

1. Domain Problem vs. Domain Model

Vấn đề cốt lõi không phải là Drupal Commerce "làm chậm request" hay "quá nặng", mà là: Drupal Commerce đưa vào một domain model và ecosystem lớn hơn nhiều so với domain problem cần giải quyết.

Nếu domain của bài toán chỉ đơn giản là:

Donation → Payment → Confirmation

Thì việc kéo theo bộ khung abstraction đồ sộ bao gồm:

Store → Order → Order Item → Cart → Customer Profile → Payment Gateway → Payment

sẽ tạo ra sự phức tạp không cần thiết (accidental complexity).

Việc duy trì cấu trúc dữ liệu khổng lồ này cho một tác vụ thanh toán một lần (one-time payment) làm tăng chi phí bảo trì hệ thống và overhead khi query dữ liệu.

2. Right Tool for the Right Domain

Tôi không có ý định chứng minh "Drupal Commerce tệ". Ngược lại, nó tỏa sáng khi bạn thực sự có nhu cầu về "Commerce". Quyết định kiến trúc luôn nằm ở việc chọn đúng công cụ cho đúng bài toán:

Use case Giải pháp phù hợp
E-commerce đầy đủ Drupal Commerce
Product catalog & Inventory Drupal Commerce
Cart + Order fulfillment Drupal Commerce
Subscription / Complex Payment Drupal Commerce (hoặc nền tảng tương đương)
Một lần donation Simple Stripe Donation
"Buy me a coffee" Simple Stripe Donation

3. Nguyên tắc: Let Stripe own the payment UI; let Drupal own the business integration

Trước đây, chúng ta thường cố gắng render UI nhập thẻ ngay trên website để giữ chân người dùng. Nhưng điều đó đồng nghĩa với việc bạn phải gánh chịu rủi ro về PCI DSS, xử lý responsive UI cho hàng loạt phương thức mới (Apple Pay, Google Pay).

Với kiến trúc tích hợp qua Stripe Hosted Checkout, tôi loại bỏ hoàn toàn trách nhiệm đó:

Drupal (Create Checkout Session) 
  │
  ▼
Stripe Checkout (User submits payment)
  │
  ▼
Stripe Server (Process & trigger Webhook)
  │
  ▼
Drupal (Handle Webhook)

Tôi không cần phải sở hữu phần UI thanh toán. Thông tin thẻ không hề chạm vào máy chủ ứng dụng. Nhiệm vụ của hệ thống backend Drupal bây giờ chỉ tập trung vào một thứ duy nhất: Xử lý integration thật tốt.

4. Kiến trúc Webhook: Trái tim của hệ thống thanh toán

Trong hệ thống thanh toán bất đồng bộ, webhook handling chính là nơi thể hiện năng lực kiến trúc. Đừng bao giờ tin tưởng tuyệt đối vào payload gửi đến mà không có kiểm chứng.

Luồng xử lý webhook trong module của tôi được thiết kế chặt chẽ như sau:

Stripe Webhook
            │
            ▼
     Verify Signature (Ngăn chặn giả mạo request)
            │
            ▼
Extract Event ID / Payment Intent ID
            │
            ▼
  Check Existing Record (Database lookup)
            │
       ┌────┴────┐
       │         │
     Exists   Not exists
       │         │
      200      Process
                 │
                 ▼
           Persist State (DB Transaction / Unique Constraints)
                 │
                 ▼
           Dispatch Event (Cho phép các module khác hook vào)

Để giải quyết triệt để bài toán Idempotency (Stripe có thể retry webhook nhiều lần do network lag), tôi không chỉ check bằng code PHP thuần túy, mà kết hợp thiết kế schema database với các ràng buộc duy nhất (Unique Constraints) trên trường session_id. Đồng thời, việc wrap quá trình lưu trữ trong DB Transaction đảm bảo không có state lấp lửng (partial save) nào xảy ra.

5. Tạm kết

Đôi khi, việc viết một đoạn code mới không phải để hệ thống làm được nhiều việc hơn, mà để ngăn hệ thống khỏi việc phải mang vác những thứ không thuộc về domain của nó.

⚙️ Implementation Để xem cách các khái niệm Webhook, Idempotency và Event Dispatcher được hiện thực hóa bằng code, bạn có thể tham khảo trực tiếp source code và case study thực tế của module tại: Simple Stripe Donation: Giải pháp thanh toán tinh gọn cho Drupal 10 & 11.

Phân tích quyết định kiến trúc: Khi nào nên dùng Drupal Commerce và tại sao một bài toán one-time donation lại cần một domain model tinh gọn hơn.

Simple Stripe Donation: Giải pháp thanh toán tinh gọn cho Drupal 10 & 11

Submitted by Ẩn danh (chưa được kiểm chứng) on
Simple Stripe Donation

Khi triển khai các trang blog cá nhân, website tổ chức phi lợi nhuận (NGO) hoặc trang cộng đồng trên Drupal, nhu cầu nhận tiền ủng hộ (donation) qua thẻ tín dụng là rất phổ biến. Tuy nhiên, giải pháp mặc định trong hệ sinh thái Drupal thường là cài đặt bộ Drupal Commerce.

Drupal Commerce là một nền tảng thương mại điện tử xuất sắc, nhưng nếu use case chỉ đơn giản là "người đọc muốn gửi tặng $5 ủng hộ tác giả", việc kéo theo toàn bộ hệ sinh thái commerce là một sự đánh đổi lớn về mặt kiến trúc và chi phí bảo trì.

Image
Simple Stripe Donation
  • Domain Complexity: Đưa vào abstraction của Store, Order, Order Item, Cart, Customer Profile... tạo ra nhiều domain complexity hơn mức use case thực sự cần.
  • PCI DSS Compliance: Việc tự xử lý form nhập thẻ trên website đòi hỏi các tiêu chuẩn bảo mật khắt khe.
  • Asynchronous Reliability: Các giao dịch thanh toán bất đồng bộ dễ gặp lỗi trùng lặp trạng thái (duplicate state) nếu không có cơ chế xử lý Webhook an toàn và Idempotency.

Thiết kế một module hoàn toàn độc lập, tách rời logic khỏi giỏ hàng/đơn hàng:

  • Offload Payment UI: Sử dụng Stripe-hosted Checkout giúp giảm đáng kể phạm vi trách nhiệm PCI DSS của ứng dụng Drupal, vì thông tin thẻ được nhập và xử lý trên giao diện thanh toán do Stripe cung cấp.
  • Event-driven & Idempotency: Xây dựng một webhook endpoint chuyên dụng để xử lý luồng bất đồng bộ từ Stripe, đảm bảo tính toàn vẹn dữ liệu.
  • Drupal Native Integration: Sử dụng Block API và Form API của Drupal Core để cung cấp UI linh hoạt cho quản trị viên.

 

Thay vì tự quản lý state của giao dịch trên giao diện, module đóng vai trò là một cầu nối (integration layer) giữa người dùng và Stripe.

[ User on Drupal Site ]
      │
      ▼
[ Drupal Controller / Service ]
      │
      ▼
[ Stripe Hosted Checkout ]
      │
      ▼
[ Stripe Server ]
      │
      │ Webhook
      ▼
[ Drupal Webhook Endpoint ]
      │
      ├── Idempotency
      ├── Store Donation
      └── Dispatch Event

Kiến trúc này cho thấy rõ sự phân tách trách nhiệm: Drupal lo business logic nội bộ, Stripe lo UI thanh toán và bảo mật thẻ, hai bên giao tiếp qua Webhook tin cậy.

 

  • Tinh gọn hệ thống: Loại bỏ dependency vào Drupal Commerce đối với use case donation một lần.
  • Bảo mật: Thông tin thẻ không được xử lý hoặc lưu trữ trên Drupal; quá trình thanh toán được thực hiện hoàn toàn trên Stripe Checkout.
  • Mã nguồn mở: Module được phát hành theo giấy phép GPL-2.0-or-later, sẵn sàng tích hợp vào bất kỳ dự án Drupal 10/11 nào.
  • Let Stripe own the payment UI; let Drupal own the business integration. Việc không cố gắng sở hữu những domain phức tạp (như xử lý thẻ tín dụng) giúp codebase nhẹ nhàng và đáng tin cậy hơn.
  • Design for evolution: Việc phát một custom event sau khi lưu transaction thành công giúp module dễ dàng mở rộng sau này (ví dụ: gửi mail, bắn log sang CRM) mà không cần sửa core logic.
  •  

Technical Deep Dive Tôi viết riêng một bài phân tích sâu hơn về kiến trúc và lý do tại sao tôi chọn xây dựng một module donation độc lập thay vì sử dụng Drupal Commerce. Đọc thêm tại: Tại sao tôi không dùng Drupal Commerce chỉ để làm tính năng Donation?.

Subscribe to Drupal 11