From 49e3878239d43aa5cdb98e39fe7b4ef9d32efcc8 Mon Sep 17 00:00:00 2001 From: Jess Lee Date: Wed, 13 Feb 2019 15:56:19 -0500 Subject: [PATCH] Add Stripe Cancellation Webhook (#1682) --- Envfile | 1 + .../stripe_cancellations_controller.rb | 39 +++++++++++++++++++ app/services/membership_service.rb | 2 + config/initializers/stripe.rb | 3 +- config/routes.rb | 1 + spec/requests/stripe_cancellations_spec.rb | 32 +++++++++++++++ 6 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 app/controllers/stripe_cancellations_controller.rb create mode 100644 spec/requests/stripe_cancellations_spec.rb diff --git a/Envfile b/Envfile index abbb12315..944a3face 100644 --- a/Envfile +++ b/Envfile @@ -130,6 +130,7 @@ variable :SLACK_WEBHOOK_URL, :String, default: "Optional" # Stripe for payment system variable :STRIPE_PUBLISHABLE_KEY, :String, default: "Optional" variable :STRIPE_SECRET_KEY, :String, default: "Optional" +variable :STRIPE_CANCELLATION_SECRET, :String, default: "Optional" # For browser webpush notifications (webpush gem) (totally random base64 nums) variable :VAPID_PUBLIC_KEY, :String, default: "dGhpcyBpcyBkZXYudG8gdGVzdCBkYXRh" diff --git a/app/controllers/stripe_cancellations_controller.rb b/app/controllers/stripe_cancellations_controller.rb new file mode 100644 index 000000000..eda31ff16 --- /dev/null +++ b/app/controllers/stripe_cancellations_controller.rb @@ -0,0 +1,39 @@ +class StripeCancellationsController < ApplicationController + skip_before_action :verify_authenticity_token, only: :create + + def create + verify_stripe_signature + unsubscribe_customer + end + + def verify_stripe_signature + payload = request.body.read + sig_header = request.env["HTTP_STRIPE_SIGNATURE"] + + begin + Stripe::Webhook.construct_event( + payload, sig_header, Rails.configuration.stripe[:stripe_cancellation_secret] + ) + rescue JSON::ParserError + # Invalid payload + status 400 + return + rescue Stripe::SignatureVerificationError + # Invalid signature + status 400 + return + end + end + + def unsubscribe_customer + event = Stripe::Event.retrieve(params[:id]) + stripe_id = event.data.object.customer + customer = Stripe::Customer.retrieve(stripe_id) + monthly_dues = event.data.object.items.data[0].plan.amount + user = User.where(stripe_id_code: stripe_id).first + MembershipService.new(customer, user, monthly_dues).unsubscribe_customer + render body: nil, status: 200 + rescue Stripe::APIConnectionError, Stripe::StripeError + render body: nil, status: 400 + end +end diff --git a/app/services/membership_service.rb b/app/services/membership_service.rb index 2fa2fd5f5..d9c449a16 100644 --- a/app/services/membership_service.rb +++ b/app/services/membership_service.rb @@ -42,6 +42,8 @@ class MembershipService end def cancel_subscription + return true if customer.subscriptions.none? + subscription.delete end diff --git a/config/initializers/stripe.rb b/config/initializers/stripe.rb index c9152e44f..687c3a088 100644 --- a/config/initializers/stripe.rb +++ b/config/initializers/stripe.rb @@ -1,6 +1,7 @@ Rails.configuration.stripe = { publishable_key: ApplicationConfig["STRIPE_PUBLISHABLE_KEY"], - secret_key: ApplicationConfig["STRIPE_SECRET_KEY"] + secret_key: ApplicationConfig["STRIPE_SECRET_KEY"], + stripe_cancellation_secret: ApplicationConfig["STRIPE_CANCELLATION_SECRET"] } Stripe.api_key = Rails.configuration.stripe[:secret_key] diff --git a/config/routes.rb b/config/routes.rb index a3667ea22..995efd930 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,6 +112,7 @@ Rails.application.routes.draw do resources :tags, only: [:index] resources :stripe_subscriptions, only: %i[create update destroy] resources :stripe_active_cards, only: %i[create update destroy] + resources :stripe_cancellations, only: [:create] resources :live_articles, only: [:index] resources :github_repos, only: %i[create update] resources :buffered_articles, only: [:index] diff --git a/spec/requests/stripe_cancellations_spec.rb b/spec/requests/stripe_cancellations_spec.rb new file mode 100644 index 000000000..4e5ae61a1 --- /dev/null +++ b/spec/requests/stripe_cancellations_spec.rb @@ -0,0 +1,32 @@ +require "rails_helper" + +RSpec.describe "StripeCancellations", type: :request do + let(:user) { create(:user) } + let(:stripe_helper) { StripeMock.create_test_helper } + + before do + StripeMock.start + allow_any_instance_of(StripeCancellationsController).to receive(:verify_stripe_signature).and_return(true) + end + + after { StripeMock.stop } + + it "mocks a stripe cancellation webhook" do + customer = Stripe::Customer.create( + email: user.email, + source: stripe_helper.generate_card_token, + ) + MembershipService.new(customer, user, 12).subscribe_customer + user.reload + expect(user.monthly_dues).not_to eq(0) + + event = StripeMock.mock_webhook_event( + "customer.subscription.deleted", + customer: user.stripe_id_code, total_count: 1, + ) + post "/stripe_cancellations", params: event.as_json + user.reload + expect(user.monthly_dues).to eq(0) + expect(response).to have_http_status(200) + end +end