Cách thêm thanh toán vào ứng dụng: Checkout, gói đăng ký và Webhook
Cách đơn giản nhất để thêm thanh toán vào ứng dụng là Stripe: dùng Payment Link nếu bạn chỉ cần một nút "Mua" và có thể xử lý đơn hàng bằng tay, Stripe Checkout nếu ứng dụng cần biết ai đã thanh toán và phản ứng tự động, và Checkout ở chế độ đăng ký cùng cổng khách hàng (customer portal) của Stripe cho thanh toán định kỳ. Trong mọi trường hợp ngoại trừ đơn giản nhất, bạn cũng cần một webhook — một endpoint trên máy chủ mà Stripe gọi khi thanh toán thành công — và nên kiểm thử mọi thứ bằng khóa thử nghiệm và thẻ thử của Stripe trước khi chuyển sang khóa thật.
Hướng dẫn này giải thích từng lựa chọn, phần webhook mà hầu hết các bài hướng dẫn bỏ qua, và những điều cần kiểm tra trước khi nhận tiền thật. Chi tiết về Stripe lấy từ tài liệu chính thức của Stripe, tính đến tháng 9 năm 2026.
Vì sao nên dùng nhà cung cấp thanh toán
Đừng bao giờ thu thập số thẻ trong biểu mẫu của riêng bạn. Xử lý dữ liệu thẻ thô khiến bạn phải tuân theo PCI DSS (tiêu chuẩn bảo mật của ngành thẻ) và là một rủi ro pháp lý nghiêm trọng. Một nhà cung cấp thanh toán như Stripe lưu trữ biểu mẫu thanh toán, lưu thẻ, xử lý xác thực với ngân hàng, và cho ứng dụng của bạn biết kết quả. Ứng dụng của bạn chỉ thấy được "khách hàng X đã trả cho Y".
Hướng dẫn này dùng Stripe vì nó được dùng rộng rãi, có tài liệu tốt và được hầu hết các công cụ xây ứng dụng bằng AI hỗ trợ. Còn có các nhà cung cấp khác, và một số đóng vai trò "merchant of record", tức là tự xử lý thuế bán hàng thay bạn; nếu bạn lo về tuân thủ thuế, mô hình đó đáng để so sánh.
Ba cách nhận thanh toán với Stripe
| Lựa chọn | Cần code | Ứng dụng có biết ai đã trả không? | Phù hợp nhất với |
|---|---|---|---|
| Payment Links | Không cần | Chỉ khi bạn thêm webhook | Bán vài sản phẩm cố định, quyên góp, kiểm tra nhu cầu |
| Checkout (một lần) | Code phía server để tạo session | Có, qua webhook | Cửa hàng, tải xuống trả phí, mua một lần |
| Checkout (đăng ký) | Code server cộng xử lý webhook | Có, qua webhook | SaaS, thành viên, mọi thứ định kỳ |
Payment Links
Payment Link là một trang thanh toán do Stripe lưu trữ, bạn tạo trong Stripe Dashboard mà không cần code. Bạn nhận được một URL có thể đặt sau một nút trong ứng dụng, chia sẻ trong email, hoặc hiển thị dưới dạng mã QR. Tài liệu Stripe cho biết Payment Link có thể bán một sản phẩm, một gói đăng ký, hoặc nhận quyên góp, và bạn có thể để khách hàng chọn số tiền phải trả hoặc giới hạn số lần mua.
Điểm bất lợi: ứng dụng của bạn không tự động biết rằng một người dùng đã đăng nhập cụ thể đã thanh toán. Với trang quyên góp hoặc cửa hàng nhỏ mà bạn tự xử lý đơn hàng từ thông báo email của Stripe, điều đó vẫn ổn. Nhưng với "trả tiền để mở khóa các tính năng Pro", thì không ổn.
Stripe Checkout
Checkout là một trang thanh toán mà máy chủ của bạn tạo theo yêu cầu cho một giao dịch cụ thể. Máy chủ của bạn cho Stripe biết mặt hàng nào đang được mua (và tùy chọn là khách hàng nào đang mua), Stripe trả về một URL, và bạn gửi khách hàng đến đó. Tài liệu Stripe mô tả nó hỗ trợ cả thanh toán một lần và thanh toán đăng ký, như một trang do Stripe lưu trữ đầy đủ, một trang nhúng vào website của bạn, hoặc một biểu mẫu nhúng.
Luồng xử lý:
- Khách hàng nhấn Mua trong ứng dụng của bạn.
- Máy chủ của bạn tạo một Checkout Session bằng secret key, kèm một ID liên kết nó với người dùng hoặc đơn hàng của bạn.
- Khách hàng được chuyển hướng đến Stripe và thanh toán.
- Stripe chuyển hướng họ về trang thành công của bạn.
- Stripe gửi một sự kiện
checkout.session.completedđến webhook của bạn, và máy chủ đánh dấu đơn hàng đã trả hoặc mở quyền truy cập.
Bước 2 phải diễn ra trên máy chủ, vì nó dùng secret key của bạn. Đó là lý do chính khiến Checkout cần một backend.
Gói đăng ký
Gói đăng ký dùng Checkout ở chế độ đăng ký với một mức giá định kỳ mà bạn thiết lập trong Stripe. Lần thanh toán đầu tiên hoạt động theo luồng trên. Sau đó, gia hạn, thẻ bị lỗi, nâng cấp và hủy diễn ra theo thời gian — thường là khi khách hàng không ở trong ứng dụng của bạn — nên ứng dụng chỉ biết về chúng qua webhook.
Để cho phép khách hàng tự quản lý gói đăng ký, Stripe cung cấp một customer portal được lưu trữ. Theo tài liệu của Stripe, khách hàng có thể cập nhật phương thức thanh toán và thông tin thanh toán, thay đổi hoặc hủy gói đăng ký, và xem cũng như tải hóa đơn tại đó. Bạn cấu hình nó trong Stripe Dashboard và thêm nút "Quản lý thanh toán" để chuyển khách hàng đến đó. Điều này giúp bạn không phải tự xây các màn hình đó.
Webhook: phần không thể bỏ qua
Webhook là một yêu cầu HTTP mà Stripe gửi đến máy chủ của bạn khi có điều gì đó xảy ra. (Xem webhook là gì để hiểu ý tưởng chung.) Hướng dẫn thực hiện đơn hàng của Stripe nói rõ: bạn không thể chỉ dựa vào trang thành công để thực hiện đơn hàng, vì khách hàng có thể thanh toán rồi mất kết nối trước khi trang đó tải xong. Với gói đăng ký và các phương thức thanh toán có độ trễ, webhook là bắt buộc.
Các sự kiện đáng xử lý
| Sự kiện | Nghĩa là gì | Ứng dụng của bạn làm gì |
|---|---|---|
checkout.session.completed | Checkout đã hoàn tất | Đánh dấu đơn hàng đã trả hoặc bắt đầu cấp quyền truy cập |
checkout.session.async_payment_succeeded | Một phương thức có độ trễ (như ghi nợ ngân hàng) được xác nhận sau đó | Thực hiện đơn hàng ngay |
checkout.session.async_payment_failed | Khoản thanh toán có độ trễ đó thất bại | Thông báo cho khách hàng; không thực hiện đơn hàng |
invoice.paid | Một khoản thanh toán gói đăng ký thành công, bao gồm cả gia hạn | Kéo dài quyền truy cập |
invoice.payment_failed | Một lần gia hạn thất bại | Cảnh báo người dùng; Stripe có thể thử lại |
customer.subscription.updated | Gói đã thay đổi, hoặc đã lên lịch hủy | Cập nhật gói của người dùng |
customer.subscription.deleted | Gói đăng ký đã kết thúc | Gỡ quyền truy cập trả phí |
Quy tắc cho một trình xử lý webhook đúng
Các quy tắc này lấy trực tiếp từ tài liệu webhook của Stripe, và các trình xử lý do AI tạo ra đôi khi bỏ sót một trong số đó:
- Xác minh chữ ký. Mỗi sự kiện có một header
Stripe-Signature. Trình xử lý của bạn kiểm tra nó bằng signing secret của endpoint (bắt đầu bằngwhsec_). Nếu không làm vậy, bất kỳ ai cũng có thể gửi một thông báo "thanh toán thành công" giả đến máy chủ của bạn. - Dùng nội dung thô của request. Kiểm tra chữ ký sẽ thất bại nếu framework của bạn phân tích hoặc sửa đổi nội dung trước.
- Trả về phản hồi 2xx nhanh chóng, rồi mới làm các việc chậm hơn. Hết thời gian chờ được tính là thất bại.
- Xử lý trùng lặp. Stripe có thể gửi cùng một sự kiện nhiều hơn một lần. Ghi lại các ID sự kiện đã xử lý, hoặc làm cho việc thực hiện đơn hàng an toàn khi chạy hai lần.
- Đừng giả định thứ tự. Stripe không đảm bảo các sự kiện đến theo đúng thứ tự chúng đã xảy ra.
- Chuẩn bị cho việc thử lại. Ở chế độ chính thức, Stripe thử lại các lần gửi thất bại trong tối đa ba ngày, nên một sự cố ngắn không làm mất khoản thanh toán nào — nhưng trình xử lý của bạn phải đối phó được với các sự kiện đến muộn.
Webhook cần một địa chỉ HTTPS công khai, nên thường chỉ hoạt động khi ứng dụng của bạn đã được publish (CLI của Stripe có thể chuyển tiếp sự kiện đến máy cục bộ cho lập trình viên).
Một prompt bao quát toàn bộ luồng
Thêm mua một lần bằng Stripe Checkout cho "Pro pack" (price ID trong STRIPE_PRICE_ID). Tạo Checkout Session trên máy chủ bằng STRIPE_SECRET_KEY từ secrets, và đưa ID của người dùng đã đăng nhập vào session. Thêm endpoint POST /api/stripe/webhook xác minh chữ ký Stripe bằng STRIPE_WEBHOOK_SECRET trên nội dung thô của request, xử lý checkout.session.completed, và đánh dấu người dùng đó đã có Pro pack. Đảm bảo an toàn nếu cùng một sự kiện đến hai lần. Thêm trang thành công và trang hủy. Không bao giờ để lộ secret key cho trình duyệt.
Với gói đăng ký, thêm: "Dùng chế độ đăng ký với STRIPE_PRICE_ID. Xử lý invoice.paid, invoice.payment_failed, customer.subscription.updated và customer.subscription.deleted. Thêm nút Quản lý thanh toán mở customer portal của Stripe."
Chế độ thử nghiệm
Stripe có môi trường sandbox (thử nghiệm) và môi trường chính thức tách biệt, với các khóa riêng. Khóa sandbox bắt đầu bằng pk_test_, sk_test_ hoặc rk_test_; khóa chính thức bắt đầu bằng pk_live_, sk_live_ hoặc rk_live_. Giao dịch thử không chuyển tiền.
Các thẻ thử hữu ích từ tài liệu kiểm thử của Stripe (ngày hết hạn trong tương lai, mã CVC ba chữ số bất kỳ):
| Số thẻ | Kết quả |
|---|---|
4242 4242 4242 4242 | Thanh toán thành công |
4000 0000 0000 0002 | Thẻ bị từ chối |
4000 0000 0000 3220 | Yêu cầu xác thực 3D Secure, sau đó thành công |
Ít nhất hãy kiểm tra:
- Một thanh toán thành công mở khóa đúng thứ cho đúng người dùng
- Thẻ bị từ chối hiển thị thông báo rõ ràng và không mở khóa gì
- Đóng tab trình duyệt ngay sau khi thanh toán vẫn dẫn đến việc có quyền truy cập (điều này chứng minh webhook hoạt động, chứ không chỉ trang thành công)
- Hủy trên trang của Stripe sẽ đưa người dùng quay về ứng dụng mà không bị tính phí
- Với gói đăng ký: hủy trong customer portal và xác nhận quyền truy cập kết thúc đúng lúc
Đưa lên chính thức
- Kích hoạt tài khoản Stripe với thông tin doanh nghiệp và ngân hàng của bạn.
- Thay bằng khóa chính thức trong secrets của ứng dụng. Stripe khuyến nghị dùng khóa giới hạn chỉ với các quyền mà tích hợp cần, thay vì một secret key không giới hạn.
- Tạo endpoint webhook chính thức trỏ đến ứng dụng đã publish. Nó có signing secret riêng, khác với bản thử — hãy cập nhật.
- Tạo lại sản phẩm và giá ở chế độ chính thức; các ID của chế độ thử không tồn tại ở đó.
- Thực hiện một giao dịch thật nhỏ và hoàn tiền.
Hãy giữ mọi khóa trong secrets của dự án, không bao giờ để trong code hay chat. Danh sách kiểm tra bảo mật của chúng tôi đề cập chi tiết hơn về khóa và xác minh webhook.
Thuế, hoàn tiền và giấy tờ
Thanh toán kéo theo những nghĩa vụ mà code không giải quyết được. Đây không phải lời tư vấn thuế; hãy hỏi kế toán viên.
- Thuế bán hàng, VAT và GST. Tùy thuộc vào nơi bạn và khách hàng ở, bạn có thể cần đăng ký, thu và nộp thuế. Stripe Tax có thể tính và thu thuế khi thanh toán, theo dõi nơi doanh số của bạn có thể phát sinh nghĩa vụ, và hỗ trợ đăng ký và khai báo — nhưng trách nhiệm vẫn thuộc về bạn. Các nhà cung cấp merchant-of-record là một lựa chọn thay thế, họ đảm nhận nhiều phần hơn trong việc này.
- Hoàn tiền và tranh chấp. Hãy quyết định chính sách hoàn tiền trước khi ra mắt và đưa nó lên website. Tranh chấp (chargeback) có thể xảy ra; hãy lưu giữ hồ sơ về những gì đã được mua và giao.
- Biên lai và hóa đơn. Stripe có thể gửi biên lai qua email; doanh nghiệp có thể cần hóa đơn đầy đủ.
- Phí. Mọi nhà cung cấp đều thu phí theo từng giao dịch. Hãy xem trang bảng giá hiện tại của nhà cung cấp thay vì dựa vào con số trong các bài viết.
Thiết lập nào phù hợp với ứng dụng của bạn
- Quyên góp hoặc vài sản phẩm: Payment Links. Xem cách xây website quyên góp cho tổ chức phi lợi nhuận.
- Cửa hàng có giỏ hàng: Checkout với các mục hàng và webhook. Xem cách xây cửa hàng trực tuyến.
- SaaS hoặc thành viên: Checkout ở chế độ đăng ký, webhook và customer portal. Xem cách xây MVP SaaS và cách định giá sản phẩm SaaS.
- Marketplace trả tiền cho người bán: một thiết lập khác, được quản lý chặt chẽ hơn (Stripe Connect hoặc tương tự). Hãy lên kế hoạch riêng cho nó.
Thêm thanh toán trong Mythex
Thanh toán trong các ứng dụng bạn xây trên Mythex dùng tài khoản Stripe và khóa của chính bạn; không có nút "Kết nối Stripe" một chạm cho ứng dụng của bạn. Luồng thông thường:
- Lấy khóa thử nghiệm của Stripe.
- Thêm
STRIPE_SECRET_KEY(và webhook secret sau) vào secrets của dự án — qua/secrets, cài đặt dự án, hoặc thẻ secrets mà agent hiển thị khi cần một khóa. - Yêu cầu agent tạo Checkout bằng một prompt tương tự như trên.
- Kiểm thử trong Preview với thẻ thử. Publish trước khi đăng ký webhook, vì nó cần URL công khai của bạn.
- Sau khi chuyển sang khóa chính thức, publish lại để ứng dụng chính thức nhận được chúng.
Xem Nhận thanh toán với Stripe và Xử lý webhook trong backend trong tài liệu.
Câu hỏi thường gặp
Cách dễ nhất để nhận thanh toán trong ứng dụng là gì?
Stripe Payment Link không cần viết code: bạn tạo nó trong Stripe Dashboard và liên kết đến nó từ ứng dụng. Nếu ứng dụng cần biết ai đã thanh toán và tự động mở khóa một thứ gì đó, hãy dùng Stripe Checkout với webhook.
Tôi có cần webhook để nhận thanh toán không?
Nếu ứng dụng của bạn phải phản ứng với một khoản thanh toán — mở khóa tài khoản, đánh dấu đơn hàng đã trả, xử lý gia hạn — thì có. Tài liệu của Stripe nói rằng bạn không thể chỉ dựa vào trang thành công, vì khách hàng có thể đã thanh toán mà không bao giờ đến được trang đó. Webhook cũng là bắt buộc đối với gói đăng ký.
Làm sao để thử thanh toán mà không mất tiền thật?
Dùng khóa API sandbox (test) của Stripe và các số thẻ thử nghiệm, chẳng hạn 4242 4242 4242 4242 với ngày hết hạn trong tương lai và mã CVC ba chữ số bất kỳ. Giao dịch thử không chuyển tiền thật.
Stripe có tự xử lý thuế bán hàng và VAT cho tôi không?
Stripe Tax có thể tính và thu thuế khi thanh toán, đồng thời theo dõi nơi bạn có thể cần đăng ký, nhưng bạn vẫn chịu trách nhiệm về nghĩa vụ thuế của mình. Hãy hỏi kế toán viên về trường hợp của bạn.