Cơ chế thu phí — Stripe làm thay những gì, và cấp quyền bằng Webhook

Bài viết này nằm trong khóa Kiến thức nền tảng về CNTT, xây dựng từ đầu những kiến thức CNTT thực tế tối thiểu mà bạn cần để lập trình và vibe coding.
Thông tin thẻ dừng lại ở dịch vụ trung gian thanh toán và không đi qua server của bạn. Các sơ đồ cho thấy vì sao thông báo Webhook mới là căn cứ để mở tính năng trả phí.

Bài viết này nói về cơ chế thu phí.

Nội dung chia thành 2 phần: cơ chế không để thông tin thẻ đi tới server của bạn, và cơ chế phản ánh kết quả thanh toán vào ứng dụng của bạn.

Thông tin thẻ đi qua đâu, và căn cứ nào để cấp quyền
Người dùngnhập thẻBên trung giannhận thông tinKết quả tớiqua thông báoSố thẻ dừnglại ở đâymy-app ghi lạiquyền sử dụng
Nhánh trên là thông tin thẻ, dừng lại bên trong dịch vụ trung gian thanh toán. Nhánh dưới là kết quả thanh toán, và my-app chỉ nhìn vào nhánh này để mở tính năng trả phí.

Số thẻ dừng ở nhánh trên và không tới được my-app.

Căn cứ duy nhất để mở tính năng trả phí là thông báo đến qua nhánh dưới.

Việc nhận số thẻ và việc trao đổi với công ty thẻ đều diễn ra bên phía dịch vụ trung gian thanh toán, phần còn lại ở my-app chỉ là ghi kết quả vào bản ghi.

Bên nhận thông tin thẻ là dịch vụ trung gian thanh toán, không phải server của bạn

Xử lý một khoản thanh toán cần số thẻ, ngày hết hạn và mã bảo mật.

PCI DSS (Payment Card Industry Data Security Standard) là tiêu chuẩn mà các đơn vị xử lý thông tin thẻ phải tuân thủ, còn dịch vụ trung gian thanh toán (payment service provider) là dịch vụ bên ngoài xử lý thanh toán thẻ thay bạn.

Stripe và PayPal là những dịch vụ trung gian thanh toán tiêu biểu.

Bạn đặt ô nhập ở đâu sẽ quyết định số thẻ đi qua những nơi nào.

Nơi đặt ô nhậpNơi số thẻ đi quaThuộc phạm vi PCI DSS
my-app nhận và lưu lạimy-app và dịch vụ trung gianmy-app cũng thuộc phạm vi
my-app nhận rồi chuyển tiếpmy-app và dịch vụ trung gianmy-app cũng thuộc phạm vi
Trang của dịch vụ trung gianChỉ dịch vụ trung gianPhạm vi của my-app là nhỏ nhất

Chỉ dòng thứ 3 là không cho số thẻ đi qua server của my-app.

Khi đó my-app chỉ giữ giá trị dùng để phân biệt khoản thanh toán, còn hai dòng trên để lại chính số thẻ hoặc bản ghi của lần truyền đó.

Dù không lưu mà chỉ chuyển tiếp thì giá trị vẫn đi qua server của bạn, và ngay khi nó đi qua, my-app cũng lọt vào phạm vi PCI DSS.

Để phạm vi mà my-app phải bảo vệ nhỏ nhất có thể, bạn giao luôn cả ô nhập cho dịch vụ trung gian thanh toán.

Trong một lần thanh toán, thông tin nào đi tới đâu
Một lần thanh toán (pay_88)
Chỉ nằm trong dịch vụ trung gian thanh toán
  • Số thẻ, ngày hết hạn, mã bảo mật
  • Bản ghi trao đổi với công ty thẻ
Tới được my-app
  • Giá trị phân biệt khoản thanh toán pay_88
  • Số tiền, và thanh toán đã xong hay chưa
  • Thanh toán của người dùng nào (u_1024)
Khung ngoài là một lần thanh toán, pay_88. Thông tin thẻ chỉ nằm trong khung phía trên. my-app chỉ giữ sự kiện thanh toán đã xong và giá trị dùng để phân biệt nó.

Số thẻ không đi qua my-app

Nếu bạn không tự nhận số thẻ thì trách nhiệm bảo vệ nó cũng không rơi về phía bạn.

Dù lưu lại hay chuyển tiếp ngay, chỉ cần đi qua server của bạn là trách nhiệm đó vẫn phát sinh, nên bạn giao luôn cả ô nhập cho dịch vụ trung gian thanh toán và chỉ để sự kiện thanh toán đã xong tới được my-app.

Thanh toán kết thúc trên trang thanh toán nằm ở tên miền của dịch vụ trung gian

