Rails中的新Stripe SCA检出流程 [英] New Stripe SCA checkout flow in Rails

查看:123
本文介绍了Rails中的新Stripe SCA检出流程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在努力将Rails应用切换到新的Stripe结帐流程,以适应新的SCA规定.

I'm struggling with switching my Rails app to the new Stripe checkout flow to accommodate the new SCA regulation.

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

I want to implement the simple dynamic product routine found in this link: https://stripe.com/docs/payments/checkout/migration#api-products-after

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

I can't figure out where to put the different pieces of code. What should go in: - controller -> in which methods
- views -> the event show view for example. The form/button the user will click
- javascript -> how to pass the right session id - controller again -> implementing the success and error use cases

Stripe技术支持刚刚将我发送到上面的文档链接,因此,在此我将非常感谢您的帮助.

The Stripe tech support just sent me to the documentation link above so I would really appreciate some help here.

推荐答案

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

The Rails workflow for the new Stripe Checkout is:

  • 创建Stripe Checkout会话并检索session.id(.rb)

  • Create a Stripe Checkout Session and retrieve the session.id (.rb)

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

Pass the session.id to a js initializer to redirect to Stripe Checkout

条纹结帐环节

这是一个示例客户端/服务器条纹签出实现我正在用于订阅服务.除了要引用条纹产品"而不是计划"之外,您的步骤基本相同:

This is a sample client/server Stripe Checkout implementation that I'm using for a Subscription service. Your steps would essentially be the same except you would be referencing a Stripe Product rather than a Plan:

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 Plan(或Product)信息作为参数传递.就您而言,您可以传递Stripe Checkout会话所需的任何Product参数:

In my case, my index.html.erb template has a link to "Get more info..." about a particular subscription. That link goes to the controller's :new action, passing the relevant Stripe Plan (or Product) info as params. In your case, you might pass whatever Product params necessary for your Stripe Checkout session:

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

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

The :new controller action will return your Stripe CHECKOUT_SESSION_ID for use in your template. (Also, note that this controller is bypassing logged_in? and forgery protection to allow for the Stripe Webhook POST response to your Checkout Session. You'll need to address your particular authorization scheme here)

现在,您需要调用Stripe API.我正在像这样的Stripe服务中做到这一点:

Now, you need to call the Stripe API. I'm doing this in a Stripe service like so:

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的调用成功,则控制器:new操作中的session对象现在将包含您的会话数据:

If your call to Stripe was successful the session object in your controller :new action will now contain your session data:

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

JS脚本加载

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

You'll be using the session.id in your link to redirect to the Stripe Checkout page:

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>

上面的模板正在做一些重要的事情:

The above template is doing several important things:

  • 加载条纹v3 js脚本(取决于您如何加载此脚本/在何处加载该脚本.如果使用content_for,则您的layout.html文件将具有相应的块:
  • Load the stripe v3 js script (it's up to you how/where you load this script. If using content_for then your layout.html file would have a corresponding block:

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

  • 将@ stripe_session.id从controller:new动作传递到<div>元素的data-stripe-id属性.

  • Pass the @stripe_session.id from the controller :new action to the data-stripe-id attribute of your <div> element.

添加事件监听器以使subscribe-btn重定向到Stripe Checkout,并传入@ stripe_session.id

Add the EventListener for the subscribe-btn to redirect to Stripe Checkout, passing in the @stripe_session.id

替代JS代码的方法

还有其他加载js脚本的方法.就个人而言,我喜欢使用刺激进行此类操作.例如,不是使用content_for并使用<script>标签加载js,而是使用subscription_controller.js刺激控制器来完成工作:

There are other ways to load the js scripts. Personally, I love using Stimulus for this sort of thing. For example, rather than loading js with content_for and using <script> tags I have a subscription_controller.js Stimulus Controller doing the work:

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应用程序添加/初始化刺激才能使以上功能起作用...
  • 条纹网页

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

    Stripe will POST to your webhook endpoints (if you configure them to). If listening for them, you configure some routes (see below) to handle them. You can also do this in a service of your choosing. For example, create another file in your app/services folder:

    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仪表板中配置的传入Stripe Webhook.如果成功,event属性将包含您当前正在摄取的任何Webhook的JSON响应.

    This file will receive and authorize the incoming Stripe webhook that you configured in your Stripe Dashboard. If successful, event attribute will contain the JSON response of whichever webhook you're ingesting at the moment.

    这允许您基于event.type调用各种方法,这将是Webhook的名称. event.data.object将带您进入特定的响应数据.

    That allows you to call various methods based on the event.type which will be the name of the webhook. event.data.object will get you into specific response data.

    铁路路线

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

    None of the above will work without the proper routes!

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

    我必须放置获取成功"&在订阅资源上方取消"路由,以使其正常解决.

    I had to place the get 'success' & 'cancel' routes above the subscription resources for them to resolve properly.

    最后,将successcancel回调添加到您的控制器,并使用它们进行所需的操作.例如:

    And, finally, add the success and cancel callbacks to your controller and do whatever you need with them. For example:

    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文件.如果您愿意,cancel操作也可以重定向或为此创建一个html.erb文件.

    Note: you'll need a corresponding success.html.erb file. The cancel action can redirect or create an html.erb file for that too if you'd like.

    因此,进行所有设置有点不容易.但是,随着管道的畅通,有很多很酷的可能性来处理各种生命周期事件/webhooks.目前,我正在听大约15个消息,以使我的订阅系统保持​​平稳运行.

    So, it was kind of a bear to get it all setup. However, with the plumbing out of the way there are lots of cool possibilities to handle all sorts of lifecycle events/webhooks. Currently, I'm listening for about 15 of them to keep my subscription system running smoothly.

    祝你好运!

    这篇关于Rails中的新Stripe SCA检出流程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