新的 Stripe Checkout 的 Rails 工作流程是:
条纹结帐会议
这是我用于订阅服务的示例客户端/服务器 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 %>
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.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'
我必须将获取“成功”和“取消”路由放在订阅资源上方,以便它们正确解析。
最后,将success
和cancel
回调添加到您的控制器并使用它们做任何您需要的事情。例如:
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 个,以保持我的订阅系统顺利运行。
祝你好运!