Để thông tin thẻ không đi qua server của bạn, chính màn hình nhập được đặt ở nơi khác — đó là trang thanh toán dạng hosted (hosted checkout page, màn hình thanh toán do dịch vụ trung gian thanh toán chuẩn bị và hiển thị trên tên miền của họ).

Việc đưa người dùng sang màn hình ở một tên miền khác là chuyển hướng (redirect).

Ở bước thứ 2, bạn truyền số tiền và địa chỉ quay lại, còn dịch vụ trung gian thanh toán trả về URL của trang thanh toán dành riêng cho khoản thanh toán đó.

Việc trao đổi này thực hiện bằng cách gọi Web API, và bạn đính kèm API key.

Hai tên miền mà trình duyệt mở trong lúc thanh toán
Trình duyệt
Tên miền của my-app
  • Trang đăng ký gói trả phí
  • /thanks, mở sau khi thanh toán
Tên miền của dịch vụ trung gian thanh toán
  • Trang thanh toán để nhập số thẻ
  • my-app không nhìn thấy bên trong trang này
Khung ngoài là trình duyệt của người dùng. Khung trên là trang của my-app, khung dưới là trang của dịch vụ trung gian thanh toán; chuyển hướng đưa từ khung trên xuống khung dưới rồi quay lại.

Trang thanh toán còn được cung cấp dưới dạng một khung đặt ngay trong trang của my-app.

Cách hiển thị khác nhau, nhưng giá trị nhập vào vẫn đi tới cùng một nơi.

Hai cách đặt: chỗ nào khác nhau và chỗ nào không đổi
Chuyển sangtrang riêngURL là tên miềnbên trung gianSau thanh toánquay về /thanksĐặt khung ngaytrong trangURL là tên miềncủa my-appSau thanh toánvẫn ở trang đóCả hai cáchSố thẻ đi tớibên trung gianmy-app chỉ nhậnsự kiện đã trả
Hai hàng trên là khác biệt giữa hai cách đặt, hàng dưới cùng là những gì không đổi ở cả hai cách. Từ trái sang phải: cách đặt, URL hiện trên trình duyệt, và điều xảy ra sau khi thanh toán.

Trang thanh toán do dịch vụ trung gian thanh toán chuẩn bị

Màn hình nhập số thẻ thuộc về dịch vụ trung gian thanh toán, không phải my-app.

Người dùng được chuyển hướng sang màn hình đó, thanh toán xong thì quay về /thanks; kể cả khi bạn đặt nó dưới dạng một khung trong trang của mình, giá trị nhập vào vẫn đi tới cùng một nơi.

Căn cứ để cấp quyền là thông báo Webhook, không phải trang mà người dùng quay về

Bên quyết định có được hiện tính năng trả phí hay không là my-app chứ không phải dịch vụ trung gian thanh toán, và bản ghi đó là quyền sử dụng (entitlement, bản ghi phạm vi tính năng mà người dùng đã thanh toán được phép dùng).

Việc màn hình của người dùng quay về /thanks tự nó không cho my-app biết thanh toán đã xong hay chưa.

Hãy nhìn những gì xảy ra sau khi thanh toán theo 2 đường tách biệt.

Hai đường đi tới my-app sau khi thanh toán xong
Trình duyệt sang/thanksTrang hiện báođã hoàn tấtNếu đóng thìkhông có gì tớiThanh toán xongtrên trang đóServer gọi/webhookThông báo kèmchữ kýGửi lại trongthời hạn đã định
Từ một lần thanh toán tách ra 2 đường. Đường trên đi qua trình duyệt và có thể dừng giữa chừng; đường dưới đi giữa các server và tiếp tục cho tới khi nhận được phản hồi thành công.

Đường trên sẽ dừng lại ở đó nếu người dùng đóng trình duyệt ngay sau khi trả tiền.

Đường dưới đi giữa các server, nên nếu thông báo không tới thì nó được gửi lại theo số lần và thời hạn đã định.

Tùy bạn lấy đường nào làm căn cứ, cùng 3 người đó sẽ có kết quả cấp quyền khác nhau.

Điều đã xảy ra với người dùng đóNếu xét theo /thanksNếu xét theo /webhook
Đã trả tiền và đã mở /thanksĐược cấp quyềnĐược cấp quyền
Trả tiền xong rồi đóng trình duyệt ngayKhông được cấp quyềnĐược cấp quyền
Mở /thanks mà không trả tiềnĐược cấp quyềnKhông được cấp quyền

Nếu xét theo /thanks, người đóng trình duyệt thì không được cấp quyền, còn người chưa trả tiền lại được cấp quyền.

Căn cứ duy nhất để cấp quyền là thông báo trên đường dưới.

Trong thanh toán, nếu phản ánh cùng một thông báo 2 lần thì thời hạn sử dụng bị kéo dài gấp đôi.

Hãy ghi lại giá trị dùng để phân biệt từng thông báo, và giữ nguyên quyền khi cùng thông báo đó tới lần thứ 2.

