Prepaid subscriptions

Admins can generate a prepaid code, which a user can use to subscribe
for free via the account/subscription page.
The subscription will be identical to the normal monthly subscription
(e.g. 3500 gems per month), except they won’t be charged.
Does not require the recipient to enter billing information.
Can be applied to an existing subscription, which will be converted to
free.
Prepaid code can only be used once.
Prepaid subscription cannot be unsubscribed via the UI.
This commit is contained in:
Matt Lott 2015-03-19 15:02:45 -07:00
parent aee68e899a
commit fec3ac38e9
18 changed files with 798 additions and 232 deletions

View file

@ -455,6 +455,8 @@
no_users_subscribed: "No users subscribed, please double check your email addresses."
current_recipients: "Current Recipients"
unsubscribing: "Unsubscribing..."
subscribe_prepaid: "Click Subscribe to use prepaid code"
using_prepaid: "Using prepaid code for monthly subscription"
choose_hero:
choose_hero: "Choose Your Hero"

View file

@ -0,0 +1,14 @@
c = require './../schemas'
PrepaidSchema = c.object({title: 'Prepaid', required: ['creator', 'redeemer', 'type']}, {
creator: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
redeemer: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
code: c.shortString(title: "Unique code to redeem")
type: { type: 'string' }
status: { enum: ['active', 'used'], default: 'active' }
properties: {type: 'object'}
})
c.extendBasicProperties(PrepaidSchema, 'prepaid')
module.exports = PrepaidSchema

View file

@ -288,6 +288,7 @@ _.extend UserSchema.properties,
token: { type: 'string' }
couponID: { type: 'string' }
free: { type: ['boolean', 'string'], format: 'date-time', description: 'Type string is subscription end date' }
prepaidCode: c.shortString description: 'Prepaid code to apply to sub purchase'
# Sponsored subscriptions
subscribeEmails: c.array { description: 'Input for subscribing other users' }, c.shortString()

View file

@ -1,2 +1,5 @@
#admin-view
color: black
color: black
#free-sub-input
min-width: 50%

View file

@ -19,9 +19,15 @@ block content
.panel.panel-default
.panel-heading
h3(data-i18n="subscribe.personal_sub")
if personalSub.prepaidCode && !personalSub.usingPrepaidCode
div
span(data-i18n="subscribe.subscribe_prepaid")
span.spl.spr= personalSub.prepaidCode
.panel-body
if personalSub.state === 'loading'
.alert.alert-info(data-i18n="subscribe.loading_info")
else if personalSub.state === 'subscribing'
.alert.alert-info(data-i18n="subscribe.subscribing")
else if personalSub.sponsor
div
span(data-i18n="subscribe.managed_by")
@ -30,11 +36,30 @@ block content
div
span(data-i18n="subscribe.will_be_cancelled")
span.spl.spr= moment(personalSub.endDate).format('l')
else if personalSub.usingPrepaidCode
div(data-i18n="subscribe.using_prepaid")
else if personalSub.self
if personalSub.subscribed
button.end-subscription-button.btn.btn-lg.btn-warning(data-i18n="subscribe.unsubscribe") Unsubscribe
else
if personalSub.state === 'declined'
.alert.alert-danger.alert-dismissible
span(data-i18n="buy_gems.declined")
button.close(type="button" data-dismiss="alert")
span(aria-hidden="true") ×
br
else if personalSub.state === 'unknown_error'
.alert.alert-danger.alert-dismissible
button.close(type="button" data-dismiss="alert")
span(aria-hidden="true") ×
p(data-i18n="loading_error.unknown")
p= personalSub.stateMessage
br
if !personalSub.subscribed || personalSub.prepaidCode
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
else
button.end-subscription-button.btn.btn-lg.btn-warning(data-i18n="subscribe.unsubscribe") Unsubscribe
.unsubscribe-feedback.row.secret
.col-lg-7
h3
@ -75,10 +100,10 @@ block content
tr
th(data-i18n="account.cost")
td= personalSub.cost
if personalSub.card
tr
th(data-i18n="account.card")
td= personalSub.card
if personalSub.card
tr
th(data-i18n="account.card")
td= personalSub.card
else
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
@ -129,7 +154,7 @@ block content
button.close(type="button" data-dismiss="alert")
span(aria-hidden="true") ×
p(data-i18n="loading_error.unknown")
p= stateMessage
p= recipientSubs.stateMessage
else if recipientSubs.justSubscribed && recipientSubs.justSubscribed.length > 0
br
.alert.alert-success.alert-dismissible

View file

@ -47,6 +47,14 @@ block content
li
a(href="/admin/growth", data-i18n="admin.growth") Growth
if me.isAdmin()
hr
h3 Prepaids
a.btn.btn-secondary#create-free-sub-btn Create Free Subscription Link
span.spl.spr
if freeSubLink
input#free-sub-input(type="text", readonly, value="#{freeSubLink}")
hr
h3 Achievements

