How do I update the subscription and sales table after receiving stripe webhook responses with Payola?

403 views Asked by At

I'm able to receive Web hook responses from Stripe, through Payola's StripeEvent method blocks. Each response renders me a 200 OK HTTP header when I click the test webhooks button on the Stripe dashboard. The issue that I am having, is the Payola::StripeWebhook's are being stored in the database, but the subscription and sales table are not updating. As a result, users are staying on trial and I cannot log payments, due to their records being stalemates. I was told that missing these events from server shutdowns/hardware failure, can lead to records not being updated correctly. I also see that there is a method called Payola::Subscription#sync_with! that is supposed to synchronize and update the records. How can I fix my StripeEvents to change the Subscription/Sales model data with Stripe Web Hook Responses?

I created events for each web hook response that I felt was necessary. For instance, I made a webhook event for charge.refunded, invoice.payment_failed, invoice.payment_succeeded, customer.subscription.deleted and trial_will_end. I see the response in my server from Stripe, but I see no updates taking place for the tables.

Server Log

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 management

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

routes

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']

UPDATE after changing the stripe event route to what the gem shows and placing it at the bottom. I receive a webhook response like the one below.

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)

UPDATE 2

I believe that its working now. I received the event below for one of the subscription plans.

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

There are 1 answers

7
Eliot Sykes On

Its not clear exactly what the issue is. So here's a few options to consider trying in case you've not tried them yet:

  • Check Payola is mounted in config/routes.rb. It should look like this:

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

  • Check background jobs are being run and watch the job queues to see if any payola-related jobs are being queued.

  • Ensure payola.rb is in config/initializers/

  • Fully restart the app and Spring anytime you change config/initializers/payola.rb, by running bin/spring stop; bin/rails server

  • Check any StripeEvent config throughout the app for anything unexpected. Likewise for any Payola config.

  • Update the Payola.subscribe calls to config.subscribe in the payola.rb file given above.

  • In your development environment only, debug the payola gem by adding your own code into the payola-payments gem's source at any points of interest. Find its location by running bundle show payola-payments and then open the directory printed in your code editor. You can make changes to any of the files in the gem to help debug. Each time you make a change to this gem, you will need to do a full restart to pick up the latest changes: bin/spring stop; bin/rails server. Once you've finished your debugging, return the gem to its original state without your changes by running bundle exec gem pristine payola-payments.