Rails Stripe webhook 更新钱包余额

Posted

技术标签:

【中文标题】Rails Stripe webhook 更新钱包余额【英文标题】:Rails Stripe webhook to update wallet balance 【发布时间】:2021-08-31 18:53:46 【问题描述】:

最近怎么样?我需要一点帮助。

我正在开发一个应用程序,允许用户购买 credits 可用于预订activities credits 存储在用户的wallet 中,并通过多态wallet_entries 添加到其中(这些wallet_entries 也连接到bookings 表,当用户预订活动,积分会被扣除) 我正在使用 Stripe 来处理 payments。当用户想要购买积分时,会创建一个 order,它包含 credit_package id 以及设置为待处理的 "state" 属性。 我设法设置了 Stripe Checkout 并配置了一个 webhook,当付款成功时,它会将 order's 状态从 pending 更新为 paid 我的目标是在 Stripe 的 Webhook 更新 order's 状态时创建一个 wallet_entry。说wallet_entry 包含诸如number_of_creditswallet 参考之类的信息,以便用正确的number_of_credits 更新用户的wallet 余额 我尝试在处理 Stripe 事件的控制器中执行此操作,我将其包装在一个原子事务中,以确保只有在 order's 状态 时,信用才会添加到 wallet更新为付费 webhook 正在成功更新 order's state,但如果我添加更多行会中断,这是我在 Stripe Webhooks 日志中遇到的错误: NameError in StripeEvent::WebhookController#event undefined local variable or method current_user for <StripeCheckoutSessionService:0x000056103da42a38&>;。我的猜测是这个逻辑应该在其他地方处理。 这是我的文件:

条带初始化器

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

条带结帐会话服务

class StripeCheckoutSessionService
  def call(event)
    ActiveRecord::Base.transaction do
      order = Order.find_by(checkout_session_id: event.data.object.id)
      order.update(state: 'paid')
      # Extra stuff that breaks code
      wallet = current_user.wallet
      wallet_entry = WalletEntry.create!(type: "Deposit", total_credits: order.credit_package.number_of_credits, wallet: wallet, entryable: order)
      wallet.credit_total += wallet_entry.total_credits
    end
  end
end

支付控制器

class PaymentsController < ApplicationController
  before_action :authenticate_user!
  def new
    @order = Order.where(state: 'pending').find(params[:order_id])
  end
end

订单控制器

class OrdersController < ApplicationController
  def create
    credit_package = CreditPackage.find(params[:credit_package_id].to_i)
    order = Order.create!(credit_package: credit_package, amount: credit_package.price, state: 'pending', amount_of_packages: 1 )

    session = Stripe::Checkout::Session.create(
      payment_method_types: ['card'],
      line_items: [
        name: credit_package.name,
        amount: credit_package.price_cents,
        currency: 'eur',
        quantity: order.amount_of_packages
    ],
    success_url: order_url(order),
    cancel_url: order_url(order)
    )

    order.update(checkout_session_id: session.id)
    redirect_to new_order_payment_path(order)
  end

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

路线

Rails.application.routes.draw do
  get 'user_interests/destroy'

  # Admin dashboard routes
  namespace :admin do
    resources :activities
    resources :bookings
    resources :coaches
    resources :coach_profiles, except: :index
    resources :coach_activities, except: :index
    resources :activity_sessions
    resources :taggings, except: :index
    resources :tags
    resources :users
    resources :user_profiles, except: :index
    resources :user_interests, except: :index

    root to: 'coaches#index'
  end

  devise_for :coaches, controllers: 
    sessions: 'coaches/sessions'
  

  devise_for :users, controllers: 
    sessions: 'users/sessions'
  

  match '/users/:id', :to => 'users#destroy', :as => :destroy_user, :via => :delete

  root 'pages#home'

  # static pages
  get 'pages/contact'
  get 'pages/nousrejoindre'
  get 'pages/notrehistoire'
  get 'pages/mentionslegales'
  get 'pages/politiques'
  get 'pages/listeactivites'
  resources :activities do
    resources :sessions, only: [:index]
  end

  resources :activity_sessions do
    resources :bookings, only: [:create]
  end
  
  resources :orders, only: [:show, :create] do
    resources :payments, only: :new
  end

  resources :bookings, only: [:destroy]
  resources :contacts
  resources :users
  resources :user_profiles
  resources :coach_profiles
  resources :user_interests, only: [:destroy]
  resources :credit_packages, only: [:index]

  # route for stripe endpoint
  mount StripeEvent::Engine, at: '/stripe-webhooks'
end
关于如何进行的任何建议?我愿意完全改变这种方法,甚至是支付网关架构。谢谢

【问题讨论】:

【参考方案1】:

对于 Stripe webhook,请求由 Stripe 向您自己的应用程序发出。在此请求的上下文中,没有登录用户,因此 current_user 在执行 wallet = current_user.wallet 的行上不可用。

您可能需要做的是使用不同的方法来获取链接到此订单的用户。我可以想象您的 Order 模型以某种方式与用户相关联,在这种情况下您可以这样做:

order = Order.find_by(checkout_session_id: event.data.object.id)
order.update(state: 'paid')      
wallet = order.user.wallet

【讨论】:

以上是关于Rails Stripe webhook 更新钱包余额的主要内容,如果未能解决你的问题,请参考以下文章

如何延迟 Rails 控制器操作,直到 Stripe 的 webhook 成功?

仅在 Stripe 中的平台余额更新时收到通知 - WebHook

Rails 上的条纹 Webhook

收到 Stripe Webhook 后将项目保存在数据库中

Firebase Cloud Functions - Stripe Connect Webhook 未触发

如何在Stripe PHP和webhook中使用好事件,比较收到的付款数量并取消订阅