首页 > 解决方案 > 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'
...

在修复此身份验证错误后,目前有许多硬编码密钥需要移动并存储在更安全的地方

标签: ruby-on-railsstripe-payments

解决方案


我暂时将 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

推荐阅读