Email đa ngôn ngữ từ Stripe Webhooks
Mở rộng quy mô SaaS ra toàn cầu luôn tiềm ẩn những cạm bẫy. Chúng tôi đã phát hiện ra một cạm bẫy trong các Stripe webhook của mình.
Hệ thống của chúng tôi đã gửi các xác nhận mua hàng, gia hạn và thông báo thất bại bằng tiếng Nhật cho những người dùng nói tiếng Anh. Lỗi này đã tồn tại trong nhiều tháng vì nó diễn ra một cách âm thầm.
Chúng tôi đã giải quyết nó bằng cách suy luận ngôn ngữ từ loại tiền tệ.
Chúng tôi đã xem xét ba phương án thiết kế:
- Phương án A: Lưu trữ ngôn ngữ trong cơ sở dữ liệu. Việc này đòi hỏi phải thực hiện migration và backfill dữ liệu cho những người dùng cũ.
- Phương án B: Lấy từ Stripe API. Việc này làm tăng thêm các lượt gọi API và nhiều khách hàng không thiết lập ngôn ngữ ưu tiên (locale).
- Phương án C: Sử dụng loại tiền tệ trong webhook payload. Cách này miễn phí, không yêu cầu thay đổi cơ sở dữ liệu và có hiệu lực ngay lập tức với những người dùng hiện tại.
Chúng tôi đã chọn Phương án C. Tiền tệ là một tín hiệu cố định tại thời điểm mua hàng. Nếu người dùng thanh toán bằng USD, họ sẽ nhận được tiếng Anh. Nếu họ thanh toán bằng JPY, họ sẽ nhận được tiếng Nhật.
Logic rất đơn giản:
function lang_from_currency(string $currency): string {
$en_currencies = ['usd'];
return in_array(strtolower($currency), $en_currencies, true) ? 'en' : 'ja';
}
Cách này hoạt động với cả bốn sự kiện chính của Stripe:
- checkout.session.completed
- invoice.payment_succeeded
- invoice.payment_failed
- customer.subscription.updated
Chúng tôi cũng phát hiện ra một cạm bẫy kỹ thuật với PHP.
Việc sử dụng mb_language('Japanese') sẽ mã hóa tiêu đề theo chuẩn ISO-2022-JP. Nếu bạn gửi một dòng tiêu đề bằng tiếng Anh với thiết lập này, Gmail và Outlook sẽ coi đó là một kiểu mã hóa lạ. Điều này làm tăng điểm spam của bạn.
Cách khắc phục là chuyển đổi mã hóa dựa trên ngôn ngữ:
mb_language($lang === 'en' ? 'uni' : 'Japanese');
Sử dụng 'uni' sẽ dùng UTF-8 Base64. Điều này giúp email của bạn không bị rơi vào thư mục spam.
Ba bài học rút ra từ cách khắc phục này:
- Sử dụng event payload. Nếu dữ liệu đã có sẵn trong webhook, đừng động vào cơ sở dữ liệu của bạn. Điều này giúp giảm thiểu rủi ro và công sức bảo trì.
- Chú ý đến việc mã hóa. Nếu bạn hỗ trợ nhiều ngôn ngữ, hãy đảm bảo mã hóa dòng tiêu đề khớp với nội dung để tránh các bộ lọc spam.
- Kiểm tra các giá trị được viết cứng (hardcoded). Khi bạn mở rộng ra quốc tế, hãy kiểm tra xem các hàm thông báo của bạn có đang để cài đặt ngôn ngữ cố định hay không.
Một thiết kế không lưu trạng thái (stateless design) giúp hệ thống của bạn dễ bảo trì hơn và khó bị lỗi hơn.
