【问题标题】:Stripe webhook error 400 bad request when using Heroku web address使用 Heroku 网址时,Stripe webhook 错误 400 错误请求
【发布时间】:2020-11-04 06:47:19
【问题描述】:

我已经在我的 Ruby on Rails 网站中集成了 Stripe。如果我用 ngrok 测试它,一切正常,但是当我使用我的 heroku 网站地址作为条带 webhook 时,它会引发 400 bad request 错误。如果我查看文档,它会说缺少必需的参数。这可能是因为我没有 ssl 证书吗?我在 Heroku 上的免费层,但 Heroku 网址以 https 开头......这不安全吗?我已经在 heroku 网站上输入了可发布密钥、秘密密钥和签名密钥。

Routes.rb

Rails.application.routes.draw do

  mount StripeEvent::Engine, at: '/stripe-webhooks'

  devise_for :users, controllers: {
    sessions: 'users/sessions',
    passwords: 'users/passwords',
    registrations: 'users/registrations'
  }

  scope '(:locale)', locale: /en|de/ do
    root to: 'pages#home'
    get '/about', to: 'pages#about', as: 'about'
    get '/shipping', to: 'pages#shipping', as: 'shipping'
    get '/privacypolicy', to: 'pages#privacypolicy', as: 'privacypolicy'
    get '/termsandconditions', to: 'pages#termsandconditions', as: 'termsandconditions'
    get '/success', to: 'pages#success'

    get 'contact', to: 'contacts#new', as: 'contact'


    resources :contacts, only: [:new, :create]


    get 'cart', to: 'carts#show', as: 'cart'
    delete 'carts', to: 'carts#destroy'
    delete 'cart_items/:id', to: 'cart_items#destroy', as: 'cart_items'

    resources :cart_items, only: [:create, :destroy] do
      member do
        get :add_quantity
        get :reduce_quantity
      end
    end

    post 'without-login', to: 'orders#without_login'

    resources :users
    resources :products do
      resources :cart_items, only: [:create]
    end
    resources :categories
    resources :orders, only: [:new, :show, :create] do
      resources :payments, only: :new
    end
  end
end

架构:

create_table "orders", force: :cascade do |t|
    t.string "product_sku"
    t.integer "amount_cents", default: 0, null: false
    t.string "checkout_session_id"
    t.bigint "user_id"
    t.datetime "created_at", precision: 6, null: false
    t.datetime "updated_at", precision: 6, null: false
    t.string "first_name"
    t.string "last_name"
    t.string "street_name"
    t.string "house_number"
    t.string "postal_code"
    t.string "city"
    t.string "country"
    t.string "email"
    t.text "comment"
    t.integer "price_cents", default: 0, null: false
    t.boolean "termsandconditions"
    t.string "status", default: "pending"
    t.index ["user_id"], name: "index_orders_on_user_id"
  end
class PaymentsController < ApplicationController
    skip_before_action :authenticate_user!

 def new
    if current_user
      @order = current_user.orders.where(status: 'pending').find(params[:order_id])
    else
      @order = Order.find(params[:order_id])
    end
    gon.order_amount = @order.amount_cents.to_f/100
    gon.order_id = @order.id
  end

