ruby-on-rails - Stripe SCA webhook 返回 401
问题描述
我正在尝试将新的 Stripe SCA 结帐集成到我的 ruby on rails 应用程序中。我按照 bnwpro 在这里写的说明开始。此时,我被重定向到 Stripe 付款页面,并在付款完成后返回我的应用程序。
我正在努力解决的问题是 webhook,在查看 Stripe CLI 时,我不断收到 401 ......
2020-03-15 17:22:47 --> charge.succeeded [evt_1GMzEgI1EtCroaCdHmRufFBK]
2020-03-15 17:22:47 --> payment_intent.succeeded [evt_1GMzEgI1EtCroaCdWzbnaczh]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCdHmRufFBK]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCdWzbnaczh]
2020-03-15 17:22:47 --> payment_method.attached [evt_1GMzEgI1EtCroaCd4quSj3kR]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCd4quSj3kR]
2020-03-15 17:22:47 --> customer.created [evt_1GMzEgI1EtCroaCdHpEmU1Ai]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEgI1EtCroaCdHpEmU1Ai]
2020-03-15 17:22:47 --> checkout.session.completed [evt_1GMzEhI1EtCroaCdf1x9gUUR]
2020-03-15 17:22:47 <-- [401] POST http://localhost:3000/stripe-webhooks [evt_1GMzEhI1EtCroaCdf1x9gUUR]
我的日志显示:
Started POST "/stripe-webhooks" for 127.0.0.1 at 2020-03-15 17:22:24 +0100
Processing by CheckoutsController#stripe_webhook as XML
Parameters: {"id"=>"evt_1GMzEJI1EtCroaCdrKXWf4gV", "object"=>"event", "api_version"=>"2018-02-28", "created"=>1584289343, "data"=>{"object"=>{"id"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG", "object"=>"payment_intent", "allowed_source_types"=>["card"], "amount"=>15000, "amount_capturable"=>0, "amount_received"=>0, "application"=>nil, "application_fee_amount"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges?payment_intent=pi_1GMzEJI1EtCroaCdY3wKhKNG"}, "client_secret"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG_secret_6djH08QObem66sqpCYdB8P9A4", "confirmation_method"=>"automatic", "created"=>1584289343, "currency"=>"usd", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "next_source_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>nil, "payment_method_options"=>{"card"=>{"installments"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"requires_source", "transfer_data"=>nil, "transfer_group"=>nil}}, "livemode"=>false, "pending_webhooks"=>2, "request"=>{"id"=>"req_iRi1XVG06fMcld", "idempotency_key"=>nil}, "type"=>"payment_intent.created", "checkout"=>{"id"=>"evt_1GMzEJI1EtCroaCdrKXWf4gV", "object"=>"event", "api_version"=>"2018-02-28", "created"=>1584289343, "data"=>{"object"=>{"id"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG", "object"=>"payment_intent", "allowed_source_types"=>["card"], "amount"=>15000, "amount_capturable"=>0, "amount_received"=>0, "application"=>nil, "application_fee_amount"=>nil, "canceled_at"=>nil, "cancellation_reason"=>nil, "capture_method"=>"automatic", "charges"=>{"object"=>"list", "data"=>[], "has_more"=>false, "total_count"=>0, "url"=>"/v1/charges?payment_intent=pi_1GMzEJI1EtCroaCdY3wKhKNG"}, "client_secret"=>"pi_1GMzEJI1EtCroaCdY3wKhKNG_secret_6djH08QObem66sqpCYdB8P9A4", "confirmation_method"=>"automatic", "created"=>1584289343, "currency"=>"usd", "customer"=>nil, "description"=>nil, "invoice"=>nil, "last_payment_error"=>nil, "livemode"=>false, "metadata"=>{}, "next_action"=>nil, "next_source_action"=>nil, "on_behalf_of"=>nil, "payment_method"=>nil, "payment_method_options"=>{"card"=>{"installments"=>nil, "request_three_d_secure"=>"automatic"}}, "payment_method_types"=>["card"], "receipt_email"=>nil, "review"=>nil, "setup_future_usage"=>nil, "shipping"=>nil, "source"=>nil, "statement_descriptor"=>nil, "statement_descriptor_suffix"=>nil, "status"=>"requires_source", "transfer_data"=>nil, "transfer_group"=>nil}}, "livemode"=>false, "pending_webhooks"=>2, "request"=>{"id"=>"req_iRi1XVG06fMcld", "idempotency_key"=>nil}, "type"=>"payment_intent.created"}}
Completed 401 Unauthorized in 1ms (ActiveRecord: 0.0ms)
导致 webhock 没有被触发,代码没有被执行。
谁能指出我正确的方向?这次 Google 和 Stack 搜索让我失望了……
编辑 1: 应用程序 > 控制器 > checkouts_controller.rb
class CheckoutsController < ApplicationController
require 'stripe'
before_action :setplans
skip_before_action :checkStatus
protect_from_forgery except: :stripe_webhook
Stripe.api_key = 'sk_test_...'
endpoint_secret = "whsec_..."
def index
end
def new
Stripe.api_key = 'sk_test_...'
@product = Product.find(params[:id])
session = Stripe::Checkout::Session.create(
payment_method_types: ['card'],
line_items: [{
name: @product.title,
amount: @product.price,
currency: 'usd',
quantity: 1,
}],
"metadata": {days: "#{@product.days}"},
success_url: 'http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'http://localhost:3000/cancel',
)
@stripe_session = session
end
def success
### the Stripe {CHECKOUT_SESSION_ID} will be available in params[:session_id]
if params[:session_id]
flash[:success] = "Thank you! Your license has been updated!"
else
flash[:danger] = "Session expired error..."
redirect_to checkouts_path
end
end
def cancel
redirect_to checkouts_path
end
def stripe_webhook
stripe_response = StripeWebhooks.subscription_events(request)
end
private
def setplans
@licenseplans = Product.where(active: true)
end
end
stripe_webhook 位于 app > services > stripe_webhooks.rb 下
class StripeWebhooks
require 'stripe'
STRIPE_API_KEY = "sk_test_..."
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)
## custom actions
end
def handle_checkout_session_completed(event)
## custom actions
end
def authorize_webhook
Stripe.api_key = 'sk_test_...'
endpoint_secret = "whsec_..."
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
配置 > 路由.rb
...
# Stripe SCA checkout routes
get 'success', to: 'checkouts#success'
get 'cancel', to: 'checkouts#cancel'
resources :checkouts
post '/stripe-webhooks', to: 'checkouts#stripe_webhook'
...
在修复此身份验证错误后,目前有许多硬编码密钥需要移动并存储在更安全的地方
解决方案
我暂时将 webhook 从服务移到了控制器,并再次添加了 skip_before_filter,但是将 endpoint_secret 键移到了 webhook 并获得了漂亮的 200 个!
现在我只需要提取元数据部分并在成功付款后采取适当的行动......
更新后的控制器:
class CheckoutsController < ApplicationController
require 'stripe'
before_action :setplans
skip_before_action :checkStatus
protect_from_forgery except: :stripe_webhook
skip_before_action :authenticate_user!, only: [:stripe_webhook]
Stripe.api_key = 'sk_test_...'
def index
end
def new
Stripe.api_key = 'sk_test_...'
@product = Product.find(params[:id])
session = Stripe::Checkout::Session.create(
payment_method_types: ['card'],
line_items: [{
name: @product.title,
amount: @product.price,
currency: 'usd',
quantity: 1,
}],
metadata: {days: :"#{@product.days.to_s}"},
success_url: 'http://localhost:3000/success?session_id={CHECKOUT_SESSION_ID}',
cancel_url: 'http://localhost:3000/cancel',
)
@stripe_session = session
end
def success
### the Stripe {CHECKOUT_SESSION_ID} will be available in params[:session_id]
if params[:session_id]
flash[:success] = "Thank you! Your license has been updated!"
else
flash[:danger] = "Session expired error..."
redirect_to checkouts_path
end
end
def cancel
redirect_to checkouts_path
end
def stripe_webhook
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
endpoint_secret = "whsec_..."
begin
event = Stripe::Webhook.construct_event(request.body.read, sig_header, endpoint_secret)
rescue JSON::ParserError
return head :bad_request
rescue Stripe::SignatureVerificationError
return head :bad_request
end
webhook_checkout_session_completed(event) if event['type'] == 'checkout.session.completed'
head :ok
end
private
def setplans
@licenseplans = Product.where(active: true)
end
def webhook_checkout_session_completed(event)
object = event['data']['object']
end
end
推荐阅读
- javascript - 如何在 React 中使用 JS 中的 CSS 更改悬停按钮中的文本
- javascript - 根据屏幕宽度不同的视频来源
- sql - SQL - 一个表中有两个 CHECK 条件
- c - 链接描述文件定义的变量
- python-3.x - 如何保持 HTTP 会话打开
- python - Python Selenium - 识别和单击 Angular 元素
- python - Group By 和 Count 嵌套字典列表中值的出现次数
- r - 我将如何使用 FFT 分析 R、Rstudio 中的音频波
- r - 提取整个流域多边形的网格化 (netcdf) 气候数据
- r - html_nodes 返回复杂表的空列表