Rails中新的Stripe SCA检出流程

时间:2019-09-11 08:09:33

标签: ruby-on-rails stripe-payments psd2 stripe-checkout

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

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

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

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

2 个答案:

答案 0 :(得分:2)

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

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

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

STRIPE CHECKOUT SESSION

这是我用于订阅服务的示例client/server Stripe Checkout implementation。除了要引用“条纹产品”而不是“计划”之外,您的步骤基本相同:

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会话所需的任何产品参数:

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

:new控制器操作将返回您的Stripe CHECKOUT_SESSION_ID,供您在模板中使用。 (还要注意,该控制器正在绕过login_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的调用成功,则控制器:new操作中的session对象现在将包含您的会话数据:

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从controller:new操作传递到<div>元素的data-stripe-id属性。

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

替代JS代码的方法

还有其他加载js脚本的方法。就个人而言,我喜欢将Stimulus用于此类操作。例如,与使用content_for加载js并使用<script>标签不同,我使用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仪表板中配置的传入Stripe Webhook。如果成功,event属性将包含您当前正在摄取的任何Webhook的JSON响应。

这使您可以基于event.type调用各种方法,这些方法将成为Webhook的名称。 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文件。如果愿意,cancel操作也可以重定向或为此创建html.erb文件。

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

祝你好运!

答案 1 :(得分:0)

我不使用ruby,但是在创建会话时成功结帐完成时通过会话ID,只需在* _url之后添加“?session_id = {CHECKOUT_SESSION_ID}”, 不知道这是否是您的情况,但很乐意提供帮助

Add DROP TABLE / DROP VIEW

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