diff --git a/app/webhooks.rb b/app/webhooks.rb index e966d67e..8a5db059 100644 --- a/app/webhooks.rb +++ b/app/webhooks.rb @@ -1,7 +1,60 @@ post '/webhooks/paypal' do + return 403 unless valid_paypal_webhook_source? + + if paypal_subscription_ended_ipn? + site = paypal_ipn_site + + if site + stripe_active = site.stripe_paying_supporter? + + site.paypal_active = false + site.paypal_profile_id = nil + site.paypal_token = nil + + unless stripe_active + site.plan_type = nil + site.plan_ended = true + end + + site.save_changes validate: false + + unless stripe_active + EmailWorker.perform_async({ + from: Site::FROM_EMAIL, + to: site.email, + subject: '[Neocities] Supporter plan has ended', + body: Tilt.new('./views/templates/email/supporter_ended.erb', pretty: true).render(self) + }) + end + end + end + 'ok' end +def paypal_subscription_ended_ipn? + %w[ + recurring_payment_profile_cancel + recurring_payment_expired + recurring_payment_suspended + recurring_payment_suspended_due_to_max_failed_payment + subscr_cancel + subscr_eot + ].include?(params[:txn_type].to_s) +end + +def paypal_ipn_site + profile_id = params[:recurring_payment_id] || + params[:recurring_payment_profile_id] || + params[:rp_profile_id] || + params[:profile_id] || + params[:subscr_id] + + return nil if profile_id.blank? + + Site.where(paypal_profile_id: profile_id).first +end + def valid_paypal_webhook_source? return true if self.class.test? return true if request.ip == '127.0.0.1' diff --git a/tests/webhook_tests.rb b/tests/webhook_tests.rb index d1912178..4ba365cd 100644 --- a/tests/webhook_tests.rb +++ b/tests/webhook_tests.rb @@ -66,6 +66,70 @@ describe 'tipping' do end end +describe 'paypal supporter IPN' do + include Rack::Test::Methods + + def app + Sinatra::Application + end + + before do + EmailWorker.jobs.clear + @site = Fabricate :site + @site.update( + paypal_active: true, + paypal_profile_id: 'I-123', + paypal_token: 'EC-123', + plan_type: 'supporter', + plan_ended: false + ) + end + + it 'ends a PayPal supporter membership when PayPal cancels the recurring profile' do + post '/webhooks/paypal', paypal_supporter_ipn_hash + + _(last_response.status).must_equal 200 + @site.reload + _(@site.values[:paypal_active]).must_equal false + _(@site.paypal_profile_id).must_be_nil + _(@site.paypal_token).must_be_nil + _(@site.values[:plan_type]).must_be_nil + _(@site.plan_ended).must_equal true + + _(EmailWorker.jobs.length).must_equal 1 + args = EmailWorker.jobs.first['args'].first + _(args['to']).must_equal @site.email + _(args['subject']).must_equal '[Neocities] Supporter plan has ended' + end + + it 'clears stale PayPal fields without ending an active Stripe membership' do + @site.update( + stripe_customer_id: 'cus_123', + stripe_subscription_id: 'sub_123', + plan_type: 'supporter', + plan_ended: false + ) + + post '/webhooks/paypal', paypal_supporter_ipn_hash + + _(last_response.status).must_equal 200 + @site.reload + _(@site.values[:paypal_active]).must_equal false + _(@site.paypal_profile_id).must_be_nil + _(@site.paypal_token).must_be_nil + _(@site.values[:plan_type]).must_equal 'supporter' + _(@site.plan_ended).must_equal false + _(EmailWorker.jobs.length).must_equal 0 + end +end + +def paypal_supporter_ipn_hash(opts={}) + { + txn_type: 'recurring_payment_profile_cancel', + recurring_payment_id: 'I-123' + }.merge(opts) +end + def paypal_tip_webhook_hash(opts={}) { :transaction_subject=>"customvarlol",