From 2561bc4caf71bfe6fde9e109b8ff98ac89e42481 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Fri, 20 Mar 2015 16:14:28 -0700 Subject: [PATCH] :bug:Fix Stripe web hook sub deleted for deleted user Add check for deleted user in subscription deleted Stripe web hook event handler, and equivalent test case. Add logging for web hook 500 errors to aid future debugging. --- server/routes/stripe.coffee | 71 +++++++++++++++---- .../functional/subscription.spec.coffee | 24 +++++++ 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/server/routes/stripe.coffee b/server/routes/stripe.coffee index b06fea792..39b88564e 100644 --- a/server/routes/stripe.coffee +++ b/server/routes/stripe.coffee @@ -11,6 +11,9 @@ module.exports.setup = (app) -> # Cache customer -> user ID map (increases test perf considerably) customerUserMap = {} + logStripeWebhookError = (msg) -> + console.warn "Stripe Webhook Error: #{msg}" + app.post '/stripe/webhook', (req, res) -> # Subscription renewal events: @@ -102,6 +105,24 @@ module.exports.setup = (app) -> subscription = req.body.data.object + checkUserExists = (done) -> + stripe.customers.retrieve subscription.customer, (err, customer) => + if err + logStripeWebhookError("Failed to retrieve #{subscription.customer}") + return res.send(500, '') + unless customer?.metadata?.id + logStripeWebhookError("Customer with no metadata.id #{subscription.customer}") + return res.send(500, '') + User.findById customer.metadata.id, (err, user) => + if err + logStripeWebhookError(err) + return res.send(500, '') + unless user + logStripeWebhookError("User not found #{customer.metadata.id}") + return res.send(500, '') + return res.send(200, '') if user.get('deleted') is true + done() + checkNormalSubscription = (done) -> User.findOne {'stripe.subscriptionID': subscription.id}, (err, user) -> return done() unless user @@ -112,17 +133,28 @@ module.exports.setup = (app) -> delete stripeInfo.subscriptionID user.set('stripe', stripeInfo) user.save (err) => - return res.send(500, '') if err + if err + logStripeWebhookError(err) + return res.send(500, '') return res.send(200, '') checkRecipientSubscription = (done) -> return done() unless subscription.plan.id is 'basic' + return done() unless subscription.metadata?.id # Shouldn't be possible User.findById subscription.metadata.id, (err, recipient) => - return res.send(500, '') if err - return res.send(500, '') unless recipient + if err + logStripeWebhookError(err) + return res.send(500, '') + unless recipient + logStripeWebhookError("Recipient not found #{subscription.metadata.id}") + return res.send(500, '') User.findById recipient.get('stripe').sponsorID, (err, sponsor) => - return res.send(500, '') if err - return res.send(500, '') unless sponsor + if err + logStripeWebhookError(err) + return res.send(500, '') + unless sponsor + logStripeWebhookError("Sponsor not found #{recipient.get('stripe').sponsorID}") + return res.send(500, '') # Update sponsor subscription stripeInfo = _.cloneDeep(sponsor.get('stripe') ? {}) @@ -130,12 +162,16 @@ module.exports.setup = (app) -> options = quantity: utils.getSponsoredSubsAmount(subscription.plan.amount, stripeInfo.recipients.length, stripeInfo.subscriptionID?) stripe.customers.updateSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, options, (err, subscription) => - return res.send(500, '') if err + if err + logStripeWebhookError(err) + return res.send(500, '') # Update sponsor user sponsor.set 'stripe', stripeInfo sponsor.save (err) => - return res.send(500, '') if err + if err + logStripeWebhookError(err) + return res.send(500, '') # Update recipient user stripeInfo = recipient.get('stripe') @@ -145,7 +181,9 @@ module.exports.setup = (app) -> else recipient.set 'stripe', stripeInfo recipient.save (err) => - return res.send(500, '') if err + if err + logStripeWebhookError(err) + return res.send(500, '') return res.send(200, '') checkSponsorSubscription = (done) -> @@ -165,7 +203,9 @@ module.exports.setup = (app) -> # Cancel all recipient subscriptions async.parallel (createUpdateFn(sub) for sub in stripeInfo.recipients), (err, results) => - return res.send(500, '') if err + if err + logStripeWebhookError(err) + return res.send(500, '') # Update sponsor user delete stripeInfo.sponsorSubscriptionID @@ -175,11 +215,14 @@ module.exports.setup = (app) -> else sponsor.set 'stripe', stripeInfo sponsor.save (err) => - return res.send(500, '') if err + if err + logStripeWebhookError(err) + return res.send(500, '') done() # TODO: use async.series for this - checkNormalSubscription -> - checkRecipientSubscription -> - checkSponsorSubscription -> - res.send(200, '') + checkUserExists -> + checkNormalSubscription -> + checkRecipientSubscription -> + checkSponsorSubscription -> + res.send(200, '') diff --git a/test/server/functional/subscription.spec.coffee b/test/server/functional/subscription.spec.coffee index 6a9d0f7e5..c15fd3164 100644 --- a/test/server/functional/subscription.spec.coffee +++ b/test/server/functional/subscription.spec.coffee @@ -681,6 +681,30 @@ describe 'Subscriptions', -> expect(stripeInfo.subscriptionID).toBeUndefined() done() + it 'User subscribes, deletes themselves, subscription ends', (done) -> + stripe.tokens.create { + card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' } + }, (err, token) -> + loginNewUser (user1) -> + # Subscribe user + subscribeUser user1, token, null, -> + User.findById user1.id, (err, user1) -> + expect(err).toBeNull() + customerID = user1.get('stripe').customerID + subscriptionID = user1.get('stripe').subscriptionID + stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) -> + expect(err).toBeNull() + expect(subscription).not.toBeNull() + # Delete user + request.del {uri: "#{userURL}/#{user1.id}"}, (err, res) -> + expect(err).toBeNull() + # Simulate Stripe subscription deleted via webhook + event = _.cloneDeep(customerSubscriptionDeletedSampleEvent) + event.data.object = subscription + request.post {uri: webhookURL, json: event}, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toEqual(200) + done() describe 'Sponsored', -> it 'Unsubscribed user1 subscribes user2', (done) ->