View file

@ -18,9 +18,14 @@ utils = require 'core/utils'
# TODO: next payment amount incorrect if have an expiring personal sub
# TODO: consider hiding managed subscription body UI while things are updating to avoid brief legacy data
# TODO: Next payment info for personal sub displays most recent payment when resubscribing before trial end
# TODO: PersonalSub and RecipientSubs have similar subscribe APIs
# TODO: Better recovery from trying to reuse a prepaid
# TODO: No way to unsubscribe from prepaid subscription
# TODO: Refactor state machines driving the UI. They've become a hot mess.
# TODO: Get basic plan price dynamically
basicPlanPrice = 999
basicPlanID = 'basic'
module.exports = class SubscriptionView extends RootView
id: "subscription-view"
@ -41,7 +46,8 @@ module.exports = class SubscriptionView extends RootView
constructor: (options) ->
super(options)
@personalSub = new PersonalSub(@supermodel)
prepaidCode = utils.getQueryVariable '_ppc'
@personalSub = new PersonalSub(@supermodel, prepaidCode)
@recipientSubs = new RecipientSubs(@supermodel)
@personalSub.update => @render?()
@recipientSubs.update => @render?()
@ -55,7 +61,10 @@ module.exports = class SubscriptionView extends RootView
# Personal Subscriptions
onClickStartSubscription: (e) ->
@openModalView new SubscribeModal()
if @personalSub.prepaidCode
@personalSub.subscribe(=> @render?())
else
@openModalView new SubscribeModal()
window.tracker?.trackEvent 'Show subscription modal', category: 'Subscription', label: 'account subscription view'
onSubscribed: ->
@ -95,7 +104,45 @@ module.exports = class SubscriptionView extends RootView
# Helper classes for managing subscription actions and updating UI state
class PersonalSub
constructor: (@supermodel) ->
constructor: (@supermodel, @prepaidCode) ->
subscribe: (render) ->
return unless @prepaidCode
if @prepaidCode is me.get('stripe')?.prepaidCode
delete @prepaidCode
return render()
@state = 'subscribing'
@stateMessage = ''
render()
stripeInfo = _.clone(me.get('stripe') ? {})
stripeInfo.planID = basicPlanID
stripeInfo.prepaidCode = @prepaidCode
me.set('stripe', stripeInfo)
me.once 'sync', =>
application.tracker?.trackEvent 'Finished subscription purchase', revenue: 0
delete @prepaidCode
@update(render)
me.once 'error', (user, response, options) =>
console.error 'We got an error subscribing with Stripe from our server:', response
stripeInfo = me.get('stripe') ? {}
delete stripeInfo.planID
delete stripeInfo.prepaidCode
me.set('stripe', stripeInfo)
xhr = options.xhr
if xhr.status is 402
@state = 'declined'
@stateMessage = ''
else
if xhr.status is 403
delete @prepaidCode
@state = 'unknown_error'
@stateMessage = "#{xhr.status}: #{xhr.responseText}"
render()
me.patch({headers: {'X-Change-Plan': 'true'}})
unsubscribe: (message) ->
removeStripe = =>
@ -133,6 +180,11 @@ class PersonalSub
success: onSubSponsorSuccess
}, 0).load()
else if stripeInfo.prepaidCode
@usingPrepaidCode = true
delete @state
render()
else if stripeInfo.subscriptionID
@self = true
@active = me.isPremium()
@ -146,7 +198,7 @@ class PersonalSub
periodEnd = new Date((sub.trial_end or sub.current_period_end) * 1000)
if sub.cancel_at_period_end
@activeUntil = periodEnd
else
else if sub.discount?.coupon?.id isnt 'free'
@nextPaymentDate = periodEnd
@cost = "$#{(sub.plan.amount/100).toFixed(2)}"
else
@ -165,6 +217,7 @@ class PersonalSub
@free = stripeInfo.free
delete @state
render()
else
delete @state
render()

View file

@ -14,6 +14,12 @@ module.exports = class MainAdminView extends RootView
'click #user-search-button': 'searchForUser'
'click #increment-button': 'incrementUserAttribute'
'click #user-search-result': 'onClickUserSearchResult'
'click #create-free-sub-btn': 'onClickFreeSubLink'
getRenderData: ->
context = super()
context.freeSubLink = @freeSubLink
context
checkForFormSubmissionEnterPress: (e) ->
if e.which is 13 and @$el.find('#espionage-name-or-email').val() isnt ''
@ -61,7 +67,25 @@ module.exports = class MainAdminView extends RootView
val = $('#increment-field').val()
me.set(val, me.get(val) + 1)
me.save()
onClickUserSearchResult: (e) ->
userID = $(e.target).closest('tr').data('user-id')
@openModalView new AdministerUserModal({}, userID) if userID
onClickFreeSubLink: (e) =>
delete @freeSubLink
return unless me.isAdmin()
options =
url: '/db/prepaid/-/create'
data: {type: 'subscription'}
method: 'POST'
options.success = (model, response, options) =>
# TODO: Don't hardcode domain.
if application.isProduction()
@freeSubLink = "https://codecombat.com/account/subscription?_ppc=#{model.code}"
else
@freeSubLink = "http://localhost:3000/account/subscription?_ppc=#{model.code}"
@render?()
options.error = (model, response, options) =>
console.error 'Failed to create prepaid', response
@supermodel.addRequestResource('create_prepaid', options, 0).load()

