Cancel recipient subscriptions immediately

This commit is contained in:
Matt Lott 2015-06-17 16:31:40 -07:00
parent 593f7a9dd7
commit 5c240d89cd
3 changed files with 62 additions and 40 deletions

View file

@ -582,18 +582,26 @@ class SubscriptionHandler extends Handler
email = req.body.stripe.unsubscribeEmail.trim().toLowerCase() email = req.body.stripe.unsubscribeEmail.trim().toLowerCase()
return done({res: 'Database error.', code: 500}) if _.isEmpty(email) return done({res: 'Database error.', code: 500}) if _.isEmpty(email)
deleteUserStripeProp = (user, propName) ->
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
delete stripeInfo[propName]
if _.isEmpty stripeInfo
user.set 'stripe', undefined
else
user.set 'stripe', stripeInfo
User.findOne {emailLower: email}, (err, recipient) => User.findOne {emailLower: email}, (err, recipient) =>
if err if err
@logSubscriptionError(user, "User lookup error. " + err) @logSubscriptionError(user, "User lookup error. " + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
unless recipient unless recipient
@logSubscriptionError(user, "Recipient #{req.body.stripe.recipient} not found. " + err) @logSubscriptionError(user, "Recipient #{email} not found.")
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
# Check recipient is currently sponsored # Check recipient is currently sponsored
stripeRecipient = recipient.get 'stripe' ? {} stripeRecipient = recipient.get 'stripe' ? {}
if stripeRecipient?.sponsorID isnt user.id if stripeRecipient?.sponsorID isnt user.id
@logSubscriptionError(user, "Recipient #{req.body.stripe.recipient} not found. " + err) @logSubscriptionError(user, "Recipient #{req.body.stripe.recipient} not found. ")
return done({res: 'Can only unsubscribe sponsored subscriptions.', code: 403}) return done({res: 'Can only unsubscribe sponsored subscriptions.', code: 403})
# Find recipient subscription # Find recipient subscription
@ -603,22 +611,41 @@ class SubscriptionHandler extends Handler
sponsoredEntry = sponsored sponsoredEntry = sponsored
break break
unless sponsoredEntry? unless sponsoredEntry?
@logSubscriptionError(user, 'Unable to find sponsored subscription. ' + err) @logSubscriptionError(user, 'Unable to find recipient subscription. ')
return done({res: 'Database error.', code: 500})
# Update recipient user
deleteUserStripeProp(recipient, 'sponsorID')
recipient.save (err) =>
if err
@logSubscriptionError(user, 'Recipient user save unsubscribe error. ' + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
# Cancel Stripe subscription # Cancel Stripe subscription
stripe.customers.cancelSubscription stripeInfo.customerID, sponsoredEntry.subscriptionID, { at_period_end: true }, (err) => stripe.customers.cancelSubscription stripeInfo.customerID, sponsoredEntry.subscriptionID, (err) =>
if err or not recipient if err
@logSubscriptionError(user, "Stripe cancel sponsored subscription failed. " + err) @logSubscriptionError(user, "Stripe cancel sponsored subscription failed. " + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
# Update sponsor user
_.remove(stripeInfo.recipients, (s) -> s.userID is recipient.id)
delete stripeInfo.unsubscribeEmail delete stripeInfo.unsubscribeEmail
user.set('stripe', stripeInfo) user.set('stripe', stripeInfo)
req.body.stripe = stripeInfo req.body.stripe = stripeInfo
user.save (err) => user.save (err) =>
if err if err
@logSubscriptionError(user, 'User save unsubscribe error. ' + err) @logSubscriptionError(user, 'Sponsor user save unsubscribe error. ' + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
return done() unless stripeInfo.sponsorSubscriptionID?
# Update sponsored subscription quantity
options =
quantity: getSponsoredSubsAmount(subscriptions.basic.amount, stripeInfo.recipients.length, stripeInfo.subscriptionID?)
stripe.customers.updateSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, options, (err, subscription) =>
if err
logStripeWebhookError(err)
return res.send(500, '')
done() done()
module.exports = new SubscriptionHandler() module.exports = new SubscriptionHandler()

View file

@ -157,6 +157,10 @@ module.exports.setup = (app) ->
unless recipient unless recipient
logStripeWebhookError("Recipient not found #{subscription.metadata.id}") logStripeWebhookError("Recipient not found #{subscription.metadata.id}")
return res.send(500, '') return res.send(500, '')
# Recipient cancellations are immediate, no work to perform if recipient's sponsorID is already gone
return res.send(200, '') unless recipient.get('stripe')?.sponsorID?
User.findById recipient.get('stripe').sponsorID, (err, sponsor) => User.findById recipient.get('stripe').sponsorID, (err, sponsor) =>
if err if err
logStripeWebhookError(err) logStripeWebhookError(err)

View file

@ -259,6 +259,8 @@ describe 'Subscriptions', ->
# console.log 'verifyNotSponsoring', sponsorID, recipientID # console.log 'verifyNotSponsoring', sponsorID, recipientID
User.findById sponsorID, (err, sponsor) -> User.findById sponsorID, (err, sponsor) ->
expect(err).toBeNull() expect(err).toBeNull()
expect(sponsor).not.toBeNull()
return done() unless sponsor
stripeInfo = sponsor.get('stripe') stripeInfo = sponsor.get('stripe')
return done() unless stripeInfo?.customerID? return done() unless stripeInfo?.customerID?
checkSubscriptions = (starting_after, done) -> checkSubscriptions = (starting_after, done) ->
@ -282,6 +284,7 @@ describe 'Subscriptions', ->
User.findById sponsorUserID, (err, user) -> User.findById sponsorUserID, (err, user) ->
expect(err).toBeNull() expect(err).toBeNull()
expect(user).not.toBeNull() expect(user).not.toBeNull()
return done() unless user
sponsorStripe = user.get('stripe') sponsorStripe = user.get('stripe')
sponsorCustomerID = sponsorStripe.customerID sponsorCustomerID = sponsorStripe.customerID
numSponsored = sponsorStripe.recipients?.length numSponsored = sponsorStripe.recipients?.length
@ -443,7 +446,7 @@ describe 'Subscriptions', ->
expect(err?).toEqual(false) expect(err?).toEqual(false)
done(updatedUser) done(updatedUser)
unsubscribeRecipient = (sponsor, recipient, immediately, done) -> unsubscribeRecipient = (sponsor, recipient, done) ->
# console.log 'unsubscribeRecipient', sponsor.id, recipient.id # console.log 'unsubscribeRecipient', sponsor.id, recipient.id
stripeInfo = sponsor.get('stripe') stripeInfo = sponsor.get('stripe')
customerID = stripeInfo.customerID customerID = stripeInfo.customerID
@ -467,19 +470,6 @@ describe 'Subscriptions', ->
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) -> request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
expect(err).toBeNull() expect(err).toBeNull()
expect(res.statusCode).toBe(200) expect(res.statusCode).toBe(200)
# Simulate subscription ending after cancellation
return done() unless immediately
# Simulate subscription cancelling at period end
stripe.customers.cancelSubscription customerID, subscriptionID, (err) ->
expect(err).toBeNull()
# Simulate customer.subscription.deleted webhook event
event = _.cloneDeep(customerSubscriptionDeletedSampleEvent)
event.data.object = subscription
request.post {uri: webhookURL, json: event}, (err, res, body) ->
expect(err).toBeNull()
done() done()
# Subscribe a bunch of recipients at once, used for bulk discount testing # Subscribe a bunch of recipients at once, used for bulk discount testing
@ -762,11 +752,11 @@ describe 'Subscriptions', ->
expect(err).toBeNull() expect(err).toBeNull()
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
unsubscribeRecipient user1, user2, true, -> unsubscribeRecipient user1, user2, ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
expect(err).toBeNull() expect(err).toBeNull()
expect(user1.get('stripe').subscriptionID).toBeDefined() expect(user1.get('stripe').subscriptionID).toBeDefined()
expect(user1.get('stripe').recipients).toBeUndefined() expect(_.isEmpty(user1.get('stripe').recipients)).toEqual(true)
expect(user1.isPremium()).toEqual(true) expect(user1.isPremium()).toEqual(true)
User.findById user2.id, (err, user2) -> User.findById user2.id, (err, user2) ->
verifyNotSponsoring user1.id, user2.id, -> verifyNotSponsoring user1.id, user2.id, ->
@ -781,7 +771,7 @@ describe 'Subscriptions', ->
loginNewUser (user1) -> loginNewUser (user1) ->
subscribeRecipients user1, [user2], token, (updatedUser) -> subscribeRecipients user1, [user2], token, (updatedUser) ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
unsubscribeRecipient user1, user2, true, -> unsubscribeRecipient user1, user2, ->
verifyNotSponsoring user1.id, user2.id, -> verifyNotSponsoring user1.id, user2.id, ->
verifyNotRecipient user2.id, done verifyNotRecipient user2.id, done
@ -793,7 +783,7 @@ describe 'Subscriptions', ->
loginNewUser (user1) -> loginNewUser (user1) ->
subscribeRecipients user1, [user2], token, (updatedUser) -> subscribeRecipients user1, [user2], token, (updatedUser) ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
unsubscribeRecipient user1, user2, false, -> unsubscribeRecipient user1, user2, ->
subscribeRecipients user1, [user2], null, (updatedUser) -> subscribeRecipients user1, [user2], null, (updatedUser) ->
verifySponsorship user1.id, user2.id, done verifySponsorship user1.id, user2.id, done
@ -846,7 +836,7 @@ describe 'Subscriptions', ->
expect(err).toBeNull() expect(err).toBeNull()
subscribeRecipients user1, [user2], null, (updatedUser) -> subscribeRecipients user1, [user2], null, (updatedUser) ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
unsubscribeRecipient user1, user2, true, -> unsubscribeRecipient user1, user2, ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
expect(err).toBeNull() expect(err).toBeNull()
expect(user1.get('stripe').subscriptionID).toBeDefined() expect(user1.get('stripe').subscriptionID).toBeDefined()
@ -1138,7 +1128,7 @@ describe 'Subscriptions', ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
# Unsubscribe recipient0 # Unsubscribe recipient0
unsubscribeRecipient user1, recipients.get(0), true, -> unsubscribeRecipient user1, recipients.get(0), ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(1) expect(stripeInfo.recipients.length).toEqual(1)
@ -1150,7 +1140,7 @@ describe 'Subscriptions', ->
expect(subscription.quantity).toEqual(getSubscribedQuantity(1)) expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
# Unsubscribe recipient1 # Unsubscribe recipient1
unsubscribeRecipient user1, recipients.get(1), true, -> unsubscribeRecipient user1, recipients.get(1), ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(0) expect(stripeInfo.recipients.length).toEqual(0)
@ -1186,7 +1176,7 @@ describe 'Subscriptions', ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
# Unsubscribe first recipient # Unsubscribe first recipient
unsubscribeRecipient user1, recipients.get(0), true, -> unsubscribeRecipient user1, recipients.get(0), ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1) expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
@ -1198,7 +1188,7 @@ describe 'Subscriptions', ->
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1)) expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
# Unsubscribe second recipient # Unsubscribe second recipient
unsubscribeRecipient user1, recipients.get(1), true, -> unsubscribeRecipient user1, recipients.get(1), ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2) expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
@ -1218,7 +1208,7 @@ describe 'Subscriptions', ->
# Unsubscribe third recipient # Unsubscribe third recipient
verifySponsorship user1.id, recipients.get(2).id, -> verifySponsorship user1.id, recipients.get(2).id, ->
unsubscribeRecipient user1, recipients.get(2), true, -> unsubscribeRecipient user1, recipients.get(2), ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 3) expect(stripeInfo.recipients.length).toEqual(recipientCount - 3)
@ -1252,8 +1242,9 @@ describe 'Subscriptions', ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
# Unsubscribe first recipient # Unsubscribe first recipient
unsubscribeRecipient user1, recipients.get(0), true, -> unsubscribeRecipient user1, recipients.get(0), ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1) expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
verifyNotSponsoring user1.id, recipients.get(0).id, -> verifyNotSponsoring user1.id, recipients.get(0).id, ->
@ -1264,7 +1255,7 @@ describe 'Subscriptions', ->
expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1)) expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1))
# Unsubscribe last recipient # Unsubscribe last recipient
unsubscribeRecipient user1, recipients.get(recipientCount - 1), true, -> unsubscribeRecipient user1, recipients.get(recipientCount - 1), ->
User.findById user1.id, (err, user1) -> User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe') stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2) expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)