Tôi vừa nhận được tin nhắn từ một bạn startup: "Sao server của tôi lỗi khi người dùng thanh toán?" Nghe quen thuộc không? Đó là lúc anh ta có 50 đơn hàng đang pending vì webhook từ payment gateway bị delay 30 phút, nhưng anh ta mới phát hiện ra khi check log lần đầu tiên.
Đó chính là vấn đề: hầu hết mọi người chỉ tập trung vào việc kết nối payment gateway, không bao giờ chuẩn bị cho các tình huống thực tế.
Chọn gateway: không phải lúc nào cũng là Stripe
Trên thực tế, nếu bạn chỉ nhìn vào Stripe hoặc PayPal, bạn đang bỏ lỡ một số điều thực tế ở Việt Nam. Stripe rất tốt, nhưng phí giao dịch khoảng 2.9% + $0.30 USD có thể khiến doanh số nhỏ hoài tía. VNPAY lại khác - lệ phí thường từ 0.5% đến 1.5%, và người dùng Việt Nam quen với nó hơn.
Mục tiêu ngành hiện tại:
- VNPAY: Phổ biến nhất ở Việt Nam, hỗ trợ ngân hàng nội địa tốt
- Momo: Có ~25 triệu người dùng, rất tiện cho mobile
- Stripe: Tốt nếu bạn cần quốc tế
- PayPal: Chậm chạp ở VN, phí cao
Tôi từng thấy một công ty chỉ dùng Stripe rồi phàn nàn tại sao conversion rate thấp. Sau khi thêm VNPAY + Momo, conversion tăng 35%. Con số nói lên tất cả.
Webhook và idempotency: nơi mọi người vấp ngã
Đây là phần mà không ai muốn nghe, nhưng phải nói: 99% lỗi thanh toán không phải do gateway, mà do cách bạn xử lý webhook.
Webhook từ payment gateway sẽ gửi lại nếu không nhận được response HTTP 200. Và nó có thể gửi nhiều lần. Nếu bạn không có idempotency key, bạn sẽ tạo ra bản ghi thanh toán trùng lặp, sau đó khách hàng phàn nàn và bạn lại phải refund bằng tay.
Cách đúng:
- Lưu transaction ID từ gateway
- Trước khi xử lý, kiểm tra xem transaction ID đó đã tồn tại chưa
- Response ngay lập tức, xử lý bất đồng bộ ở phía backend
- Ghi log mọi webhook đến (bạn sẽ cần debug)
Chia sẻ bài viết
Bài viết liên quan
Bạn cần tư vấn về công nghệ?
Đội ngũ Idflow luôn sẵn sàng hỗ trợ bạn trong hành trình chuyển đổi số.
POST /webhook/payment
- Check if transaction_id exists → return 200 OK immediately
- Queue async job to update user balance
- Persist transaction first, status = PROCESSING
- Then mark status = CONFIRMED
Tôi từng đọc blog của một startup nói: "Chúng tôi xử lý webhook đồng bộ, trực tiếp update database". Một tuần sau, cơ sở dữ liệu của họ bị lock, API chậm, khách phàn nàn. Ngay sau đó: webhook timeout, lỗi xử lý, khách bị charge hai lần. Rồi sau một tháng, họ mới refactor thành async. Đau đớn.
Tiền tệ, quốc gia, và những chi tiết bị quên lãng
Một lỗi phổ biến: lập trình viên giả định rằng mọi giao dịch đều tính bằng một loại tiền tệ. Sai rồi.
Nếu bạn chấp nhận thanh toán từ nhiều quốc gia:
- Stripe charges theo tiền tệ khác nhau (USD, VND, JPY, v.v.)
- Tỷ giá hối đoái thay đổi
- Một số gateway không hỗ trợ tiền tệ nhất định
Cách tốt nhất? Luôn lưu dữ liệu gốc trong loại tiền tệ của giao dịch, không bao giờ quy đổi.
Ví dụ: Khách hàng ở Nhật Bản thanh toán 10,000 JPY. Bạn nhận 10,000 JPY từ gateway, lưu trong database là 10000 JPY (không phải 2.5 triệu VND ngay lập tức). Khi cần báo cáo, thì convert.
Rate limiting và DDoS: gateway của bạn mạnh mà máy chủ bạn yếu
Payment gateway thường có rate limit. Stripe cho phép khoảng 100 request/giây trên một account. Nếu bạn có khuyến mãi lớn và một lúc 500 người thanh toán cùng lúc, bạn sẽ hit limit.
Cách xử lý:
- Implement queue system (RabbitMQ, Bull, Celery)
- Retry với exponential backoff
- Monitor metric từ gateway (remaining quota)
Tôi thấy một ứng dụng Black Friday sale: mọi người tập trung vào UI/UX, nhưng không ai nghĩ về payment throughput. Kết quả: 1000 người cùng thanh toán lúc 12:05 AM, 40% giao dịch fail vì rate limit. Họ thua BlackFriday do lỗi kỹ thuật, không phải tiếp thị.
Bảo mật: PCI-DSS và những tiêu chuẩn khác
Nếu bạn lưu credit card number ngay cả một lần, bạn phải đạt PCI-DSS level 1 (rất đắt, rất phức tạp). Cách dễ hơn: đừng lưu card details. Dùng tokenization.
Gateway tạo token từ card
Bạn lưu token, không lưu card
Mỗi lần thanh toán, dùng token để charge
Stripe handle điều này tốt lắm. Nếu bạn tự implement, bạn sẽ ngốn 3 tháng chỉ để audit.
Testing: sandbox vs. production
Mọi gateway đều có sandbox environment. Nhưng sandbox không phải là reality.
Webhook delay: sandbox rồi vài ms, production rồi 5-10 giây
Rate limit: sandbox không có, production có
Currency handling: sandbox có thể không cover hết tiền tệ
Bạn phải test ở production (với real transaction, hoặc test card). Stripe cho phép test mode card như 4242 4242 4242 4242. Sau đó, bạn phải monitor metrics thực tế, chứ không chỉ ngồi nhà để imagination về "nó sẽ hoạt động".
Một insight cuối cùng
Thanh toán không phải là feature. Nó là business critical. Mỗi lỗi, mỗi delay = khách hàng mất tiền, hoặc ngược lại, công ty mất tiền.
Tôi gặp một CEO nói: "Tại sao chúng ta phải đầu tư 2 tuần vào payment system? Chúng ta có 100 việc khác cần làm." Sau 6 tháng, họ mất 15,000 USD do refund issues từ bug payment. Liều doping kỳ lạ: 2 tuần implement = cứu 15k USD.
Nếu bạn xây dựng sản phẩm có thanh toán điện tử, hãy tốn thời gian ở đây. Cách tốt nhất để integrate là ngồi xuống với tài liệu của gateway, viết test coverage cao, deploy cẩn thận, monitor từ ngày đầu—và những nơi như Idflow Technology chuyên tư vấn chính xác về những chi tiết này.