通过Payola收到条纹式Webhook响应后,如何更新订阅和销售表?

时间:2019-01-17 01:46:43

标签: ruby-on-rails ruby stripe-payments webhooks payola

通过Payola的StripeEvent方法块,我能够从Stripe接收Web挂钩响应。当我单击Stripe仪表板上的测试webhooks按钮时,每个响应都会为我提供200 OK HTTP标头。我遇到的问题是Payola :: StripeWebhook的存储在数据库中,但是订阅和销售表没有更新。结果,用户一直处于试用状态,并且由于他们的记录已陷入僵局,因此我无法记录付款。有人告诉我,由于服务器关闭/硬件故障而丢失这些事件,可能会导致记录无法正确更新。我还看到有一种名为 Payola :: Subscription#sync_with!的方法,该方法应该同步和更新记录。如何通过Stripe Web Hook Responses修复StripeEvents以更改Subscription / Sales模型数据?

我为我认为必要的每个Web挂钩响应创建了事件。例如,我为charge.refunded,invoice.payment_failed,invoice.payment_succeeded,customer.subscription.deleted和trial_will_end创建了一个网络挂钩事件。我在Stripe的服务器上看到了响应,但是没有看到表的更新。

服务器日志

Started POST "/payola/events" for 54.187.205.235 at 2019-01-16 19:54:07 -0500
Processing by StripeEvent::WebhookController#event as XML
Parameters: {"id"=>"evt_1DtP922xuZZdQdXfrVD0Iel0", "object"=>"event", "api_version"=>"2017-06-05", "created"=>1547686448, "data"=>{"object"=>{"id"=>"biz_basic_account", "object"=>"plan", "active"=>true, "aggregate_usage"=>nil, "amount"=>4600, "billing_scheme"=>"per_unit", "created"=>1543343985, "currency"=>"usd", "interval"=>"month", "interval_count"=>1, "livemode"=>false, "metadata"=>{}, "name"=>"Business Basics | $4.00", "nickname"=>"Business Basic", "product"=>"prod_E3I35OUSByBfci", "statement_descriptor"=>nil, "tiers"=>nil, "tiers_mode"=>nil, "transform_usage"=>nil, "trial_period_days"=>3, "usage_type"=>"licensed"}, "previous_attributes"=>{"nickname"=>nil, "trial_period_days"=>nil}}, "livemode"=>false, "pending_webhooks"=>1, "request"=>{"id"=>"req_E9ztRRhsBJ1PyU", "idempotency_key"=>nil}, "type"=>"plan.updated", "webhook"=>{"id"=>"evt_1DtP922xuZZdQdXfrVD0Iel0", "object"=>"event", "api_version"=>"2017-06-05", "created"=>1547686448, "data"=>{"object"=>{"id"=>"biz_basic_account", "object"=>"plan", "active"=>true, "aggregate_usage"=>nil, "amount"=>4600, "billing_scheme"=>"per_unit", "created"=>1543343985, "currency"=>"usd", "interval"=>"month", "interval_count"=>1, "livemode"=>false, "metadata"=>{}, "name"=>"Business Basics | $4.00", "nickname"=>"Business Basic", "product"=>"prod_E3I35OUSByBfci", "statement_descriptor"=>nil, "tiers"=>nil, "tiers_mode"=>nil, "transform_usage"=>nil, "trial_period_days"=>3, "usage_type"=>"licensed"}, "previous_attributes"=>{"nickname"=>nil, "trial_period_days"=>nil}}, "livemode"=>false, "pending_webhooks"=>1, "request"=>{"id"=>"req_E9ztRRhsBJ1PyU", "idempotency_key"=>nil}, "type"=>"plan.updated"}}
 Payola::StripeWebhook Exists (1.0ms)  SELECT  1 AS one FROM "payola_stripe_webhooks" WHERE "payola_stripe_webhooks"."stripe_id" = $1 LIMIT $2  [["stripe_id", "evt_1DtPegrgr922xuZZdQdXfrrgegeD0rgegl0"], ["LIMIT", 1]]
   (0.0ms)  BEGIN
  CACHE Payola::StripeWebhook Exists (0.0ms)  SELECT  1 AS one FROM "payola_stripe_webhooks" WHERE "payola_stripe_webhooks"."stripe_id" = $1 LIMIT $2  [["stripe_id", "evt_1DtPegrgr922xuZZdQdXfrrgegeD0rgegl0"], ["LIMIT", 1]]
  Payola::StripeWebhook Create (0.0ms)  INSERT INTO "payola_stripe_webhooks" ("stripe_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["stripe_id", "evt_1DtPegrgr922xuZZdQdXfrrgegeD0rgegl0"], ["created_at", "2019-01-17 00:54:08.269998"], ["updated_at", "2019-01-17 00:54:08.269998"]]
   (0.0ms)  COMMIT
Completed 200 OK in 469ms (ActiveRecord: 1.0ms)

payola.rb / StripeEvent管理

Payola.configure do |config|
  # Example subscription:
  #
  # config.subscribe 'payola.package.sale.finished' do |sale|
  #   EmailSender.send_an_email(sale.email)
  # end
  #
  # In addition to any event that Stripe sends, you can subscribe
  # to the following special payola events:
  #
  #  - payola.<sellable class>.sale.finished
  #  - payola.<sellable class>.sale.refunded
  #  - payola.<sellable class>.sale.failed
  #
  # These events consume a Payola::Sale, not a Stripe::Event
  #
  # Example charge verifier:
  #
  # config.charge_verifier = lambda do |sale|
  #   raise "Nope!" if sale.email.includes?('yahoo.com')
  # end

  config.secret_key = ENV['STRIPE_SECRET_KEY']
  config.publishable_key = ENV['STRIPE_PUBLISHABLE_KEY']

  config.charge_verifier = lambda do |event|
    user = User.find_by(email: event.email)
      if event.is_a?(Payola::Subscription) && user.subscriptions.active.any?
    raise 'Error: This user already has a subscription'
   end

      event.owner = user
      event.save!
   end

  # Keep this subscription unless you want to disable refund handling
  config.subscribe 'charge.refunded' do |event|
    subscription = Payola::Subscription.find_by(stripe_id: event.data.object.id)
    subscription.refund! unless sale.refunded?
  end

  Payola.subscribe 'invoice.payment_failed' do |event|
    subscription = Payola::Subscription.find_by(stripe_id: event.data.object.id)
    user = User.find_by(email: subscription.email)
    ChargeFailedMailer.charge_failure_user(user)
    user.update_attribute(:account_delinquent, true)
  end

  Payola.subscribe 'invoice.payment_succeeded' do |event|
    subscription = Payola::Subscription.find_by(stripe_id: event.data.object.id)
      user = User.find_by(email: subscription.email)
      sale = Sale.find_by(stripe_id: event.data.object.id)
      ChargeSucceededMailer.charge_succeeded_user(user)
      user.update_attribute(:account_delinquent, false)
      subscription.update_attribute(:status, event.data.object.status)
      subscription.save
  end

  Payola.subscribe 'customer.subscription.deleted' do |event|
    subscription = Payola::Subscription.find_by(stripe_id: event.data.object.id)
      user = User.find_by(email: subscription.email)
      sale = Sale.find_by(stripe_id: event.data.object.id)
    CanceledSubscriptionMailer.subscription_canceled_for_user(user).deliver
  user.destroy

  end

  Payola.subscribe 'customer.subscription.trial_will_end' do |event|
    subscription = Payola::Subscription.find_by(stripe_id: event.data.object.id)
      user = User.find_by(email: subscription.email)
      sale = Sale.find_by(stripe_id: event.data.object.id)
      TrialEndingMailer.trial_ending_for_user(user)

  end

  Payola.subscribe 'customer.subscription.updated' do |event|
    puts 'Subscription updated!'
  end
end

路线

Rails.application.routes.draw do
  match '/subscription_expired', to: 'service_disruption#index', :via => :get
  #mount Payola::Engine => '/payola', as: :payola
  mount ActionCable.server => '/cable'
  mount CountryStateSelect::Rails::Engine => '/'
  get 'my_review/index'
  require 'sidekiq/web'
  mount Sidekiq::Web => '/sidekiq'
  post '/rate' => 'rater#create', :as => 'rate'
  get 'update_account/index'
  get 'u_help', to: 'user_help#index', as: :user_help
  get 'username_validator/:username', to: 'usernames#username_validator'

  devise_for :users, path: '', path_names: {sign_in: 'login', sign_out: 'logout', sign_up: 'signup'}, controllers: {registrations: 'users/registrations'}

  namespace :api do
    scope :v1 do
    end
  end

  devise_scope :user do
    put 'user_change_plan', to: 'users/registrations#user_change_plan'
    put 'user_change_credit_card', to: 'users/registrations#user_change_credit_card'
    authenticated do
      root to: 'user_dashboard#index', as: 'authenticated_user_root'
    end
    unauthenticated do
      root to: 'home#index', as: 'unauthenticated_user_root'
    end
  end

  controller :home do
    get :index, to: 'home#index', as: 'home', path: 'home'
    get :pricing, to: 'home#pricing', as: 'pricing', path: 'pricing'
    get :about, to: 'home#about', as: 'about', path: 'about'
    get :contact, to: 'home#contact', as: 'contact', path: 'contact'
    get :login_portal, to: 'home#login_portal', as: 'login_portal', path: 'login_portal'
    get :signup_portal, to: 'home#signup_portal', as: 'signup_portal', path: 'signup_portal'
  end


  scope module: 'payola' do
    mount StripeEvent::Engine => 'payola/events', as: :payola
    post 'payola/buy/:product_class/:permalink' => 'transactions#create', as: :buy
    get 'payola/confirm/:guid' => 'transactions#show', as: :confirm
    get 'payola/status/:guid' => 'transactions#status', as: :status
    post 'payola/subscribe/:plan_class/:plan_id' => 'subscriptions#create', as: :subscribe
    get 'payola/confirm_subscription/:guid' => 'subscriptions#show', as: :confirm_subscription
    get 'payola/subscription_status/:guid' => 'subscriptions#status', as: :subscription_status
    delete 'payola/cancel_subscription/:guid' => 'subscriptions#destroy', as: :cancel_subscription
    post 'payola/change_plan/:guid' => 'subscriptions#change_plan', as: :change_subscription_plan
    post 'payola/change_quantity/:guid' => 'subscriptions#change_quantity', as: :change_subscription_quantity
    post 'payola/update_card/:guid' => 'subscriptions#update_card', as: :update_card
    post 'payola/update_customer/:id' => 'customers#update', as: :update_customer
    post 'payola/create_card/:customer_id' => 'cards#create', as: :create_card
    delete 'payola/destroy_card/:id/:customer_id' => 'cards#destroy', as: :destroy_card
  end

  root 'home#index'
end

application.js

//= require jquery3
......
//= require payola/subscription_form_onestep
//= require payola/subscription_form_twostep
//= require payola/checkout_button
//= require payola/form
//= require payola/subscription_checkout_button
//= require payola/subscription_form_register

stripe.rb

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

Stripe.api_key = Rails.application.secrets.stripe_api_key
StripeEvent.signing_secret = ENV['STRIPE_SIGNING_SECRET']

更新 将条纹事件路线更改为宝石显示的内容并将其放置在底部之后。我收到类似下面的webhook响应。

Completed 200 OK in 260332ms (Views: 259165.4ms | ActiveRecord: 81.9ms)


Started POST "/events" for 54.187.174.169 at 2019-01-18 21:11:30 -0500
Processing by StripeEvent::WebhookController#event as XML
  Parameters: {"created"=>1326853478, "livemode"=>false, "id"=>"invoice.payment_00000000000000", "type"=>"invoice.payment_succeeded", "object"=>"event", "request"=>nil, "pending_webhooks"=>1, "api_version"=>"2017-06-05", "data"=>{"object"=>{"id"=>"in_00000000000000", "object"=>"invoice", "amount_due"=>900, "amount_paid"=>900, "amount_remaining"=>0, "application_fee"=>nil, "attempt_count"=>1, "attempted"=>true, "auto_advance"=>false, "billing"=>"charge_automatically", "billing_reason"=>"subscription", "charge"=>"_00000000000000", "closed"=>true, "currency"=>"usd", "custom_fields"=>nil, "customer"=>"cus_00000000000000", "date"=>1455648779, "default_source"=>nil, "description"=>nil, "discount"=>nil, "due_date"=>nil, "ending_balance"=>0, "finalized_at"=>1455648779, "footer"=>nil, "forgiven"=>false, "hosted_invoice_url"=>"https://pay.stripe.com/invoice/invst_1cWFwm2egegegegege", "invoice_pdf"=>"https://pay.stripe.com/invoice/invst_1cWFwm20xegegegegege/pdf", "lines"=>{"data"=>[{"id"=>"sub_00000000000000", "object"=>"line_item", "amount"=>900, "currency"=>"usd", "description"=>nil, "discountable"=>true, "livemode"=>false, "metadata"=>{}, "period"=>{"end"=>1502909579, "start"=>1500231179}, "plan"=>{"id"=>"gbsubscriptionlevel1_00000000000000", "object"=>"plan", "active"=>true, "aggregate_usage"=>nil, "amount"=>900, "billing_scheme"=>"per_unit", "created"=>1455057017, "currency"=>"usd", "interval"=>"month", "interval_count"=>1, "livemode"=>false, "metadata"=>{}, "name"=>"Going Big Subscription Basic", "nickname"=>nil, "product"=>"prod_00000000000000", "statement_descriptor"=>"Going Big SUBSCRIPTION", "tiers"=>nil, "tiers_mode"=>nil, "transform_usage"=>nil, "trial_period_days"=>nil, "usage_type"=>"licensed"}, "proration"=>false, "quantity"=>1, "subscription"=>nil, "subscription_item"=>"si_00000000000000", "type"=>"subscription"}], "has_more"=>false, "object"=>"list", "url"=>"/v1/invoices/in_17fDwV2xuZZdQdXgegegegeNB8uL/lines"}, "livemode"=>false, "metadata"=>{}, "next_payment_attempt"=>nil, "number"=>nil, "paid"=>true, "period_end"=>1455648779, "period_start"=>1455648779, "receipt_number"=>nil, "starting_balance"=>0, "statement_descriptor"=>nil, "status"=>"paid", "subscription"=>"sub_00000000000000", "subtotal"=>900, "tax"=>nil, "tax_percent"=>nil, "total"=>900, "webhooks_delivered_at"=>1455648780}}, "webhook"=>{"created"=>1326853478, "livemode"=>false, "id"=>"invoice.payment_00000000000000", "type"=>"invoice.payment_succeeded", "object"=>"event", "request"=>nil, "pending_webhooks"=>1, "api_version"=>"2017-06-05", "data"=>{"object"=>{"id"=>"in_00000000000000", "object"=>"invoice", "amount_due"=>900, "amount_paid"=>900, "amount_remaining"=>0, "application_fee"=>nil, "attempt_count"=>1, "attempted"=>true, "auto_advance"=>false, "billing"=>"charge_automatically", "billing_reason"=>"subscription", "charge"=>"_00000000000000", "closed"=>true, "currency"=>"usd", "custom_fields"=>nil, "customer"=>"cus_00000000000000", "date"=>1455648779, "default_source"=>nil, "description"=>nil, "discount"=>nil, "due_date"=>nil, "ending_balance"=>0, "finalized_at"=>1455648779, "footer"=>nil, "forgiven"=>false, "hosted_invoice_url"=>"https://pay.stripe.com/invoice/invst_1cWFwm2rggegegegeg", "invoice_pdf"=>"https://pay.stripe.com/invoice/invst_1cWrgegegegegege/pdf", "lines"=>{"data"=>[{"id"=>"sub_00000000000000", "object"=>"line_item", "amount"=>900, "currency"=>"usd", "description"=>nil, "discountable"=>true, "livemode"=>false, "metadata"=>{}, "period"=>{"end"=>1502909579, "start"=>1500231179}, "plan"=>{"id"=>"gbsubscriptionlevel1_00000000000000", "object"=>"plan", "active"=>true, "aggregate_usage"=>nil, "amount"=>900, "billing_scheme"=>"per_unit", "created"=>1455057017, "currency"=>"usd", "interval"=>"month", "interval_count"=>1, "livemode"=>false, "metadata"=>{}, "name"=>"Going Big Subscription Basic", "nickname"=>nil, "product"=>"prod_00000000000000", "statement_descriptor"=>"Going Big SUBSCRIPTION", "tiers"=>nil, "tiers_mode"=>nil, "transform_usage"=>nil, "trial_period_days"=>nil, "usage_type"=>"licensed"}, "proration"=>false, "quantity"=>1, "subscription"=>nil, "subscription_item"=>"si_00000000000000", "type"=>"subscription"}], "has_more"=>false, "object"=>"list", "url"=>"/v1/invoices/in_17fDwV2xuZZdQdegergegeg8uL/lines"}, "livemode"=>false, "metadata"=>{}, "next_payment_attempt"=>nil, "number"=>nil, "paid"=>true, "period_end"=>1455648779, "period_start"=>1455648779, "receipt_number"=>nil, "starting_balance"=>0, "statement_descriptor"=>nil, "status"=>"paid", "subscription"=>"sub_00000000000000", "subtotal"=>900, "tax"=>nil, "tax_percent"=>nil, "total"=>900, "webhooks_delivered_at"=>1455648780}}}}
  Payola::StripeWebhook Exists (109.9ms)  SELECT  1 AS one FROM "payola_stripe_webhooks" WHERE "payola_stripe_webhooks"."stripe_id" = $1 LIMIT $2  [["stripe_id", "invoice.payment_00000000000000"], ["LIMIT", 1]]
Completed 200 OK in 379ms (ActiveRecord: 114.9ms)

更新2

我相信它现在可以正常工作。我收到了以下有关一项订阅计划的活动。

Event Details
ID
evt_1Dv7l02xuZZdQdXfaSXmAOSz
Date
2019/01/21 18:44:26
Type
customer.subscription.updated
Source
Automatic

1 个答案:

答案 0 :(得分:0)

目前尚不清楚问题出在哪里。因此,如果您还没有尝试过,可以考虑以下几种选择:

  • 检查Payola在mountconfig/routes.rb。它应该看起来像这样:

    mount Payola::Engine => '/payments', as: :payments

  • 检查正在运行的后台作业,并查看作业队列以查看是否有与Payola相关的作业正在排队。

  • 确保payola.rb位于config/initializers/

  • 通过运行config/initializers/payola.rb

  • ,在每次更改bin/spring stop; bin/rails server时完全重启应用程序和Spring。
  • 在整个应用程序中检查任何StripeEvent配置,以防意外。同样,对于任何Payola配置。

  • 将上述Payola.subscribe文件中的config.subscribe调用更新为payola.rb

  • 仅在您的开发环境中,通过在任何关注点将自己的代码添加到payola-payments gem的源中来调试payola gem。通过运行bundle show payola-payments找到其位置,然后打开在代码编辑器中打印的目录。您可以更改gem中的任何文件以帮助调试。每次更改此gem时,都需要完全重新启动以获取最新更改:bin/spring stop; bin/rails server。完成调试后,通过运行bundle exec gem pristine payola-payments将gem还原为原始状态。