来自 Stripe Webhooks 的多语言邮件
在全球范围内扩展 SaaS 会遇到隐藏的陷阱。我们在 Stripe webhooks 中发现了一个。
我们的系统向说英语的用户发送了日语版的购买确认、续费和失败通知。这个 Bug 存在了好几个月,因为它一直很隐蔽。
我们通过从货币推断语言解决了这个问题。
我们考虑了三种设计方案:
- 方案 A:在数据库中存储语言。这需要对旧用户进行数据迁移和回填。
- 方案 B:从 Stripe API 获取。这会增加额外的 API 调用,而且许多客户并没有设置首选语言区域。
- 方案 C:使用 webhook payload 中的货币。这种方式是免费的,不需要更改数据库,并且可以立即对现有用户生效。
我们选择了方案 C。货币在购买时刻是一个固定的信号。如果用户使用 USD 支付,他们会收到英语邮件;如果使用 JPY 支付,他们会收到日语邮件。
逻辑很简单:
function lang_from_currency(string $currency): string {
$en_currencies = ['usd'];
return in_array(strtolower($currency), $en_currencies, true) ? 'en' : 'ja';
}
这适用于所有四种主要的 Stripe 事件:
- checkout.session.completed
- invoice.payment_succeeded
- invoice.payment_failed
- customer.subscription.updated
我们还发现了 PHP 中的一个技术陷阱。
使用 mb_language('Japanese') 会将主题行编码为 ISO-2022-JP。如果你在这种设置下发送英语主题行,Gmail 和 Outlook 会将其视为异常编码。这会提高你的垃圾邮件评分。
解决方法是根据语言切换编码:
mb_language($lang === 'en' ? 'uni' : 'Japanese');
使用 'uni' 会采用 UTF-8 Base64 编码。这能让你的邮件避免进入垃圾邮件箱。
从这次修复中得到的三个教训:
- 利用事件 payload。如果数据已经存在于 webhook 中,就不要去动数据库。这可以降低风险并减少维护工作。
- 注意编码。如果你支持多种语言,请确保主题行的编码与内容匹配,以避免触发垃圾邮件过滤器。
- 审计硬编码值。当你走向国际化时,检查一下你的通知函数是否包含硬编码的语言设置。
无状态设计使你的系统更易于维护,也更不容易出错。