View file

@ -154,6 +154,7 @@ module.exports = class SubscribeModal extends ModalView
stripe = me.get('stripe') ? {}
delete stripe.token
delete stripe.planID
# TODO: Need me.set('stripe', stripe) here?
xhr = options.xhr
if xhr.status is 402
@state = 'declined'

View file

@ -23,6 +23,7 @@ module.exports.handlers =
'earned_achievement': 'achievements/earned_achievement_handler'
'poll': 'polls/poll_handler'
'user_polls_record': 'polls/user_polls_record_handler'
'prepaid': 'prepaids/prepaid_handler'
module.exports.routes =
[

View file

@ -4,6 +4,7 @@
async = require 'async'
Handler = require '../commons/Handler'
discountHandler = require './discount_handler'
Prepaid = require '../prepaids/Prepaid'
User = require '../users/User'
{findStripeSubscription} = require '../lib/utils'
{getSponsoredSubsAmount} = require '../../app/core/utils'
@ -25,52 +26,50 @@ class SubscriptionHandler extends Handler
return done({res: 'You must be signed in to subscribe.', code: 403})
token = req.body.stripe.token
prepaidCode = req.body.stripe.prepaidCode
customerID = user.get('stripe')?.customerID
if not (token or customerID)
@logSubscriptionError(user, 'Missing stripe token or customer ID.')
return done({res: 'Missing stripe token or customer ID.', code: 422})
if not (token or customerID or prepaidCode)
@logSubscriptionError(user, 'Missing Stripe token or customer ID or prepaid code')
return done({res: 'Missing Stripe token or customer ID or prepaid code', code: 422})
# Create/retrieve Stripe customer
if token
if customerID
# Get Stripe customer
if customerID
if token
stripe.customers.update customerID, { card: token }, (err, customer) =>
if err or not customer
# should not happen outside of test and production polluting each other
@logSubscriptionError(user, 'Cannot find customer: ' + customerID + '\n\n' + err)
return done({res: 'Cannot find customer.', code: 404})
@checkForExistingSubscription(req, user, customer, done)
@checkForCoupon(req, user, customer, done)
else
options =
card: token
email: user.get('email')
metadata: { id: user._id + '', slug: user.get('slug') }
stripe.customers.create options, (err, customer) =>
stripe.customers.retrieve customerID, (err, customer) =>
if err
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
return done({res: 'Card error', code: 402})
else
@logSubscriptionError(user, 'Stripe customer creation error. ' + err)
return done({res: 'Database error.', code: 500})
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
stripeInfo.customerID = customer.id
user.set('stripe', stripeInfo)
user.save (err) =>
if err
@logSubscriptionError(user, 'Stripe customer id save db error. ' + err)
return done({res: 'Database error.', code: 500})
@checkForExistingSubscription(req, user, customer, done)
@logSubscriptionError(user, 'Stripe customer retrieve error. ' + err)
return done({res: 'Database error.', code: 500})
@checkForCoupon(req, user, customer, done)
else
stripe.customers.retrieve(customerID, (err, customer) =>
options =
email: user.get('email')
metadata: { id: user._id + '', slug: user.get('slug') }
options.card = token if token?
stripe.customers.create options, (err, customer) =>
if err
@logSubscriptionError(user, 'Stripe customer retrieve error. ' + err)
return done({res: 'Database error.', code: 500})
@checkForExistingSubscription(req, user, customer, done)
)
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
return done({res: 'Card error', code: 402})
else
@logSubscriptionError(user, 'Stripe customer creation error. ' + err)
return done({res: 'Database error.', code: 500})
checkForExistingSubscription: (req, user, customer, done) ->
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
stripeInfo.customerID = customer.id
user.set('stripe', stripeInfo)
user.save (err) =>
if err
@logSubscriptionError(user, 'Stripe customer id save db error. ' + err)
return done({res: 'Database error.', code: 500})
@checkForCoupon(req, user, customer, done)
checkForCoupon: (req, user, customer, done) ->
# Check if user is subscribing someone else
if req.body.stripe?.subscribeEmails?
return @updateStripeRecipientSubscriptions req, user, customer, done
@ -78,12 +77,31 @@ class SubscriptionHandler extends Handler
if user.get('stripe')?.sponsorID
return done({res: 'You already have a sponsored subscription.', code: 403})
couponID = user.get('stripe')?.couponID
if req.body?.stripe?.prepaidCode
Prepaid.findOne code: req.body.stripe.prepaidCode, (err, prepaid) =>
if err
@logSubscriptionError(user, 'Prepaid lookup error. ' + err)
return done({res: 'Database error.', code: 500})
return done({res: 'Prepaid not found', code: 404}) unless prepaid?
return done({res: 'Prepaid not for subscription', code: 403}) unless prepaid.get('type') is 'subscription'
return done({res: 'Prepaid has already been used', code: 403}) unless prepaid.get('status') is 'active'
return done({res: 'Database error.', code: 500}) unless prepaid.get('properties')?.couponID
couponID = prepaid.get('properties').couponID
# SALE LOGIC
# overwrite couponID with another for everyone-sales
#couponID = 'hoc_399' if not couponID
# Update user
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
stripeInfo.couponID = couponID
stripeInfo.prepaidCode = req.body.stripe.prepaidCode
user.set('stripe', stripeInfo)
@checkForExistingSubscription(req, user, customer, couponID, done)
else
couponID = user.get('stripe')?.couponID
# SALE LOGIC
# overwrite couponID with another for everyone-sales
#couponID = 'hoc_399' if not couponID
@checkForExistingSubscription(req, user, customer, couponID, done)
checkForExistingSubscription: (req, user, customer, couponID, done) ->
findStripeSubscription customer.id, subscriptionID: user.get('stripe')?.subscriptionID, (subscription) =>
if subscription
@ -97,19 +115,25 @@ class SubscriptionHandler extends Handler
if err
@logSubscriptionError(user, 'Stripe cancel subscription error. ' + err)
return done({res: 'Database error.', code: 500})
options = { plan: 'basic', metadata: {id: user.id}, trial_end: subscription.current_period_end }
options.coupon = couponID if couponID
stripe.customers.createSubscription customer.id, options, (err, subscription) =>
if err
@logSubscriptionError(user, 'Stripe customer plan setting error. ' + err)
return done({res: 'Database error.', code: 500})
@updateUser(req, user, customer, subscription, false, done)
else if couponID
# Update subscription with given couponID
stripe.customers.updateSubscription customer.id, subscription.id, coupon: couponID, (err, subscription) =>
if err
@logSubscriptionError(user, 'Stripe update subscription coupon error. ' + err)
return done({res: 'Database error.', code: 500})
@updateUser(req, user, customer, subscription, false, done)
else
# can skip creating the subscription
return @updateUser(req, user, customer, subscription, false, done)
# Skip creating the subscription
@updateUser(req, user, customer, subscription, false, done)
else
options = { plan: 'basic', metadata: {id: user.id}}
@ -143,8 +167,25 @@ class SubscriptionHandler extends Handler
if err
@logSubscriptionError(user, 'Stripe user plan saving error. ' + err)
return done({res: 'Database error.', code: 500})
user?.saveActiveUser 'subscribe'
return done()
if stripeInfo.prepaidCode?
# Update prepaid to 'used'
Prepaid.findOne code: stripeInfo.prepaidCode, (err, prepaid) =>
if err
@logSubscriptionError(user, 'Prepaid find error. ' + err)
return done({res: 'Database error.', code: 500})
unless prepaid?
@logSubscriptionError(user, "Expected prepaid not found: #{stripeInfo.prepaidCode}")
return done({res: 'Database error.', code: 500})
prepaid.set('status', 'used')
prepaid.set('redeemer', user.get('_id'))
prepaid.save (err) =>
if err
@logSubscriptionError(user, 'Prepaid update error. ' + err)
return done({res: 'Database error.', code: 500})
done()
else
done()
updateStripeRecipientSubscriptions: (req, user, customer, done) ->
return done({res: 'Database error.', code: 500}) unless req.body.stripe?.subscribeEmails?

View file

@ -0,0 +1,5 @@
mongoose = require 'mongoose'
PrepaidSchema = new mongoose.Schema {}, {strict: false, minimize: false}
module.exports = Prepaid = mongoose.model('prepaid', PrepaidSchema)

View file

@ -0,0 +1,45 @@
Handler = require '../commons/Handler'
Prepaid = require './Prepaid'
# TODO: Should this happen on a save() call instead of a prepaid/-/create post?
# TODO: Probably a better way to create a unique 8 charactor string property using db voodoo
PrepaidHandler = class PrepaidHandler extends Handler
modelClass: Prepaid
jsonSchema: require '../../app/schemas/models/prepaid.schema'
allowedMethods: ['POST']
hasAccess: (req) ->
req.user?.isAdmin()
getByRelationship: (req, res, args...) ->
relationship = args[1]
return @createPrepaid(req, res) if relationship is 'create'
super arguments...
createPrepaid: (req, res) ->
return @sendForbiddenError(res) unless @hasAccess(req)
return @sendForbiddenError(res) unless req.body.type is 'subscription'
@generateNewCode (code) =>
return @sendDatabaseError(res, 'Database error.') unless code
prepaid = new Prepaid
creator: req.user.id
type: req.body.type
status: 'active'
code: code
properties:
couponID: 'free'
prepaid.save (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, prepaid.toObject())
generateNewCode: (done) ->
tryCode = ->
code = _.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8).join('')
Prepaid.findOne code: code, (err, prepaid) ->
return if err
return done(code) unless prepaid
tryCode()
tryCode()
module.exports = new PrepaidHandler()