my-app phân loại những gì tới /webhook như thế nào
Thông báo cóchữ ký saievt_7 tớilần đầuevt_7 đượcgửi lại/webhooknhận vàoBỏ đi; quyềngiữ nguyênCấp quyền vàghi evt_7Đã có bản ghinên bỏ qua
Cả 3 thứ bên trái đều tới cùng một /webhook. Chúng được nhận chung ở giữa, rồi chữ ký và giá trị phân biệt thông báo chia chúng thành 3 nhánh bên phải.

Thông báo có chữ ký không khớp thì bị bỏ đi mà không đổi quyền.

Khác biệt duy nhất giữa 2 trường hợp còn lại là thông báo đó đã được phản ánh hay chưa.

3 loại bản ghi đặt trong cơ sở dữ liệu của my-app
Cơ sở dữ liệu của my-app
Bản ghi người dùng
  • Giá trị phân biệt người dùng u_1024
  • Hash của mật khẩu
Bản ghi quyền sử dụng
  • Loại gói (paid)
  • Ngày hết hạn
  • Thuộc về người dùng nào (u_1024)
Bản ghi các thông báo đã nhận
  • Giá trị phân biệt thông báo evt_7
  • Thời điểm đã phản ánh
Khung ngoài là cơ sở dữ liệu của my-app. Bản ghi quyền ở giữa giữ loại gói và ngày hết hạn sử dụng. Nhờ bản ghi thông báo ở dưới, một thông báo được gửi lại sẽ không bị phản ánh 2 lần.

Dù bạn ẩn nút trả phí trên trang, đoạn mã đó vẫn tới thiết bị của người dùng.

Kiểm tra quyền ở phía server mỗi lần chính là phân quyền đã nói trong Cách chức năng đăng nhập hoạt động.

Số thẻ dừng lại trong khung ở giữa và không vào khung bên phải.

Quyền được cấp khi thông báo ở mũi tên 2 tới nơi, không phải khi trình duyệt của người dùng quay về.

Thứ báo tin thanh toán là thông báo từ server

Thứ báo rằng thanh toán đã xong là thông báo tới từ server của dịch vụ trung gian thanh toán, không phải trang mà người dùng quay về.

Bạn đối chiếu chữ ký của thứ nhận được, giữ nguyên quyền nếu đó là thông báo đã phản ánh, ghi kết quả vào cơ sở dữ liệu của mình, rồi quyết định có hiện tính năng trả phí hay không.

Với subscription, mỗi kỳ lại có một thông báo tới và quyền thay đổi

Hợp đồng tính phí theo tháng hoặc theo năm là subscription (hợp đồng mà việc thanh toán tự động lặp lại theo từng kỳ đã định).

Giữa thanh toán một lần và subscription, thứ thay đổi là số lần thông báo tới
Hình thức trả tiềnTrả một lầnChỉ 1 thông báoThời hạn quyềnkhông đổiSubscriptionTới nhiều lầntheo từng kỳThời hạn dời tớilần trả kế tiếp
Cột trái là hình thức thanh toán. Đường trên là trả một lần, đường dưới là subscription. Cả hai đều lấy thông báo Webhook làm căn cứ cấp quyền; khác nhau ở số lần thông báo tới và ở thời hạn.

Một khoản thanh toán lặp lại không phải lúc nào cũng thành công.

Ở hàng thất bại, dịch vụ trung gian thanh toán để cách vài ngày rồi thử thu lại.

Trong thời gian đó có giữ trạng thái dùng được hay cắt ngay là do my-app quyết định.

Thứ cho phép bạn kiểm tra trước khi phát hành là chế độ thử nghiệm (test mode, trạng thái do dịch vụ trung gian thanh toán chuẩn bị để kiểm tra hoạt động mà không phát sinh khoản thu thật).

API key cho thử nghiệm và cho bản chính thức là khác nhau, nên bạn chuyển đổi bằng giá trị của biến môi trường.

Quyết định cả thông báo gỡ quyền trước khi phát hành

Với subscription, mỗi khi tới kỳ lại có một lần thanh toán, và mỗi lần như vậy lại có một thông báo tới.

Nếu chỉ xử lý thông báo cấp quyền thì người dùng đã ngừng trả tiền vẫn dùng được, nên hãy quyết định cấp, gia hạn và gỡ bỏ cho từng thông báo tới, và chạy thử toàn bộ ở chế độ thử nghiệm trước khi phát hành.

QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Khi nhận thanh toán qua dịch vụ trung gian thanh toán, thông tin thẻ đi qua đâu?

Câu 2Bạn dựa vào điều nào làm căn cứ để kết luận thanh toán đã xong và cấp quyền cho người dùng?

Câu 3Khi có thông báo cho biết thanh toán thất bại trong hình thức thu phí định kỳ, ứng dụng của bạn làm gì?