fix for paypal webhook cancel

This commit is contained in:
Kyle Drake
2026-05-18 14:47:54 -05:00
parent 51071692cd
commit aa7611289c
2 changed files with 117 additions and 0 deletions
+53
View File
@@ -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'
+64
View File
@@ -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",