end
class OrdersController < ApplicationController
  skip_before_action :authenticate_user!

  def new
    @order = Order.new
    @cart = current_cart
    # Storing the two constants in a gon variable to send data to the JS file
    gon.ceilings = Product::ORDER_CEILINGS
    gon.prices = Product::SHIPPING_PRICES
  end

  def create
    @order = Order.new(order_params)
    @cart = current_cart
    @cart.cart_items.each { |item| @order.cart_items << item }
    @order.amount = @cart.total_price

    shipping_costs = calculate_shipping_costs(params[:order][:country], @order.amount)
    @order.amount += Monetize.parse(shipping_costs)

    @order.user = current_user if current_user
    @order.email = current_user.email if current_user

    if @order.save
      save_user_address if params[:save_address].to_i == 1
      trigger_stripe(shipping_costs)
      cleanup_cart
      redirect_to new_order_payment_path(@order)
    else
      @cart = @current_cart
      render :new
    end
  end

  def show
    if current_user
      @order = current_user.orders.find(params[:id])
    else
      @order = Order.find(params[:id])
    end

    mail = OrderMailer.with(order: @order).confirmation
    mail.deliver_now
    # may need to change this for guest users- must check that their email address is saved to the database
  end

  def index
    @orders = current_user.orders
  end

  def without_login
    session[:without_login] = true
    redirect_to new_order_path
  end

  def submit
  end


  private

  def order_params
    params.require(:order).permit(:first_name, :last_name, :email, :street_name, :house_number, :postal_code, :city, :country, :comment)
  end

  def trigger_stripe(shipping_costs)
    stripe_session = Stripe::Checkout::Session.create(
      payment_method_types: ['card'],
      customer_email: customer_email,
      locale: I18n.locale.to_s,
      line_items: stripe_line_items(@order.cart_items, shipping_costs),
      success_url: order_url(@order),
      cancel_url: order_url(@order)
    )
    @order.update(checkout_session_id: stripe_session.id)
  end

  def cleanup_cart
    @cart.cart_items.each { |item| item.update(cart_id: nil) }
    Cart.destroy(session[:cart_id])
    session[:cart_id] = nil
  end

  def stripe_line_items(order_items, shipping_costs)
    all_items = []

    order_items.each do |item|
      item_hash = {
        name: item.product.title,
        amount: (item.total_price.amount * 100).to_i / item.quantity,
        quantity: item.quantity,
        currency: 'eur'
       }
       all_items.push(item_hash)
    end

    shipping_item_hash = {
      name: "Delivery",
      amount: (shipping_costs * 100).to_i,
      quantity: 1,
      currency: 'eur'
    }
    all_items.push(shipping_item_hash)

    return all_items
  end

  def customer_email
    current_user ? current_user.email : nil
  end

  def save_user_address
    if @order.user != nil
      current_user.attributes = @order.attributes.except("id", "email", "status", "comment", "amount_cents", "amount_currency", "checkout_session_id", "user_id", "updated_at", "created_at")
      current_user.save
    end
  end
class StripeCheckoutSessionService
  def call(event)
    order = Order.find_by(checkout_session_id: event.data.object.id)
    order.update(status: 'paid')
  end
end

付款 new.html.erb:

<script src="https://js.stripe.com/v3/"></script>
<script>
  const paymentButton = document.getElementById('pay-stripe');
  paymentButton.addEventListener('click', () => {
    const stripe = Stripe('<%= ENV['STRIPE_PUBLISHABLE_KEY'] %>');
    stripe.redirectToCheckout({
      sessionId: '<%= @order.checkout_session_id %>'
    });
  });
  </script>

初始化条带

Rails.configuration.stripe = {
  publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'],
  secret_key:      ENV['STRIPE_SECRET_KEY'],
  signing_secret:  ENV['STRIPE_WEBHOOK_SECRET_KEY']
}

Stripe.api_key = Rails.configuration.stripe[:secret_key]

StripeEvent.signing_secret = Rails.configuration.stripe[:signing_secret]

StripeEvent.configure do |events|
  events.subscribe 'checkout.session.completed', StripeCheckoutSessionService.new
end

【问题讨论】:

  • 能否包含您的 webhook 的代码以及您看到的错误消息?

标签: heroku stripe-payments


【解决方案1】:

如果 StripeEvent 从 Stripe 返回一个签名验证错误(请参阅https://github.com/integrallis/stripe_event/blob/31b948d6afd4a2f82c6ad3cd973211366b48a0d8/app/controllers/stripe_event/webhook_controller.rb#L12),它看起来会以 400 错误响应。

您应该仔细检查您的签名密码,并确保它与您的 heroku webhook 的密码相匹配,而不是您的 ngrok webhook。

【讨论】:

  • 是的,我检查了签名密钥。它在 heroku 配置和 .env 文件中都是正确的。我实际上删除了 ngrok webhook,以防它可能导致任何问题。所以现在我只有一个 heroku webhook 并且 3 个键是正确的
  • 您是否有任何可能表明正在发生的事情的后端日志?
猜你喜欢
  • 2011-04-02
  • 2018-08-24
  • 2022-06-28
  • 1970-01-01
  • 2016-07-30
  • 2021-04-07
  • 2017-08-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多