Sponsored sub cleanup on unsubscribe

Sponsored subscriptions can be partially set up when the incremental
subscription charge fails.  This cleans up the sponsored subscriptions
when one of the recipient subscriptions is cancelled.

Related to #2786
This commit is contained in:
Matt Lott 2015-05-28 16:21:08 -07:00
parent 5a272f8bed
commit 9828bd5d1d
3 changed files with 90 additions and 23 deletions

View file

@ -592,7 +592,7 @@ class SubscriptionHandler extends Handler
# Check recipient is currently sponsored
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)
return done({res: 'Can only unsubscribe sponsored subscriptions.', code: 403})

View file

@ -141,6 +141,15 @@ module.exports.setup = (app) ->
checkRecipientSubscription = (done) ->
return done() unless subscription.plan.id is 'basic'
return done() unless subscription.metadata?.id # Shouldn't be possible
deleteUserStripeProp = (user, propName) ->
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
delete stripeInfo[propName]
if _.isEmpty stripeInfo
user.set 'stripe', undefined
else
user.set 'stripe', stripeInfo
User.findById subscription.metadata.id, (err, recipient) =>
if err
logStripeWebhookError(err)
@ -158,33 +167,55 @@ module.exports.setup = (app) ->
# Update sponsor subscription
stripeInfo = _.cloneDeep(sponsor.get('stripe') ? {})
_.remove(stripeInfo.recipients, (s) -> s.userID is recipient.id)
options =
quantity: utils.getSponsoredSubsAmount(subscription.plan.amount, stripeInfo.recipients.length, stripeInfo.subscriptionID?)
unless stripeInfo.sponsorSubscriptionID
# TODO: fix #2786 error for a particular customer which doesn't have this
console.error "Couldn't find sponsorSubscriptionID from stripeInfo", stripeInfo, 'for customer', stripeInfo.customerID, 'with options', options, 'and subscription', subscription, 'for user', recipient.id, 'with sponsor', sponsor.id
return res.send(500, '')
stripe.customers.updateSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, options, (err, subscription) =>
if err
logStripeWebhookError(err)
return res.send(500, '')
stripeInfo.recipients ?= []
# Update sponsor user
sponsor.set 'stripe', stripeInfo
sponsor.save (err) =>
if stripeInfo.sponsorSubscriptionID
_.remove(stripeInfo.recipients, (s) -> s.userID is recipient.id)
options =
quantity: utils.getSponsoredSubsAmount(subscription.plan.amount, stripeInfo.recipients.length, stripeInfo.subscriptionID?)
stripe.customers.updateSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, options, (err, subscription) =>
if err
logStripeWebhookError(err)
return res.send(500, '')
# Update recipient user
stripeInfo = recipient.get('stripe')
delete stripeInfo.sponsorID
if _.isEmpty stripeInfo
recipient.set 'stripe', undefined
else
recipient.set 'stripe', stripeInfo
recipient.save (err) =>
# Update sponsor user
sponsor.set 'stripe', stripeInfo
sponsor.save (err) =>
if err
logStripeWebhookError(err)
return res.send(500, '')
# Update recipient user
deleteUserStripeProp recipient, 'sponsorID'
recipient.save (err) =>
if err
logStripeWebhookError(err)
return res.send(500, '')
return res.send(200, '')
else
# Remove sponsorships from sponsor and recipients
console.error "Couldn't find sponsorSubscriptionID from stripeInfo", stripeInfo, 'for customer', stripeInfo.customerID, 'with options', options, 'and subscription', subscription, 'for user', recipient.id, 'with sponsor', sponsor.id
# Update recipients
createUpdateFn = (recipientID) ->
(callback) ->
User.findById recipientID, (err, recipient) =>
if err
logStripeWebhookError(err)
return callback(err)
deleteUserStripeProp recipient, 'sponsorID'
recipient.save (err) =>
logStripeWebhookError(err) if err
callback(err)
async.parallel (createUpdateFn(recipient.userID) for recipient in stripeInfo.recipients), (err, results) =>
if err
logStripeWebhookError(err)
return res.send(500, '')
# Update sponsor
deleteUserStripeProp sponsor, 'recipients'
sponsor.save (err) =>
if err
logStripeWebhookError(err)
return res.send(500, '')

View file

@ -291,6 +291,7 @@ describe 'Subscriptions', ->
expect(numSponsored).toBeGreaterThan(0)
# Verify Stripe sponsor subscription data
return done() unless sponsorCustomerID and sponsorStripe.sponsorSubscriptionID
stripe.customers.retrieveSubscription sponsorCustomerID, sponsorStripe.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription.plan.amount).toEqual(1)
@ -328,6 +329,7 @@ describe 'Subscriptions', ->
expect(recipientInfo.couponID).toEqual('free')
# Verify Stripe recipient subscription data
return done() unless sponsorCustomerID and recipientInfo.subscriptionID
stripe.customers.retrieveSubscription sponsorCustomerID, recipientInfo.subscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription.plan.amount).toEqual(subPrice)
@ -445,6 +447,8 @@ describe 'Subscriptions', ->
# console.log 'unsubscribeRecipient', sponsor.id, recipient.id
stripeInfo = sponsor.get('stripe')
customerID = stripeInfo.customerID
expect(stripeInfo.recipients).toBeDefined()
return done() unless stripeInfo.recipients
for r in stripeInfo.recipients
if r.userID is recipient.id
subscriptionID = r.subscriptionID
@ -739,6 +743,38 @@ describe 'Subscriptions', ->
expect(user1.isPremium()).toEqual(true)
verifySponsorship user1.id, user2.id, done
it 'Clean up sponsorships upon sub cancel after setup sponsor sub fails', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeUser user1, token, null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
subscribeRecipients user1, [user2], null, (updatedUser) ->
# Delete user1 sponsorSubscriptionID to simulate failed sponsor sub
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
stripeInfo = _.cloneDeep(user1.get('stripe') ? {})
delete stripeInfo.sponsorSubscriptionID
user1.set 'stripe', stripeInfo
user1.save (err, user1) ->
expect(err).toBeNull()
User.findById user1.id, (err, user1) ->
unsubscribeRecipient user1, user2, true, ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
expect(user1.get('stripe').subscriptionID).toBeDefined()
expect(user1.get('stripe').recipients).toBeUndefined()
expect(user1.isPremium()).toEqual(true)
User.findById user2.id, (err, user2) ->
verifyNotSponsoring user1.id, user2.id, ->
verifyNotRecipient user2.id, done
it 'Unsubscribed user1 unsubscribes user2 and their sub ends', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }