2

我正在努力将我的 Rails 应用程序切换到新的 Stripe 结帐流程以适应新的 SCA 法规。

我想实现此链接中的简单动态产品例程:https ://stripe.com/docs/payments/checkout/migration#api-products-after

我不知道在哪里放置不同的代码。应该输入什么: - 控制器 -> 在哪些方法中
- 视图 -> 事件显示视图例如。用户将点击的表单/按钮
- javascript -> 如何传递正确的会话 id - 再次控制器 -> 实现成功和错误用例

Stripe 技术支持刚刚将我发送到上面的文档链接,因此我非常感谢这里的一些帮助。

4

2 回答 2

4

新的 Stripe Checkout 的 Rails 工作流程是:

  • 创建一个 Stripe Checkout Session 并检索 session.id (.rb)

  • 将 session.id 传递给 js 初始化程序以重定向到 Stripe Checkout

条纹结帐会议

这是我用于订阅服务的示例客户端/服务器 Stripe Checkout 实现。除了引用 Stripe 产品而不是计划之外,您的步骤基本相同:

subscriptions_controller.rb
STRIPE_API_KEY = Rails.application.credential.stripe[:secret_key]
skip_before_action :user_logged_in?, only: :stripe_webhook
protect_from_forgery except: :stripe_webhook

def stripe_webhook
  stripe_response = StripeWebhooks.subscription_events(request)
end

def index
end

def new
  session = StripeSession.new_session(STRIPE_API_KEY, current_user.email, params[:plan])
  @stripe_session = session
end

就我而言,我的index.html.erb模板有一个指向“获取更多信息...”的链接,以了解特定订阅。该链接转到控制器的 :new 操作,将相关的 Stripe 计划(或产品)信息作为参数传递。在您的情况下,您可以传递 Stripe Checkout 会话所需的任何 Product 参数:

subscriptions/index.html.erb
<%= link_to 'Get more info...', new_subscription_path(plan: 'plan_xxx' %>

:new 控制器操作将返回您的 Stripe CHECKOUT_SESSION_ID 以在您的模板中使用。(另外,请注意,此控制器绕过了 logged_in? 和伪造保护,以允许 Stripe Webhook POST 响应您的 Checkout 会话。您需要在此处解决您的特定授权方案)

现在,您需要调用 Stripe API。我在这样的 Stripe 服务中这样做:

app/services/stripe_session.rb
class StripeSession
  require 'stripe' ### make sure gem 'stripe' is in your Gemfile ###

  def self.new_session(key, user_email, plan)
    new(key, customer_email: user_email, plan: plan).new_checkout_session
  end

  def initialize(key, options={})
    @key = key
    @customer_email = options[:customer_email]
    @plan = options[:plan]
  end

  def new_checkout_session
    Stripe.api_key = key

    session = Stripe::Checkout::Session.create(
      customer_email: customer_email,
      payment_method_types: ['card'],
      subscription_data: {
        items: [{
          plan: plan,
        }],
      },
      success_url: 'https://yourapp.com/success?session_id={CHECKOUT_SESSION_ID}',
      cancel_url: 'https://yourapp.com/cancel'
    )
  end

  private
  attr_reader :key, :customer_email, :plan
end

如果您对 Stripe 的调用成功,则session控制器中的对象 :new 操作现在将包含您的会话数据:

def new
  session = StripeSession.new_session(STRIPE_API_KEY, current_user.email, params[:plan])
  @stripe_session = session
end

JS 脚本加载

您将在链接中使用 session.id 重定向到 Stripe Checkout 页面:

subscriptions/new.html.erb
<%= content_for :header do %>
  <script src="https://js.stripe.com/v3/" data-turbolinks-eval="false"></script>
<% end %>

<div data-stripe="<%= @stripe_session.id %>">
  <%= link_to 'Subscribe', '', class: 'subscribe-btn', remote: true %>
</div>

<script>
  const subscribeBtn = document.querySelector('.subscribe-btn')

  subscribeBtn.addEventListener('click', e => {
    e.preventDefault()

    const CHECKOUT_SESSION_ID = subscribeBtn.parentElement.dataset.stripe

    stripe.redirectToCheckout({
      sessionId: CHECKOUT_SESSION_ID
    }).then((result) => {
      // handle any result data you might need
      console.log(result.error.message)
    })
  }
</script>

上面的模板做了几件重要的事情:

  • 加载条带 v3 js 脚本(取决于您如何/在何处加载此脚本。如果使用,content_for那么您的 layout.html 文件将具有相应的块:

<% if content_for? :add_to_head %> <%= yield :add_to_head %> <% end %>

  • 将 @stripe_session.id 从控制器 :new 操作传递给<div>元素的 data-stripe-id 属性。

  • 为 subscribe-btn 添加 EventListener 以重定向到 Stripe Checkout,传入 @stripe_session.id

JS 脚本的另一种方法

还有其他方法可以加载 js 脚本。就个人而言,我喜欢将Stimulus用于此类事情。例如,我没有content_for使用<script>标签加载 js,而是使用了一个subscription_controller.js刺激控制器来完成这项工作:

subscriptions/new.html.erb (now becomes)
<div data-controller="subscription" data-session="<%= @stripe_session.id %>">
  <%= link_to 'Subscribe', '', class: 'btn', remote: true, 
    data: {action: 'subscription#redirectToCheckout', target: 'subscription.sessionID'}
  %>
</div>

---
(The Stimulus controller)
app/javascript/controllers/subscription_controller.js
import { Controller } from "stimulus"

export default class extends Controller {
  static targets = [ 'sessionID' ]

  get sessionID() {
    return this.sessionIDTarget.parentElement.dataset.session
  }

  initialize() {
    const script = document.createElement('script')
    script.src = "https://js.stripe.com/v3/"

    document.head.appendChild(script)
  }

  redirectToCheckout(e) {
    e.preventDefault()

    // grab your key securely in whichever way works for you
    const stripe = Stripe('pk_test_xxx')

    const CHECKOUT_SESSION_ID = this.sessionID

    stripe.redirectToCheckout({
        sessionId: CHECKOUT_SESSION_ID
    }).then((result) => {
        console.log(result.error.message)
    })
  }
}
  • 您需要在 Rails 应用程序中添加/初始化 Stimulus 才能使上述功能正常工作...

条纹网钩

Stripe 将 POST 到您的 webhook 端点(如果您将它们配置为)。如果监听它们,您可以配置一些routes(见下文)来处理它们。您也可以在您选择的服务中执行此操作。例如,在您的 app/services 文件夹中创建另一个文件:

app/services/stripe_webhooks.rb
class StripeWebhooks
  require 'stripe'
  STRIPE_API_KEY = Rails.application.credentials.stripe[:secret_key]

  def self.subscription_events(request)
    new(request).subscription_lifecycle_events
  end

  def initialize(request)
    @webhook_request = request
  end

  def subscription_lifecycle_events
    authorize_webhook

    case event.type
    when 'customer.created'
      handle_customer_created
    when 'checkout.session.completed'
      handle_checkout_session_completed
    when # etc.
    end
  end

  private

  attr_reader :webhook_request, :event

  def handle_customer_created(event)
    ## your work here
  end

  def handle_checkout_session_completed(event)
    ## your work here
  end

  def authorize_webhook
    Stripe.api_key = STRIPE_API_KEY

    endpoint_secret = Rails.application.credentials.stripe[:webhooks][:subscription]

    payload = webhook_request.body.read
    sig_header = webhook_request.env['HTTP_STRIPE_SIGNATURE']
    @event = nil

    begin
      @event = Stripe::Webhook.construct_event(
        payload, sig_header, endpoint_secret
      )
    rescue JSON::ParserError => e
      puts e.message
    rescue Stripe::SignatureVerificationError => e
      puts e.message
    end
  end
end

此文件将接收并授权您在 Stripe Dashboard 中配置的传入 Stripe webhook。如果成功,event属性将包含您目前正在摄取的任何 webhook 的 JSON 响应。

这允许您根据event.typewebhook 的名称调用各种方法。 event.data.object将带您进入特定的响应数据。

铁路路线

如果没有正确的路线,以上任何一项都不会起作用!

routes.rb
get 'success', to: 'subscriptions#success'
get 'cancel', to: 'subscriptions#cancel'
resources :subscriptions
post '/stripe-webhooks', to: 'subscriptions#stripe_webhook'

我必须将获取“成功”和“取消”路由放在订阅资源上方,以便它们正确解析。

最后,将successcancel回调添加到您的控制器并使用它们做任何您需要的事情。例如:

subscriptions_controller.rb
...
def success
  ### the Stripe {CHECKOUT_SESSION_ID} will be available in params[:session_id]

  if params[:session_id]
    flash.now[:success] = "Thanks for your Subscribing/Purchasing/Whatever..."
  else
    flash[:error] = "Session expired error...your implementation will vary"
    redirect_to subscriptions_path
  end
end

def cancel
  redirect_to subscriptions_path
end
...

注意:您需要相应的success.html.erb文件。如果您愿意,取消操作也可以重定向或创建一个 html.erb 文件。

所以,把它全部设置好是一种负担。然而,随着管道的使用,有很多很酷的可能性来处理各种生命周期事件/webhook。目前,我正在收听大约 15 个,以保持我的订阅系统顺利运行。

祝你好运!

于 2019-09-12T18:03:41.017 回答
0

我没有使用 ruby​​,但如果在创建会话时成功结帐完成时传递会话 ID,只需在 * _url 后添加“?session_id = {CHECKOUT_SESSION_ID}”,不知道这是否是您的情况但很高兴帮助

    mode : "subscription",
    customer : customerid,
    success_url: 'https://example.com/success?session_id={CHECKOUT_SESSION_ID}',
    cancel_url: 'https://example.com/cancel?session_id={CHECKOUT_SESSION_ID}',

另外,我建议观看此https://youtube.com/watch?v=8TNQL9x6Ntg

于 2019-09-11T09:38:23.680 回答