View file

@ -108,6 +108,7 @@ module.exports.setup = (app) ->
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
delete stripeInfo.planID
delete stripeInfo.prepaidCode
delete stripeInfo.subscriptionID
user.set('stripe', stripeInfo)
user.save (err) =>

View file

@ -143,7 +143,7 @@ UserHandler = class UserHandler extends Handler
)
else
wantsPlan = req.body.stripe.planID?
hasPlan = user.get('stripe')?.planID?
hasPlan = user.get('stripe')?.planID? and not req.body.stripe.prepaidCode?
finishSubscription hasPlan, wantsPlan
# Discount setting

View file

@ -37,6 +37,7 @@ models_path = [
'../../server/achievements/Achievement'
'../../server/achievements/EarnedAchievement'
'../../server/payments/Payment'
'../../server/prepaids/Prepaid'
]
for m in models_path
@ -113,6 +114,11 @@ wrapUpGetUser = (email, user, done) ->
GLOBAL.getURL = (path) ->
return 'http://localhost:3001' + path
GLOBAL.createPrepaid = (type, done) ->
options = uri: GLOBAL.getURL('/db/prepaid/-/create')
options.json = type: type if type?
request.post options, done
newUserCount = 0
GLOBAL.createNewUser = (done) ->
name = password = "user#{newUserCount++}"

