如何为订阅构建 SMS payments platform?
结论
这种流程通常称为运营商代扣(Direct Carrier Billing,DCB),早期也常通过 Premium SMS 实现。费用会计入用户的话费账单,或直接从预付费余额中扣除,并非由普通短信服务商收取。
Twilio 可以收发短信,但通常不能仅凭用户回复 “GO” 完成话费扣款或建立订阅。要在 Ruby on Rails 中实现这类系统,需要接入具备当地运营商结算能力的 DCB 聚合商,如 Boku、Bango、DIMOCO 等。各服务商支持的国家、运营商和订阅模式会不断调整,接入前需要逐项确认,不能只参考其宣传的”全球覆盖”数字。
如果费用不一定要计入手机账单,更实际的做法是使用 Stripe Billing 等支付平台管理订阅,再通过 Twilio 验证手机号并确认订阅。
推荐的系统结构
常见流程是:
- 用户输入手机号码。
- 系统识别号码所属的国家和运营商。
- DCB 服务商返回该号码是否支持话费支付。
- 页面向用户展示价格、扣费周期和取消方式。
- 服务商发送确认短信,或由应用通过 Twilio 发送提示。
- 用户回复
GO,或在运营商托管的页面上确认。 - DCB 服务商完成扣款,并通过 webhook 通知 Rails 应用。
- 应用收到成功通知后开通权限。
- 后续续费、取消、退款和扣款失败等状态也通过 webhook 同步。
这里最重要的一点是:**短信回复只能证明用户已授权,不能作为支付成功的依据。**开通服务前,必须等支付服务商确认扣款成功。
Rails 中的实现方式
建议优先使用服务商提供的托管支付页面或官方 SDK。运营商计费需要经过国家、运营商和业务类型等方面的审批。自行拼接 Twilio 短信和内部订单状态,并不能构成合法且可以结算的话费支付系统。
可以先建立一个不依赖具体服务商的订阅模型:
# db/migrate/xxxx_create_mobile_subscriptions.rb
class CreateMobileSubscriptions < ActiveRecord::Migration[7.1]
def change
create_table :mobile_subscriptions do |t|
t.references :user, null: false, foreign_key: true
t.string :provider, null: false
t.string :provider_subscription_id
t.string :phone_number, null: false
t.string :country_code, null: false
t.string :status, null: false, default: "pending"
t.string :currency
t.integer :amount_minor
t.datetime :current_period_ends_at
t.timestamps
end
add_index :mobile_subscriptions,
[:provider, :provider_subscription_id],
unique: true
end
end
创建订阅时,由 DCB 服务商生成支付会话。下面只演示接口结构,字段名和地址需要替换为所选服务商的真实 API:
class CarrierBillingClient
def create_subscription(phone_number:, amount_minor:, currency:, callback_url:)
# 使用服务商官方 SDK 或经过认证的 HTTP 客户端。
# 不要假设下面这些字段是任何特定服务商的正式参数。
provider_client.create_subscription(
phone_number: phone_number,
amount: amount_minor,
currency: currency,
callback_url: callback_url
)
end
private
def provider_client
@provider_client ||= SelectedProvider::Client.new(
api_key: Rails.application.credentials.dig(:carrier_billing, :api_key)
)
end
end
支付结果通过 webhook 更新:
class CarrierBillingWebhooksController < ActionController::API
def create
payload = request.raw_post
signature = request.headers["X-Provider-Signature"]
return head :unauthorized unless valid_signature?(payload, signature)
event = JSON.parse(payload)
process_event(event)
head :ok
end
private
def process_event(event)
event_id = event.fetch("id")
return if ProcessedWebhook.exists?(provider_event_id: event_id)
ApplicationRecord.transaction do
subscription = MobileSubscription.lock.find_by!(
provider: "selected_provider",
provider_subscription_id: event.fetch("subscription_id")
)
case event.fetch("type")
when "subscription.activated"
subscription.update!(status: "active")
when "subscription.renewed"
subscription.update!(
status: "active",
current_period_ends_at: Time.iso8601(event.fetch("period_ends_at"))
)
when "subscription.payment_failed"
subscription.update!(status: "past_due")
when "subscription.cancelled"
subscription.update!(status: "cancelled")
end
ProcessedWebhook.create!(provider_event_id: event_id)
end
end
def valid_signature?(payload, signature)
# 必须按照服务商文档实现签名验证和时间戳检查。
# 不应只比较一个普通请求参数。
SelectedProvider::Webhook.verify(payload, signature)
end
end
实际开发中还要处理 webhook 重放、乱序和重复通知。数据库应保存服务商事件 ID,并用唯一索引保证幂等性。
Twilio 在其中适合做什么
Twilio 可以用于:
- 验证手机号码;
- 发送订单状态和服务通知;
- 接收
GO、STOP、HELP等短信指令; - 将入站短信转发到 Rails webhook;
- 在不支持 DCB 的地区提供备用确认流程。
Twilio 的入站短信 webhook 不应直接把订阅状态设为 active。收到用户回复后,最多只能将状态改为 authorization_received,然后调用 DCB 服务商并等待扣款结果。
如果通过 Twilio 接收回复,还需要验证 Twilio 的请求签名:
validator = Twilio::Security::RequestValidator.new(
Rails.application.credentials.dig(:twilio, :auth_token)
)
valid = validator.validate(
request.original_url,
request.request_parameters,
request.headers["X-Twilio-Signature"]
)
return head :unauthorized unless valid
如何选择服务商
评估 DCB 平台时,不能只比较覆盖的国家数量,还要逐项确认:
- 目标国家是否已经正式上线,而非仍在规划中;
- 每个国家支持哪些移动运营商;
- 支持一次性支付、循环订阅,还是同时支持两者;
- 是否需要使用运营商托管的确认页面或进行双重确认;
- 单笔和每月扣款上限;
- 分成比例、结算周期、拒付和退款规则;
- 是否提供测试环境、带签名的 webhook 和对账接口;
- 是否允许所经营的内容类别;
- 用户取消订阅后,状态需要多长时间才能同步;
- 是否要求本地公司、税务登记或当地短代码。
DCB 的覆盖通常按照”国家 × 运营商 × 业务类型”计算。一个平台即使覆盖多个国家,也可能只有部分运营商支持循环订阅。若希望覆盖更多地区,通常需要选择一个主要聚合商,再为重点市场接入备用服务商,很难依靠单一 API 覆盖全球。
合规和产品注意事项
订阅确认页面和短信应明确展示价格、扣费周期、服务名称和取消方法。月费说明不能隐藏在首次购买之后,也不能使用含糊的措辞,让用户误以为回复短信只是为了验证号码。
此外还要处理:
- 保存用户的明确同意及相关证据;
- 提供
STOP、退订页面和客服渠道; - 处理未成年人及受限制内容;
- 遵守数据保护要求,并尽量减少手机号码的存储;
- 遵守当地消费者保护、短信营销和自动续费规定;
- 处理税费、运营商分成、退款和对账;
- 防范 SIM 更换、号码回收以及账户被他人使用的风险。
如果目标只是让用户通过手机快速完成订阅,而非从话费中扣款,那么标准订阅支付平台配合短信验证,通常覆盖范围更广,结算更透明,也更容易通过审核。只有当目标用户缺少银行卡,或当地市场高度依赖预付费手机余额时,DCB 才通常值得承担额外的接入和合规成本。
备注:内容仅供参考。
