0

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
Cole Phiper
  • 349
  • 1
  • 13

1 Answers1

0

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.

Eliot Sykes
  • 9,616
  • 6
  • 50
  • 64
  • Hello Eliot, I am using module routes for the source code - Since Payola is not yet working for Rails 5.2. I will post my entire configuration for you to see. I simply moved the components to each of their respective folders to avoid the checks for the Rails version. – Cole Phiper Jan 17 '19 at 13:54
  • I updated the post to list how my setup is constructed. I was wondering if I should have the payola routes listed at the top for precaution. Mind you, I'm able to create subscriptions for new users without issue. Since, my approach requires the subscription be created when he/she registers their account (devise) – Cole Phiper Jan 17 '19 at 17:38
  • It seems as if rubymine is unable to recognize subscription.rb since it has a require 'aasm' statement at the top. I'm checking to see if thats the issue. The file is completely white, and unusable I guess. – Cole Phiper Jan 17 '19 at 18:06
  • Hi Cole, I'm afraid I can't tell what the issue is and haven't tried Payola on Rails 5.2. – Eliot Sykes Jan 18 '19 at 09:21
  • Eliot, can you give me an idea of what normally happens in this situation. After the webhook is created successfully? Does the subscriptions/sales tables automatically update with my current configuration (with Payola.subscribe switched to config.subscribe) Can you show me a log of your output after the webhook is created? Thank you. I really would like to upvote you for your help. – Cole Phiper Jan 19 '19 at 01:26
  • Hi Cole, first there's no need to offer votes in exchange for assistance here. Please don't offer again. Second, read again through the payola gem source. In particular, here: https://github.com/payolapayments/payola/blob/011fb02481f4aea3b2e679e2ef00e784ccb61347/lib/payola/engine.rb#L33-L36 These are the webhook events and the classes that will be `call`ed when those webhook events happen. The service classes can be found here: https://github.com/payolapayments/payola/tree/011fb02481f4aea3b2e679e2ef00e784ccb61347/app/services/payola – Eliot Sykes Jan 21 '19 at 08:59
  • Since I’m able to create subscriptions when users register. I also should be able to update the subscription table with sync_with! Maybe it won’t be called until it’s time for the card to make a payment on Stripe – Cole Phiper Jan 21 '19 at 17:11