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) ->