View file

@ -0,0 +1,95 @@
require '../common'
describe '/db/prepaid', ->
prepaidURL = getURL('/db/prepaid')
prepaidCreateURL = getURL('/db/prepaid/-/create')
verifyPrepaid = (user, prepaid, done) ->
expect(prepaid.creator).toEqual(user.id)
expect(prepaid.type).toEqual('subscription')
expect(prepaid.status).toEqual('active')
expect(prepaid.code).toMatch(/^\w{8}$/)
expect(prepaid.properties?.couponID).toEqual('free')
done()
it 'Clear database users and prepaids', (done) ->
clearModels [User, Prepaid], (err) ->
throw err if err
done()
it 'Anonymous creates prepaid code', (done) ->
createPrepaid 'subscription', (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(401)
done()
it 'Non-admin creates prepaid code', (done) ->
loginNewUser (user1) ->
expect(user1.isAdmin()).toEqual(false)
createPrepaid 'subscription', (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Admin creates prepaid code with type subscription', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
verifyPrepaid user1, body, done
it 'Admin creates prepaid code with invalid type', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'bulldozer', (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Admin creates prepaid code with no type specified', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid null, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Non-admin requests /db/prepaid', (done) ->
loginNewUser (user1) ->
expect(user1.isAdmin()).toEqual(false)
request.get {uri: prepaidURL}, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Admin requests /db/prepaid', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
request.get {uri: prepaidURL}, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
prepaids = JSON.parse(body)
found = false
for p in prepaids
if p._id is prepaid._id
found = true
verifyPrepaid user1, p, done
break
expect(found).toEqual(true)
done() unless found

View file

@ -217,7 +217,7 @@ describe '/db/user, editing stripe property', ->
setTimeout(f, 500) # bit of a race condition here, response returns before stripe has been updated
describe 'Sponsored subscriptions', ->
describe 'Subscriptions', ->
# TODO: Test recurring billing via webhooks
# TODO: Test error rollbacks, Stripe is authority
@ -359,23 +359,37 @@ describe 'Sponsored subscriptions', ->
expect(payment.get('gems')).toBeGreaterThan(subGems - 1)
done()
subscribeUser = (user, token, done) ->
subscribeUser = (user, token, prepaidCode, done) ->
requestBody = user.toObject()
requestBody.stripe =
planID: 'basic'
requestBody.stripe.token = token.id if token?
requestBody.stripe.prepaidCode = prepaidCode if prepaidCode?
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
expect(body.stripe.customerID).toBeDefined()
expect(body.stripe.planID).toBe('basic')
expect(body.stripe.token).toBeUndefined()
if prepaidCode?
expect(body.stripe.prepaidCode).toEqual(prepaidCode)
expect(body.stripe.couponID).toEqual('free')
expect(body.purchased.gems).toBeGreaterThan(subGems - 1)
User.findById user.id, (err, user) ->
stripeInfo = user.get('stripe')
expect(stripeInfo.customerID).toBeDefined()
expect(stripeInfo.planID).toBe('basic')
expect(stripeInfo.token).toBeUndefined()
if prepaidCode?
expect(stripeInfo.prepaidCode).toEqual(prepaidCode)
expect(stripeInfo.couponID).toEqual('free')
expect(user.get('purchased').gems).toBeGreaterThan(subGems - 1)
done()
unsubscribeUser = (user, done) ->
requestBody = user.toObject()
delete requestBody.stripe.planID
delete requestBody.stripe.prepaidCode
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
@ -383,7 +397,11 @@ describe 'Sponsored subscriptions', ->
expect(user.get('stripe').customerID).toBeDefined()
expect(user.get('stripe').planID).toBeUndefined()
expect(user.get('stripe').token).toBeUndefined()
done()
stripe.customers.retrieveSubscription user.get('stripe').customerID, user.get('stripe').subscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription?.cancel_at_period_end).toEqual(true)
done()
subscribeRecipients = (sponsor, recipients, token, done) ->
# console.log 'subscribeRecipients', sponsor.id, (recipient.id for recipient in recipients), token?
@ -444,7 +462,7 @@ describe 'Sponsored subscriptions', ->
# Simulate subscription ending after cancellation
return done() unless immediately
# Simulate subscription cancelling a trial end
# Simulate subscription cancelling at period end
stripe.customers.cancelSubscription customerID, subscriptionID, (err) ->
expect(err).toBeNull()
@ -494,7 +512,177 @@ describe 'Sponsored subscriptions', ->
throw err if err
done()
describe 'Basic', ->
describe 'Personal', ->
it 'Subscribe user with new token', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
subscribeUser user1, token, null, done
it 'Admin subscribes self with valid prepaid', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('status')).toEqual('used')
done()
it 'Admin subscribes self with invalid prepaid', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
requestBody = user1.toObject()
requestBody.stripe =
planID: 'basic'
requestBody.stripe.prepaidCode = 'MattMatt'
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(404)
done()
it 'User2 subscribes with used prepaid', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
loginNewUser (user2) ->
requestBody = user2.toObject()
requestBody.stripe =
planID: 'basic'
requestBody.stripe.prepaidCode = prepaid.code
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Subscribe normally, subscribe with valid prepaid', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
subscribeUser user1, token, null, ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('status')).toEqual('used')
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()
expect(subscription.discount?.coupon?.id).toEqual('free')
done()
it 'Subscribe with coupon, subscribe with valid prepaid', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
requestBody = user1.toObject()
requestBody.stripe =
planID: 'basic'
token: token.id
couponID: '20pct'
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, updatedUser) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
createPrepaid 'subscription', (err, res, prepaid) ->
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('status')).toEqual('used')
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()
expect(subscription.discount?.coupon?.id).toEqual('free')
done()
it 'Subscribe with prepaid, then cancel', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('status')).toEqual('used')
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
unsubscribeUser user1, ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
expect(user1.get('stripe').prepaidCode).toEqual(prepaid.get('code'))
done()
it 'Subscribe with prepaid, then delete', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
expect(err).toBeNull()
expect(prepaid.get('status')).toEqual('used')
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
unsubscribeUser user1, ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
stripeInfo = user1.get('stripe')
expect(stripeInfo.prepaidCode).toEqual(prepaid.get('code'))
# Delete subscription
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.subscriptionID, (err, subscription) ->
expect(err).toBeNull()
event = _.cloneDeep(customerSubscriptionDeletedSampleEvent)
event.data.object = subscription
request.post {uri: webhookURL, json: event}, (err, res, body) ->
expect(err).toBeNull()
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
stripeInfo = user1.get('stripe')
expect(stripeInfo.planID).toBeUndefined()
expect(stripeInfo.prepaidCode).toBeUndefined()
expect(stripeInfo.subscriptionID).toBeUndefined()
done()
describe 'Sponsored', ->
it 'Unsubscribed user1 subscribes user2', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
@ -572,7 +760,7 @@ describe 'Sponsored subscriptions', ->
}, (err, token) ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeUser user1, token, (updatedUser) ->
subscribeUser user1, token, null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
subscribeRecipients user1, [user2], null, (updatedUser) ->
@ -588,7 +776,7 @@ describe 'Sponsored subscriptions', ->
}, (err, token) ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeUser user1, token, (updatedUser) ->
subscribeUser user1, token, null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
subscribeRecipients user1, [user2], null, (updatedUser) ->
@ -608,12 +796,12 @@ describe 'Sponsored subscriptions', ->
}, (err, token) ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeUser user1, token, (updatedUser) ->
subscribeUser user1, token, null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
subscribeRecipients user1, [user2], null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
unsubscribeUser user1, (updatedUser) ->
unsubscribeUser user1, ->
verifySponsorship user1.id, user2.id, done
it 'Sponsored user2 tries to subscribe', (done) ->
@ -636,6 +824,29 @@ describe 'Sponsored subscriptions', ->
expect(res.statusCode).toBe(403)
done()
it 'Sponsored user2 tries to subscribe with valid prepaid', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeRecipients user1, [user2], token, (updatedUser) ->
loginUser user2, (user2) ->
user2.set('permissions', ['admin'])
user2.save (err, user1) ->
expect(err).toBeNull()
expect(user2.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
requestBody = user2.toObject()
requestBody.stripe =
planID: 'basic'
prepaidCode: prepaid.code
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Sponsored user2 tries to unsubscribe', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
@ -771,7 +982,7 @@ describe 'Sponsored subscriptions', ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
subscribeUser user1, token, (updatedUser) ->
subscribeUser user1, token, null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
expect(user1.get('stripe').subscriptionID).toBeDefined()
@ -791,186 +1002,216 @@ describe 'Sponsored subscriptions', ->
expect(sub.metadata?.id).toEqual(user1.id)
done()
describe 'Bulk discounts', ->
# Bulk discount algorithm (includes personal sub):
# 1 100%
# 2-11 80%
# 12+ 60%
it 'Subscribe with prepaid, then get sponsored', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
loginNewUser (user2) ->
requestBody = user2.toObject()
requestBody.stripe =
token: token.id
subscribeEmails: [user1.get('emailLower')]
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
User.findById user1.id, (err, user) ->
expect(err).toBeNull()
stripeInfo = user.get('stripe')
expect(stripeInfo.customerID).toBeDefined()
expect(stripeInfo.planID).toBeDefined()
expect(stripeInfo.subscriptionID).toBeDefined()
expect(stripeInfo.sponsorID).toBeUndefined()
done()
it 'Unsubscribed user1 subscribes two users', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
createNewUser (user3) ->
createNewUser (user2) ->
describe 'Bulk discounts', ->
# Bulk discount algorithm (includes personal sub):
# 1 100%
# 2-11 80%
# 12+ 60%
it 'Unsubscribed user1 subscribes two users', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
createNewUser (user3) ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeRecipients user1, [user2, user3], token, (updatedUser) ->
verifySponsorship user1.id, user2.id, ->
verifySponsorship user1.id, user3.id, done
it 'Subscribed user1 subscribes 2 users, unsubscribes 2', (done) ->
recipientCount = 2
recipientsToVerify = [0, 1]
recipients = new SubbedRecipients recipientCount, recipientsToVerify
# Create recipients
recipients.createRecipients ->
expect(recipients.length()).toEqual(recipientCount)
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
# Create sponsor user
loginNewUser (user1) ->
subscribeRecipients user1, [user2, user3], token, (updatedUser) ->
verifySponsorship user1.id, user2.id, ->
verifySponsorship user1.id, user3.id, done
subscribeUser user1, token, null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
it 'Subscribed user1 subscribes 2 users, unsubscribes 2', (done) ->
recipientCount = 2
recipientsToVerify = [0, 1]
recipients = new SubbedRecipients recipientCount, recipientsToVerify
# Subscribe recipients
recipients.subRecipients user1, null, ->
User.findById user1.id, (err, user1) ->
# Create recipients
recipients.createRecipients ->
expect(recipients.length()).toEqual(recipientCount)
# Unsubscribe recipient0
unsubscribeRecipient user1, recipients.get(0), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(1)
verifyNotSponsoring user1.id, recipients.get(0).id, ->
verifyNotRecipient recipients.get(0).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
# Unsubscribe recipient1
unsubscribeRecipient user1, recipients.get(1), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(0)
verifyNotSponsoring user1.id, recipients.get(1).id, ->
verifyNotRecipient recipients.get(1).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(0)
done()
# Create sponsor user
loginNewUser (user1) ->
subscribeUser user1, token, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
it 'Subscribed user1 subscribes 3 users, unsubscribes 2, themselves, then 1', (done) ->
recipientCount = 3
recipientsToVerify = [0, 1, 2]
recipients = new SubbedRecipients recipientCount, recipientsToVerify
# Subscribe recipients
recipients.subRecipients user1, null, ->
User.findById user1.id, (err, user1) ->
# Create recipients
recipients.createRecipients ->
expect(recipients.length()).toEqual(recipientCount)
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
# Unsubscribe recipient0
unsubscribeRecipient user1, recipients.get(0), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(1)
verifyNotSponsoring user1.id, recipients.get(0).id, ->
verifyNotRecipient recipients.get(0).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
# Create sponsor user
loginNewUser (user1) ->
subscribeUser user1, token, null, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
# Unsubscribe recipient1
unsubscribeRecipient user1, recipients.get(1), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(0)
verifyNotSponsoring user1.id, recipients.get(1).id, ->
verifyNotRecipient recipients.get(1).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(0)
done()
# Subscribe recipients
recipients.subRecipients user1, null, ->
User.findById user1.id, (err, user1) ->
it 'Subscribed user1 subscribes 3 users, unsubscribes 2, themselves, then 1', (done) ->
recipientCount = 3
recipientsToVerify = [0, 1, 2]
recipients = new SubbedRecipients recipientCount, recipientsToVerify
# Unsubscribe first recipient
unsubscribeRecipient user1, recipients.get(0), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
verifyNotSponsoring user1.id, recipients.get(0).id, ->
verifyNotRecipient recipients.get(0).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
# Create recipients
recipients.createRecipients ->
expect(recipients.length()).toEqual(recipientCount)
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
# Unsubscribe second recipient
unsubscribeRecipient user1, recipients.get(1), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
verifyNotSponsoring user1.id, recipients.get(1).id, ->
verifyNotRecipient recipients.get(1).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
# Create sponsor user
loginNewUser (user1) ->
subscribeUser user1, token, (updatedUser) ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
# Unsubscribe self
User.findById user1.id, (err, user1) ->
unsubscribeUser user1, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.planID).toBeUndefined()
# Subscribe recipients
recipients.subRecipients user1, null, ->
User.findById user1.id, (err, user1) ->
# Unsubscribe third recipient
verifySponsorship user1.id, recipients.get(2).id, ->
unsubscribeRecipient user1, recipients.get(2), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 3)
verifyNotSponsoring user1.id, recipients.get(2).id, ->
verifyNotRecipient recipients.get(2).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
done()
# Unsubscribe first recipient
unsubscribeRecipient user1, recipients.get(0), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
verifyNotSponsoring user1.id, recipients.get(0).id, ->
verifyNotRecipient recipients.get(0).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
it 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->
# TODO: verify interim invoices?
recipientCount = 13
recipientsToVerify = [0, 1, 10, 11, 12]
recipients = new SubbedRecipients recipientCount, recipientsToVerify
# Unsubscribe second recipient
unsubscribeRecipient user1, recipients.get(1), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
verifyNotSponsoring user1.id, recipients.get(1).id, ->
verifyNotRecipient recipients.get(1).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
# Create recipients
recipients.createRecipients ->
expect(recipients.length()).toEqual(recipientCount)
# Unsubscribe self
User.findById user1.id, (err, user1) ->
unsubscribeUser user1, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.planID).toBeUndefined()
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
# Unsubscribe third recipient
verifySponsorship user1.id, recipients.get(2).id, ->
unsubscribeRecipient user1, recipients.get(2), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 3)
verifyNotSponsoring user1.id, recipients.get(2).id, ->
verifyNotRecipient recipients.get(2).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
done()
# Create sponsor user
loginNewUser (user1) ->
it 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->
# TODO: verify interim invoices?
recipientCount = 13
recipientsToVerify = [0, 1, 10, 11, 12]
recipients = new SubbedRecipients recipientCount, recipientsToVerify
# Subscribe recipients
recipients.subRecipients user1, token, ->
User.findById user1.id, (err, user1) ->
# Create recipients
recipients.createRecipients ->
expect(recipients.length()).toEqual(recipientCount)
# Unsubscribe first recipient
unsubscribeRecipient user1, recipients.get(0), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
verifyNotSponsoring user1.id, recipients.get(0).id, ->
verifyNotRecipient recipients.get(0).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1))
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
# Create sponsor user
loginNewUser (user1) ->
# Subscribe recipients
recipients.subRecipients user1, token, ->
User.findById user1.id, (err, user1) ->
# Unsubscribe first recipient
unsubscribeRecipient user1, recipients.get(0), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 1)
verifyNotSponsoring user1.id, recipients.get(0).id, ->
verifyNotRecipient recipients.get(0).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getUnsubscribedQuantity(recipientCount - 1))
# Unsubscribe last recipient
unsubscribeRecipient user1, recipients.get(recipientCount - 1), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
verifyNotSponsoring user1.id, recipients.get(recipientCount - 1).id, ->
verifyNotRecipient recipients.get(recipientCount - 1).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
numSponsored = recipientCount - 2
if numSponsored <= 1
expect(subscription.quantity).toEqual(subPrice)
else if numSponsored <= 11
expect(subscription.quantity).toEqual(subPrice + (numSponsored - 1) * subPrice * 0.8)
else
expect(subscription.quantity).toEqual(subPrice + 10 * subPrice * 0.8 + (numSponsored - 11) * subPrice * 0.6)
done()
# Unsubscribe last recipient
unsubscribeRecipient user1, recipients.get(recipientCount - 1), true, ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.recipients.length).toEqual(recipientCount - 2)
verifyNotSponsoring user1.id, recipients.get(recipientCount - 1).id, ->
verifyNotRecipient recipients.get(recipientCount - 1).id, ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
numSponsored = recipientCount - 2
if numSponsored <= 1
expect(subscription.quantity).toEqual(subPrice)
else if numSponsored <= 11
expect(subscription.quantity).toEqual(subPrice + (numSponsored - 1) * subPrice * 0.8)
else
expect(subscription.quantity).toEqual(subPrice + 10 * subPrice * 0.8 + (numSponsored - 11) * subPrice * 0.6)
done()