You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Rails生产环境中用Pay Gem动态切换Stripe Connect实时/测试模式

解决方案:在生产环境中动态切换Pay Gem的Stripe实时/测试模式

针对Pay Gem 2.2.2版本,我们可以通过线程安全的动态配置覆盖+请求上下文绑定的方式,实现生产环境下同时支持Stripe实时与测试模式,且能根据用户选择动态切换密钥,无需依赖全局初始化器。


步骤1:配置两套Stripe密钥

首先在config/credentials/production.yml.enc中添加实时和测试环境的完整密钥配置:

production:
  stripe:
    live_public_key: "pk_live_xxx"
    live_secret_key: "sk_live_xxx"
    live_webhook_secret: "whsec_live_xxx"
    test_public_key: "pk_test_xxx"
    test_secret_key: "sk_test_xxx"
    test_webhook_secret: "whsec_test_xxx"

步骤2:创建模式切换服务类

创建一个工具类统一管理不同模式的配置读取:

# app/services/stripe_config_manager.rb
class StripeConfigManager
  class << self
    def config_for_mode(mode)
      case mode.to_sym
      when :live
        {
          public_key: Rails.application.credentials.production[:stripe][:live_public_key],
          secret_key: Rails.application.credentials.production[:stripe][:live_secret_key],
          webhook_secret: Rails.application.credentials.production[:stripe][:live_webhook_secret]
        }
      when :test
        {
          public_key: Rails.application.credentials.production[:stripe][:test_public_key],
          secret_key: Rails.application.credentials.production[:stripe][:test_secret_key],
          webhook_secret: Rails.application.credentials.production[:stripe][:test_webhook_secret]
        }
      else
        raise ArgumentError, "无效的Stripe模式:#{mode}"
      end
    end
  end
end

步骤3:重写Pay的Stripe适配器,支持动态密钥

修改Pay的Stripe适配器,让它优先从线程本地存储读取密钥(线程安全,避免全局污染):

# config/initializers/pay.rb
Pay.setup do |config|
  # 设置默认的实时环境密钥作为 fallback
  config.stripe.secret_key = Rails.application.credentials.production[:stripe][:live_secret_key]
  config.stripe.public_key = Rails.application.credentials.production[:stripe][:live_public_key]
end

# 重写适配器的密钥获取逻辑,支持线程动态配置
module Pay
  module Stripe
    class Adapter
      def secret_key
        Thread.current[:pay_stripe_secret_key] || super
      end

      def public_key
        Thread.current[:pay_stripe_public_key] || super
      end
    end
  end
end

步骤4:在请求上下文绑定模式

在控制器中通过around_action设置当前请求的Stripe模式,并确保线程变量在请求结束后清理:

# app/controllers/application_controller.rb
around_action :bind_stripe_mode

private

def bind_stripe_mode
  # 获取用户选择的模式:可以从请求参数、用户偏好设置等渠道获取
  stripe_mode = params[:stripe_mode] || current_user&.stripe_preference || :live
  config = StripeConfigManager.config_for_mode(stripe_mode)

  # 将密钥存入线程本地存储
  Thread.current[:pay_stripe_secret_key] = config[:secret_key]
  Thread.current[:pay_stripe_public_key] = config[:public_key]

  yield
ensure
  # 请求结束后清理线程变量,避免干扰后续请求
  Thread.current[:pay_stripe_secret_key] = nil
  Thread.current[:pay_stripe_public_key] = nil
end

步骤5:视图与Webhook适配

视图中输出对应模式的公钥

直接使用Pay提供的方法,会自动读取当前线程的公钥:

<script src="https://js.stripe.com/v3/"></script>
<script>
  const stripe = Stripe('<%= Pay::Stripe.public_key %>');
</script>

分开处理实时/测试Webhook

配置两个独立的Webhook端点,根据路径区分模式并验证签名:

# config/routes.rb
post '/stripe/webhook/live', to: 'stripe_webhooks#create'
post '/stripe/webhook/test', to: 'stripe_webhooks#create'
# app/controllers/stripe_webhooks_controller.rb
def create
  # 根据请求路径判断模式
  mode = request.path.include?('test') ? :test : :live
  webhook_secret = StripeConfigManager.config_for_mode(mode)[:webhook_secret]

  # 验证Stripe签名
  event = Stripe::Webhook.construct_event(
    request.body.read,
    request.headers['Stripe-Signature'],
    webhook_secret
  )

  # 处理Webhook事件(如订单确认、退款等)
  handle_stripe_event(event)

  head :ok
end

核心原理说明

  1. 线程安全隔离:通过Thread.current存储当前请求的密钥,避免全局修改Stripe API密钥导致的多请求冲突;
  2. 适配器重写:绕过Pay默认的Rails.env绑定逻辑,让密钥读取优先从线程变量获取;
  3. 请求上下文绑定:在每个请求生命周期内动态设置模式,支持用户自定义选择(如测试用户用测试模式,正式用户用实时模式)。

内容的提问来源于stack exchange,提问作者Sandhiya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 17:21:16