Merge branch 'prepaid-v2' into master

This commit is contained in:
Matt Lott 2015-09-25 10:03:44 -07:00
parent ad0dfef50f
commit 928f72e2cf
23 changed files with 961 additions and 61 deletions

View file

@ -26,6 +26,7 @@ module.exports = class CocoRouter extends Backbone.Router
'account/subscription': go('account/SubscriptionView')
'account/subscription/sale': go('account/SubscriptionSaleView')
'account/invoices': go('account/InvoicesView')
'account/prepaid': go('account/PrepaidView')
'admin': go('admin/MainAdminView')
'admin/candidates': go('admin/CandidatesView')

View file

@ -243,3 +243,8 @@ module.exports.getCoursePraise = getCoursePraise = ->
}
]
praise[_.random(0, praise.length - 1)]
module.exports.getPrepaidCodeAmount = getPrepaidCodeAmount = (price=999, users=0, months=0) ->
return 0 unless users > 0 and months > 0
total = price * users * months
total

View file

@ -1129,6 +1129,7 @@
recently_played: "Recently Played"
no_recent_games: "No games played during the past two weeks."
payments: "Payments"
prepaid: "Prepaid"
purchased: "Purchased"
sale: "Sale"
subscription: "Subscription"
@ -1159,6 +1160,14 @@
retrying: "Server error, retrying."
success: "Successfully paid. Thanks!"
account_prepaid:
purchase_code: "Purchase a Subscription Code"
purchase_amount: "Amount"
purchase_total: "Total"
purchase_button: "Submit Purchase"
your_codes: "Your Codes:"
redeem_codes: "Redeem a Subscription Code"
loading_error:
could_not_load: "Error loading from server"
connection_failure: "Connection failed."

14
app/models/Prepaid.coffee Normal file
View file

@ -0,0 +1,14 @@
CocoModel = require './CocoModel'
schema = require 'schemas/models/prepaid.schema'
module.exports = class Prepaid extends CocoModel
@className: "Prepaid"
urlRoot: '/db/prepaid'
openSpots: ->
@get('maxRedeemers') - @get('redeemers')?.length
userHasRedeemed: (userID) ->
for redeemer in @get('redeemers')
return redeemer.date if redeemer.userID is userID
return null

View file

@ -0,0 +1,3 @@
#users, #months
max-width: 100px

View file

@ -4,14 +4,14 @@ block content
if me.get('anonymous')
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
else
ol.breadcrumb
li
a(href="/")
span.glyphicon.glyphicon-home
li.active(data-i18n="nav.account")
#account-links.panel.panel-default
.panel-heading(data-i18n="nav.account")
ul.list-group
@ -23,4 +23,6 @@ block content
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
li.list-group-item
a.btn.btn-lg.btn-primary(href="/account/subscription", data-i18n="account.subscription")
li.list-group-item
a.btn.btn-lg.btn-primary(href="/account/prepaid") Prepaid Codes

View file

@ -0,0 +1,19 @@
extends /templates/core/modal-base
block modal-header-content
h3 Prepaid Code Details (#{ppc.get('code')})
block modal-body-content
if redeemedOn
p You redeemed this code: #{redeemedOn}
else
if ppc.openSpots()
p: strong Adds #{ppc.get('properties').months} month(s) to your current subscription.
p You can redeem this code.
else
p You cannot redeem this code.
block modal-footer-content
button#close.btn.btn-primary(type="button", data-dismiss="modal") Cancel
if !redeemedOn && ppc.openSpots() > 0
button#redeem.btn.btn-primary(type="button", data-dismiss="modal") Redeem Code To My Account

View file

@ -0,0 +1,99 @@
extends /templates/base
block content
if me.get('anonymous')
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
else
ol.breadcrumb
li
a(href="/")
span.glyphicon.glyphicon-home
li
a(href="/account", data-i18n="nav.account")
li.active(data-i18n="account.prepaid")
.row
.col-md-12
.panel.panel-default
.panel-heading
.panel-title
a(data-toggle="collapse" href="#purchasepanel")
span(data-i18n="account_prepaid.purchase_code")
.panel-collapse.collapse(class=ppc ? "": "in")#purchasepanel
.panel-body
p Subscription Codes can be redeemed to add premium subscription time to one or more Code Combat accounts.
p Each Code Combat account can only redeem a particular Subscription Code once.
p Subscription Code months will be added to the end of any existing subscription on the account.
.form-horizontal
.form-group
label.control-label.col-md-2(for="users") Users
.col-md-2
input#users.form-control(name="users", type="number", value="#{purchase.users}", min=1)
.form-group
label.control-label.col-md-2(for="months") Months
.col-md-2
input#months.form-control(name="months", type="number", value="#{purchase.months}", min=1)
.form-group
label.control-label.col-md-2(data-i18n="account_prepaid.purchase_total")
.col-md-10
p.form-control-static $
span#total #{purchase.total}
button#purchase-button.btn.btn-success.pull-right(data-i18n="account_prepaid.purchase_button")
.row
.col-md-12
.panel.panel-default
.panel-heading
.panel-title
a(data-toggle="collapse" href="#redeempanel")
span(data-i18n="account_prepaid.redeem_codes")
.panel-collapse.collapse.in#redeempanel
.panel-body
.form-horizontal
.form-group
label.control-label.col-md-2(for="ppc") Code:
.col-md-10
input#ppc.form-control(name="ppc", type="text", value="#{ppc}" required)
button#redeem-button.btn.btn-success.pull-right View Code Details
.row
.col-md-12
.panel.panel-default
.panel-heading
.panel-title
a(data-toggle="collapse" href="#codeslist")
span(data-i18n="account_prepaid.your_codes")
.panel-collapse.collapse.in#codeslist
.panel-body
if codes && codes.length
table.table.table-striped
tr
th
span(title="You can copy the code's link and send it to someone.") Code
span.glyphicon.glyphicon-question-sign(aria-hidden="true")
th Months
th Quantity
th Status
for code in codes.models
if code.get('type') === 'terminal_subscription'
- var owner = (code.get('creator') == me.id ? true : false)
- var properties = code.get('properties')
- var redeemers = code.get('redeemers')
if redeemers
- var redeemed = redeemers.length
else
- var redeemed = '0'
tr
td
a(href="/account/prepaid?_ppc=#{code.get('code')}")= code.get('code')
td= properties.months || '-'
if owner
td= code.get('maxRedeemers') - redeemed
else
td -
if owner
td Purchased
else
td Redeemed
else
p No codes yet!

View file

@ -119,6 +119,15 @@ block content
else
button.start-subscription-button.btn.btn-lg.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
// - Prepaid Codes
.panel.panel-default
.panel-heading
h3 Prepaid Codes
.panel-body
p
span You can
a(href="/account/prepaid") purchase a prepaid code
span that can be applied to your own account or given to others.
//- Sponsored Subscriptions
.panel.panel-default

View file

@ -63,6 +63,16 @@ block content
if freeSubLink
input#free-sub-input(type="text", readonly, value="#{freeSubLink}")
.form-inline
.form-group
label(for="users") Users
input#users.form-control(name="users", type="number", min=1)
.form-group
label(for="months") Months
input#months.form-control(name="months", type="number", min=1)
a#terminal-create.btn.btn-default Create Terminal Subscription Code
hr
h3 Achievements

View file

@ -37,6 +37,8 @@ block header
a(href="/account/payments", data-i18n="account.payments")
li
a(href="/account/subscription", data-i18n="account.subscription")
li
a(href="/account/prepaid") Prepaid Codes
li
a#logout-button(data-i18n="login.log_out")
@ -44,7 +46,7 @@ block header
button.btn.btn-sm.btn-primary.header-font.signup-button(data-i18n="login.sign_up")
button.btn.btn-sm.btn-default.header-font.login-button(data-i18n="login.log_in")
select.language-dropdown.form-control
block outer_content
#site-content-area
@ -75,15 +77,15 @@ block footer
if !isIE
a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow
iframe.github-star-button(src="https://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")
#footer-credits
span
span
span © All Rights Reserved
br
span CodeCombat 2015
img#footer-logo(src="/images/pages/base/logo.png", alt="CodeCombat")
span
span Site Design by
span
span Site Design by
br
a(href="http://www.fullyillustrated.com/") Fully Illustrated
//a.firebase-bade(href="https://www.firebase.com/") // Not using right now

View file

@ -0,0 +1,27 @@
ModalView = require 'views/core/ModalView'
template = require 'templates/account/prepaid-redeem-modal'
{me} = require 'core/auth'
module.exports = class PrepaidRedeemModal extends ModalView
id: 'prepaid-redeem-modal'
template: template
closeButton: true
events:
'click #redeem' : 'onRedeemClicked'
constructor: (options) ->
super options
@ppc = options.ppc
hasRedeemed = @ppc.userHasRedeemed(me.get('_id'))
@redeemedOn = new moment(hasRedeemed).calendar() if hasRedeemed
getRenderData: ->
c = super()
c.ppc = @ppc
c.redeemedOn = @redeemedOn if @redeemedOn
c
onRedeemClicked: ->
@trigger 'confirm-redeem'

View file

@ -0,0 +1,179 @@
RootView = require 'views/core/RootView'
template = require 'templates/account/prepaid-view'
stripeHandler = require 'core/services/stripe'
{getPrepaidCodeAmount} = require '../../core/utils'
CocoCollection = require 'collections/CocoCollection'
Prepaid = require '../../models/Prepaid'
utils = require 'core/utils'
RedeemModal = require 'views/account/PrepaidRedeemModal'
forms = require 'core/forms'
module.exports = class PrepaidView extends RootView
id: 'prepaid-view'
template: template
className: 'container-fluid'
events:
'change #users': 'onUsersChanged'
'change #months': 'onMonthsChanged'
'click #purchase-button': 'onPurchaseClicked'
'click #redeem-button': 'onRedeemClicked'
subscriptions:
'stripe:received-token': 'onStripeReceivedToken'
baseAmount: 9.99
constructor: (options) ->
super(options)
@purchase =
total: @baseAmount
users: 3
months: 3
@updateTotal()
@codes = new CocoCollection([], { url: '/db/user/'+me.id+'/prepaid_codes', model: Prepaid })
@codes.on 'add', (code) =>
@render?()
@codes.on 'sync', (code) =>
@render?()
@supermodel.loadCollection(@codes, 'prepaid', {cache: false})
@ppc = utils.getQueryVariable('_ppc') ? ''
getRenderData: ->
c = super()
c.purchase = @purchase
c.codes = @codes
c.ppc = @ppc
c
afterRender: ->
super()
@$el.find("span[title]").tooltip()
statusMessage: (message, type='alert') ->
noty text: message, layout: 'topCenter', type: type, killer: false, timeout: 5000, dismissQueue: true, maxVisible: 3
updateTotal: ->
@purchase.total = getPrepaidCodeAmount(@baseAmount, @purchase.users, @purchase.months)
@renderSelectors("#total", "#users", "#months")
# Form Input Callbacks
onUsersChanged: (e) ->
newAmount = $(e.target).val()
newAmount = 1 if newAmount < 1
@purchase.users = newAmount
el = $('#purchasepanel')
if newAmount < 3 and @purchase.months < 3
message = "Either Users or Months must be greater than 2"
err = [message: message, property: 'users', formatted: true]
forms.clearFormAlerts(el)
forms.applyErrorsToForm(el, err)
else
forms.clearFormAlerts(el)
@updateTotal()
onMonthsChanged: (e) ->
newAmount = $(e.target).val()
newAmount = 1 if newAmount < 1
@purchase.months = newAmount
el = $('#purchasepanel')
if newAmount < 3 and @purchase.users < 3
message = "Either Users or Months must be greater than 2"
err = [message: message, property: 'months', formatted: true]
forms.clearFormAlerts(el)
forms.applyErrorsToForm(el, err)
else
forms.clearFormAlerts(el)
@updateTotal()
onPurchaseClicked: (e) ->
return unless $("#users").val() >= 3 or $("#months").val() >= 3
@purchaseTimestamp = new Date().getTime()
@stripeAmount = @purchase.total * 100
@description = "Prepaid Code for " + @purchase.users + " users / " + @purchase.months + " months"
stripeHandler.open
amount: @stripeAmount
description: @description
bitcoin: true
alipay: if me.get('chinaVersion') or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
onRedeemClicked: (e) ->
@ppc = $('#ppc').val()
unless @ppc
@statusMessage "You must enter a code.", "error"
return
options =
url: '/db/prepaid/-/code/'+ @ppc
method: 'GET'
options.success = (model, res, options) =>
redeemModal = new RedeemModal ppc: model
redeemModal.on 'confirm-redeem', @confirmRedeem
@openModalView redeemModal
options.error = (model, res, options) =>
console.warn 'Error getting Prepaid Code'
prepaid = new Prepaid()
prepaid.fetch(options)
# @supermodel.addRequestResource('get_prepaid', options, 0).load()
confirmRedeem: =>
options =
url: '/db/subscription/-/subscribe_prepaid'
method: 'POST'
data: { ppc: @ppc }
options.error = (model, res, options, foo) =>
console.error 'FAILED redeeming prepaid code'
msg = model.responseText ? ''
@statusMessage "Error: Could not redeem prepaid code. #{msg}", "error"
options.success = (model, res, options) =>
console.log 'SUCCESS redeeming prepaid code'
@statusMessage "Prepaid Code Redeemed!", "success"
@supermodel.loadCollection(@codes, 'prepaid', {cache: false})
@codes.fetch()
@supermodel.addRequestResource('subscribe_prepaid', options, 0).load()
onStripeReceivedToken: (e) ->
# TODO: show that something is happening in the UI
options =
url: '/db/prepaid/-/purchase'
method: 'POST'
options.data =
amount: @stripeAmount
description: @description
stripe:
token: e.token.id
timestamp: @purchaseTimestamp
type: 'terminal_subscription'
maxRedeemers: @purchase.users
months: @purchase.months
options.error = (model, response, options) =>
console.error 'FAILED: Prepaid purchase', response
console.error options
@statusMessage "Error purchasing prepaid code", "error"
# Not sure when this will happen. Stripe popup seems to give appropriate error messages.
options.success = (model, response, options) =>
console.log 'SUCCESS: Prepaid purchase', model.code
@statusMessage "Successfully purchased Prepaid Code!", "success"
@codes.add(model)
@statusMessage "Finalizing purchase...", "information"
@supermodel.addRequestResource('purchase_prepaid', options, 0).load()

View file

@ -15,6 +15,7 @@ module.exports = class MainAdminView extends RootView
'click #increment-button': 'incrementUserAttribute'
'click #user-search-result': 'onClickUserSearchResult'
'click #create-free-sub-btn': 'onClickFreeSubLink'
'click #terminal-create': 'onClickTerminalSubLink'
getRenderData: ->
context = super()
@ -89,3 +90,27 @@ module.exports = class MainAdminView extends RootView
options.error = (model, response, options) =>
console.error 'Failed to create prepaid', response
@supermodel.addRequestResource('create_prepaid', options, 0).load()
onClickTerminalSubLink: (e) =>
@freeSubLink = ''
return unless me.isAdmin()
options =
url: '/db/prepaid/-/create'
method: 'POST'
data:
type: 'terminal_subscription'
maxRedeemers: parseInt($("#users").val())
months: parseInt($("#months").val())
options.success = (model, response, options) =>
# TODO: Don't hardcode domain.
if application.isProduction()
@freeSubLink = "https://codecombat.com/account/prepaid?_ppc=#{model.code}"
else
@freeSubLink = "http://localhost:3000/account/prepaid?_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

@ -1,6 +1,7 @@
AnalyticsString = require '../analytics/AnalyticsString'
log = require 'winston'
mongoose = require 'mongoose'
config = require '../../server_config'
module.exports =
isID: (id) -> _.isString(id) and id.length is 24 and id.match(/[a-f0-9]/gi)?.length is 24
@ -21,6 +22,9 @@ module.exports =
# Grabs latest subscription (e.g. in case of a resubscribe)
return done() unless customerID?
return done() unless options.subscriptionID? or options.userID?
# Some prepaid tests were calling this in such a way that stripe wasn't defined.
stripe = require('stripe')(config.stripe.secretKey) unless stripe
subscriptionID = options.subscriptionID
userID = options.userID

View file

@ -14,6 +14,7 @@ User = require '../users/User'
{findStripeSubscription} = require '../lib/utils'
{getSponsoredSubsAmount} = require '../../app/core/utils'
StripeUtils = require '../lib/stripe_utils'
moment = require 'moment'
recipientCouponID = 'free'
@ -38,6 +39,7 @@ class SubscriptionHandler extends Handler
return @getStripeSubscriptions(req, res) if args[1] is 'stripe_subscriptions'
return @getSubscribers(req, res) if args[1] is 'subscribers'
return @purchaseYearSale(req, res) if args[1] is 'year_sale'
return @subscribeWithPrepaidCode(req, res) if args[1] is 'subscribe_prepaid'
super(arguments...)
getStripeEvents: (req, res) ->
@ -117,23 +119,24 @@ class SubscriptionHandler extends Handler
log.debug 'Analytics error:\n' + err
@sendSuccess(res, userMap)
cancelSubscriptionImmediately: (user, subscription, done) =>
return done() unless user and subscription
stripe.customers.cancelSubscription subscription.customer, subscription.id, (err) =>
return done(err) if err
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
delete stripeInfo.planID
delete stripeInfo.prepaidCode
delete stripeInfo.subscriptionID
user.set('stripe', stripeInfo)
user.save (err) =>
return done(err) if err
done()
purchaseYearSale: (req, res) ->
return @sendForbiddenError(res) unless req.user?
return @sendForbiddenError(res) if req.user?.get('stripe')?.sponsorID
cancelSubscriptionImmediately = (user, subscription, done) =>
return done() unless user and subscription
stripe.customers.cancelSubscription subscription.customer, subscription.id, (err) =>
return done(err) if err
stripeInfo = _.cloneDeep(user.get('stripe') ? {})
delete stripeInfo.planID
delete stripeInfo.prepaidCode
delete stripeInfo.subscriptionID
user.set('stripe', stripeInfo)
user.save (err) =>
return done(err) if err
done()
StripeUtils.getCustomer req.user, req.body.stripe?.token, (err, customer) =>
if err
@logSubscriptionError(req.user, "Purchase year sale get customer: #{JSON.stringify(err)}")
@ -142,7 +145,7 @@ class SubscriptionHandler extends Handler
findStripeSubscription customer.id, subscriptionID: req.user.get('stripe')?.subscriptionID, (subscription) =>
stripeSubscriptionPeriodEndDate = new Date(subscription.current_period_end * 1000) if subscription
cancelSubscriptionImmediately req.user, subscription, (err) =>
@cancelSubscriptionImmediately req.user, subscription, (err) =>
if err
@logSubscriptionError(user, "Purchase year sale Stripe cancel subscription error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
@ -192,6 +195,95 @@ class SubscriptionHandler extends Handler
@logSubscriptionError(req.user, "Year sub sale HipChat tower msg error: #{JSON.stringify(error)}")
@sendSuccess(res, user)
subscribeWithPrepaidCode: (req, res) ->
return @sendForbiddenError(res) unless req.user?
return @sendBadInputError(res,"You must provide a valid prepaid code") unless req.body?.ppc
# Check if code exists and has room for more redeemers
Prepaid.findOne({ code: req.body.ppc?.toString() }).exec (err, prepaid) =>
if err
@logSubscriptionError(req.user, "Redeem Prepaid Code find: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
return @sendForbiddenError(res) if prepaid is null
oldRedeemers = prepaid.get('redeemers') ? []
return @sendForbiddenError(res) if oldRedeemers.length >= prepaid.get('maxRedeemers')
months = parseInt(prepaid.get('properties')?.months)
return @sendForbiddenError(res) if isNaN(months) or months < 1
for redeemer in oldRedeemers
return @sendForbiddenError(res) if redeemer.userID.equals(req.user._id)
customerID = req.user.get('stripe')?.customerID
unless customerID
@redeemCode(req, res, oldRedeemers, months)
else
stripe.customers.retrieve customerID, (err, customer) =>
if err
@logSubscriptionError(req.user, "Redeem Prepaid Code get customer: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
findStripeSubscription customer.id, subscriptionID: req.user.get('stripe')?.subscriptionID, (subscription) =>
stripeSubscriptionPeriodEndDate = null
if subscription
stripeSubscriptionPeriodEndDate = new Date(subscription.current_period_end * 1000)
@cancelSubscriptionImmediately req.user, subscription, (err) =>
if err
@logSubscriptionError(user, "Redeem Prepaid Code Stripe cancel subscription error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
@redeemCode(req, res, oldRedeemers, months, stripeSubscriptionPeriodEndDate)
redeemCode: (req, res, oldRedeemers, months, startDate=null) =>
return @sendForbiddenError(res) unless req.user?
return @sendForbiddenError(res) unless req.body?.ppc
return @sendForbiddenError(res) unless oldRedeemers
return @sendForbiddenError(res) if isNaN(months) or months < 1
newRedeemerPush = { $push: { redeemers : { date: new Date().toISOString(), userID: req.user._id } }}
# Only update the prepaid document if the length of the redeemers array hasn't changed in the db.
# This will probably fail if redeemers isn't defined. new terminal_subscriptions created should be sure to set the redeemers array
# TODO: find a better way?
Prepaid.update { 'code': req.body.ppc, 'redeemers': { $size: oldRedeemers.length }}, newRedeemerPush, (err, num, info) =>
if err
@logSubscriptionError(req.user, "Subscribe with Prepaid Code update: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
return @sendNotFoundError(res, "Error while updating prepaid redeemer") if num isnt 1
# Add terminal subscription to User, extending existing subscriptions
# TODO: refactor this into some form useable by both this and purchaseYearSale?
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
endDate = new moment()
if startDate
endDate = new moment(startDate)
else if _.isString(stripeInfo.free) and new moment().isBefore(new moment(stripeInfo.free))
endDate = new moment(stripeInfo.free)
endDate = endDate.add(months, 'months')
stripeInfo.free = endDate.toISOString().substring(0, 10)
req.user.set('stripe', stripeInfo)
# Add gems to User
purchased = _.clone(req.user.get('purchased'))
purchased ?= {}
purchased.gems ?= 0
purchased.gems += subscriptions.basic.gems * months
req.user.set('purchased', purchased)
req.user.save (err, user) =>
if err
@logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
@sendSuccess(res, user)
subscribeUser: (req, user, done) ->
if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
return done({res: 'You must be signed in to subscribe.', code: 403})

View file

@ -2,6 +2,8 @@ mongoose = require 'mongoose'
config = require '../../server_config'
PrepaidSchema = new mongoose.Schema {}, {strict: false, minimize: false,read:config.mongo.readpref}
PrepaidSchema.index({code: 1}, { unique: true })
PrepaidSchema.statics.generateNewCode = (done) ->
tryCode = ->
code = _.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 8).join('')

View file

@ -1,5 +1,7 @@
Handler = require '../commons/Handler'
Prepaid = require './Prepaid'
StripeUtils = require '../lib/stripe_utils'
{getPrepaidCodeAmount} = require '../../app/core/utils'
# 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
@ -7,31 +9,113 @@ Prepaid = require './Prepaid'
PrepaidHandler = class PrepaidHandler extends Handler
modelClass: Prepaid
jsonSchema: require '../../app/schemas/models/prepaid.schema'
allowedMethods: ['POST']
allowedMethods: ['GET','POST']
baseAmount: 999
logPurchaseError: (user, msg) ->
console.warn "Prepaid Purchase Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
hasAccess: (req) ->
req.user?.isAdmin()
getByRelationship: (req, res, args...) ->
relationship = args[1]
return @getPrepaid(req, res, args[2]) if relationship is 'code'
return @createPrepaid(req, res) if relationship is 'create'
return @purchasePrepaid(req, res) if relationship is 'purchase'
super arguments...
getPrepaid: (req, res, code) ->
return @sendForbiddenError(res) unless req.user?
return @sendNotFoundError(res, "You must specify a code") unless code
Prepaid.findOne({ code: code.toString() }).exec (err, prepaid) =>
if err
console.warn "Get Prepaid Code Error [#{req.user.get('slug')} (#{req.user.id})]: #{JSON.stringify(err)}"
return @sendDatabaseError(res, err)
return @sendNotFoundError(res, "Code not found") unless prepaid
@sendSuccess(res, prepaid.toObject())
createPrepaid: (req, res) ->
return @sendForbiddenError(res) unless @hasAccess(req)
return @sendForbiddenError(res) unless req.body.type is 'subscription'
return @sendForbiddenError(res) unless req.body.type in ['subscription','terminal_subscription']
return @sendForbiddenError(res) unless req.body.maxRedeemers > 0
Prepaid.generateNewCode (code) =>
return @sendDatabaseError(res, 'Database error.') unless code
prepaid = new Prepaid
# TODO: change the creator to use ObjectID like with the terminal_subscription
options =
creator: req.user.id
type: req.body.type
code: code
maxRedeemers: req.body.maxRedeemers
properties:
couponID: 'free'
properties: {}
redeemers: []
if req.body.type is 'subscription'
options.properties.couponID = 'free'
if req.body.type is 'terminal_subscription'
options.properties.months = req.body.months
options.creator = req.user._id
prepaid = new Prepaid options
prepaid.save (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, prepaid.toObject())
purchasePrepaid: (req, res) ->
return @sendForbiddenError(res) unless req.user?
return @sendForbiddenError(res) unless req.body.type is 'terminal_subscription'
maxRedeemers = parseInt(req.body.maxRedeemers)
months = parseInt(req.body.months)
return @sendForbiddenError(res) unless isNaN(maxRedeemers) is false and maxRedeemers > 0
return @sendForbiddenError(res) unless isNaN(months) is false and months > 0
return @sendError(res, 403, "Users or Months must be greater than 3") if maxRedeemers < 3 and months < 3
StripeUtils.getCustomer req.user, req.body.stripe?.token, (err, customer) =>
if err
@logPurchaseError(req.user, "getCustomer error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
metadata =
type: req.body.type
userID: req.user._id + ''
timestamp: parseInt(req.body.stripe?.timestamp)
description: req.body.description
maxRedeemers: maxRedeemers
months: months
productID: 'prepaid ' + req.body.type
amount = getPrepaidCodeAmount(@baseAmount, maxRedeemers, months)
StripeUtils.createCharge req.user, amount, metadata, (err, charge) =>
if err
@logPurchaseError(req.user, "createCharge error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
StripeUtils.createPayment req.user, charge, (err, payment) =>
if err
@logPurchaseError(req.user, "createPayment error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
Prepaid.generateNewCode (code) =>
return @sendDatabaseError(res, 'Database error.') unless code
prepaid = new Prepaid
creator: req.user._id
type: req.body.type
code: code
maxRedeemers: req.body.maxRedeemers
redeemers: []
properties:
months: req.body.months
prepaid.save (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, prepaid.toObject())
module.exports = new PrepaidHandler()

View file

@ -46,14 +46,18 @@ module.exports.setup = (app) ->
invoiceID = req.body.data.object.id
stripe.invoices.retrieve invoiceID, (err, invoice) =>
return res.send(500, '') if err
if err
logStripeWebhookError("Retrieve invoice error: #{JSON.stringify(err)}")
return res.send(500, '')
unless invoice.total or invoice.discount?.coupon?.id is 'free'
# invoices made when trialing, probably given for people who resubscribe after unsubscribing
return res.send(200, '')
return res.send(200, '') unless invoice.lines?.data?.length > 0
getUserID invoice.customer, (err, userID) =>
return res.send(500, '') if err
if err
logStripeWebhookError("Get user ID error: #{JSON.stringify(err)}")
return res.send(500, '')
# User is recipient if no metadata.id
recipientID = invoice.lines.data[0].metadata?.id or userID
@ -62,7 +66,9 @@ module.exports.setup = (app) ->
subscriptionID = invoice.lines.data[0].subscription or invoice.lines.data[0].id
User.findById recipientID, (err, recipient) =>
return res.send(500, '') if err
if err
logStripeWebhookError("Find recipient user error: #{JSON.stringify(err)}")
return res.send(500, '')
return res.send(200) unless recipient # just for the sake of testing...
Payment.findOne {'stripe.invoiceID': invoiceID}, (err, payment) =>
@ -82,7 +88,9 @@ module.exports.setup = (app) ->
payment.set 'gems', 3500 if invoice.lines.data[0].plan?.id is 'basic'
payment.save (err) =>
return res.send(500, '') if err
if err
logStripeWebhookError("Save payment error: #{JSON.stringify(err)}")
return res.send(500, '')
return res.send(201, '') if invoice.lines.data[0].plan?.id isnt 'basic'
# Update purchased gems
@ -94,7 +102,9 @@ module.exports.setup = (app) ->
purchased.gems = gems
recipient.set('purchased', purchased)
recipient.save (err) ->
return res.send(500, '') if err
if err
logStripeWebhookError("Save recipient user error: #{JSON.stringify(err)}")
return res.send(500, '')
return res.send(201, '')
handleSubscriptionDeleted = (req, res) ->

View file

@ -23,6 +23,7 @@ UserRemark = require './remarks/UserRemark'
{isID} = require '../lib/utils'
hipchat = require '../hipchat'
sendwithus = require '../sendwithus'
Prepaid = require '../prepaids/Prepaid'
serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset', 'lastIP']
candidateProperties = [
@ -305,6 +306,7 @@ UserHandler = class UserHandler extends Handler
return @avatar(req, res, args[0]) if args[1] is 'avatar'
return @getByIDs(req, res) if args[1] is 'users'
return @getNamesByIDs(req, res) if args[1] is 'names'
return @getPrepaidCodes(req, res) if args[1] is 'prepaid_codes'
return @nameToID(req, res, args[0]) if args[1] is 'nameToID'
return @getLevelSessionsForEmployer(req, res, args[0]) if args[1] is 'level.sessions' and args[2] is 'employer'
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
@ -453,6 +455,11 @@ UserHandler = class UserHandler extends Handler
sendMail emailParams
getPrepaidCodes: (req, res) ->
orQuery = [{ creator: req.user._id }, { 'redeemers.userID' : req.user._id }]
Prepaid.find({}).or(orQuery).exec (err, documents) =>
@sendSuccess(res, documents)
agreeToCLA: (req, res) ->
return @sendForbiddenError(res) unless req.user
doc =

View file

@ -120,11 +120,33 @@ wrapUpGetUser = (email, user, done) ->
GLOBAL.getURL = (path) ->
return 'http://localhost:3001' + path
GLOBAL.createPrepaid = (type, maxRedeemers, done) ->
GLOBAL.createPrepaid = (type, maxRedeemers, months, done) ->
options = uri: GLOBAL.getURL('/db/prepaid/-/create')
options.json =
type: type
maxRedeemers: maxRedeemers
if months
options.json.months = months
request.post options, done
GLOBAL.fetchPrepaid = (ppc, done) ->
options = uri: GLOBAL.getURL('/db/prepaid/-/code/'+ppc)
request.get options, done
GLOBAL.purchasePrepaid = (type, maxRedeemers, months, done) ->
options = uri: GLOBAL.getURL('/db/prepaid/-/purchase')
options.json =
type: type
maxRedeemers: maxRedeemers
months: months
stripe:
timestamp: new Date().getTime()
request.post options, done
GLOBAL.subscribeWithPrepaid = (ppc, done) =>
options = url: GLOBAL.getURL('/db/subscription/-/subscribe_prepaid')
options.json =
ppc: ppc
request.post options, done
newUserCount = 0

View file

@ -1,9 +1,18 @@
require '../common'
config = require '../../../server_config'
moment = require 'moment'
{findStripeSubscription} = require '../../../server/lib/utils'
describe '/db/prepaid', ->
prepaidURL = getURL('/db/prepaid')
prepaidCreateURL = getURL('/db/prepaid/-/create')
headers = {'X-Change-Plan': 'true'}
joeData = null
stripe = require('stripe')(config.stripe.secretKey)
joeCode = null
verifyPrepaid = (user, prepaid, done) ->
expect(prepaid.creator).toEqual(user.id)
expect(prepaid.type).toEqual('subscription')
@ -13,12 +22,12 @@ describe '/db/prepaid', ->
done()
it 'Clear database users and prepaids', (done) ->
clearModels [User, Prepaid], (err) ->
clearModels [User, Prepaid, Payment], (err) ->
throw err if err
done()
it 'Anonymous creates prepaid code', (done) ->
createPrepaid 'subscription', 1, (err, res, body) ->
createPrepaid 'subscription', 1, 0, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(401)
done()
@ -26,7 +35,7 @@ describe '/db/prepaid', ->
it 'Non-admin creates prepaid code', (done) ->
loginNewUser (user1) ->
expect(user1.isAdmin()).toEqual(false)
createPrepaid 'subscription', 4, (err, res, body) ->
createPrepaid 'subscription', 4, 0, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
@ -37,18 +46,35 @@ describe '/db/prepaid', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, body) ->
createPrepaid 'subscription', 1, 0, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
verifyPrepaid user1, body, done
it 'Admin creates prepaid code with type terminal_subscription', (done) ->
loginNewUser (user1) ->
user1.set('permissions', ['admin'])
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'terminal_subscription', 2, 3, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
expect(body.creator).toEqual(user1.id)
expect(body.type).toEqual('terminal_subscription')
expect(body.maxRedeemers).toEqual(2)
expect(body.properties?.months).toEqual(3)
expect(body.code).toMatch(/^\w{8}$/)
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', 1, (err, res, body) ->
createPrepaid 'bulldozer', 1, 0, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
@ -59,7 +85,7 @@ describe '/db/prepaid', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid null, 1, (err, res, body) ->
createPrepaid null, 1, 0, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
@ -70,7 +96,7 @@ describe '/db/prepaid', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 0, (err, res, body) ->
createPrepaid 'subscription', 0, 0, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
@ -89,7 +115,7 @@ describe '/db/prepaid', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
request.get {uri: prepaidURL}, (err, res, body) ->
@ -104,3 +130,231 @@ describe '/db/prepaid', ->
break
expect(found).toEqual(true)
done() unless found
# *** Purchase Prepaid Codes *** #
it 'Anonymous submits a prepaid purchase', (done) ->
logoutUser () ->
purchasePrepaid 'terminal_subscription', 3, 3, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(401)
done()
it 'Should error if type isnt terminal_subscription', (done) ->
loginNewUser (user1) ->
purchasePrepaid 'subscription', 3, 3, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Should error if maxRedeemers is invalid', (done) ->
loginNewUser (user1) ->
purchasePrepaid 'terminal_subscription', -1, 3, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
purchasePrepaid 'terminal_subscription', 'foo', 3, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Should error if months is invalid', (done) ->
loginNewUser (user1) ->
purchasePrepaid 'terminal_subscription', 3, -1, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
purchasePrepaid 'terminal_subscription', 3, 'foo', (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'Should error if maxRedeemers and months are less than 3', (done) ->
loginNewUser (user1) ->
purchasePrepaid 'terminal_subscription', 1, 1, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
done()
it 'User submits valid prepaid code purchase', (done) ->
stripe.tokens.create {
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
}, (err, token) ->
stripeTokenID = token.id
loginJoe (joe) ->
joeData = joe.toObject()
joeData.stripe = {
token: stripeTokenID
planID: 'basic'
}
request.put {uri: getURL('/db/user'), json: joeData, headers: headers }, (err, res, body) ->
joeData = body
expect(res.statusCode).toBe(200)
expect(joeData.stripe.customerID).toBeDefined()
expect(firstSubscriptionID = joeData.stripe.subscriptionID).toBeDefined()
expect(joeData.stripe.planID).toBe('basic')
expect(joeData.stripe.token).toBeUndefined()
purchasePrepaid 'terminal_subscription', 3, 3, (err, res, prepaid) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
expect(prepaid.type).toEqual('terminal_subscription')
expect(prepaid.code).toBeDefined()
# Saving this code for later tests
joeCode = prepaid.code
expect(prepaid.creator).toBeDefined()
expect(prepaid.maxRedeemers).toEqual(3)
expect(prepaid.properties).toBeDefined()
expect(prepaid.properties.months).toEqual(3)
done()
it 'Should have logged a Payment with the correct amount', (done) ->
loginJoe (joe) ->
query =
purchaser: joe._id
Payment.find query, (err, payments) ->
expect(err).toBeNull()
expect(payments).not.toBeNull()
expect(payments.length).toEqual(1)
expect(payments[0].get('amount')).toEqual(8991)
done()
it 'Anonymous cant redeem a prepaid code', (done) ->
logoutUser () ->
subscribeWithPrepaid joeCode, (err, res) ->
expect(err).toBeNull()
expect(res?.statusCode).toEqual(401)
done()
it 'User cant redeem a nonexistant prepaid code', (done) ->
loginJoe (joe) ->
subscribeWithPrepaid 'abc123', (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(403)
done()
it 'User cant redeem empty code', (done) ->
loginJoe (joe) ->
subscribeWithPrepaid '', (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(422)
done()
it 'Anonymous cant fetch a prepaid code', (done) ->
expect(joeCode).not.toBeNull()
logoutUser () ->
fetchPrepaid joeCode, (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(403)
done()
it 'User can fetch a prepaid code', (done) ->
expect(joeCode).not.toBeNull()
loginJoe (joe) ->
fetchPrepaid joeCode, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(200)
expect(body).toBeDefined()
return done() unless body
prepaid = JSON.parse(body)
expect(prepaid.code).toEqual(joeCode)
expect(prepaid.maxRedeemers).toEqual(3)
expect(prepaid.properties?.months).toEqual(3)
done()
it 'Creator can redeeem a prepaid code', (done) ->
loginJoe (joe) ->
expect(joeCode).not.toBeNull()
expect(joeData.stripe?.customerID).toBeDefined()
expect(joeData.stripe?.subscriptionID).toBeDefined()
return done() unless joeData.stripe?.customerID
# joe has a stripe subscription, so test if the months are added to the end of it.
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) =>
expect(err).toBeNull()
findStripeSubscription customer.id, subscriptionID: joeData.stripe?.subscriptionID, (subscription) =>
if subscription
stripeSubscriptionPeriodEndDate = new moment(subscription.current_period_end * 1000)
else
expect(stripeSubscriptionPeriodEndDate).toBeDefined()
return done()
subscribeWithPrepaid joeCode, (err, res, result) =>
expect(err).toBeNull()
expect(res.statusCode).toEqual(200)
endDate = stripeSubscriptionPeriodEndDate.add(3, 'months').toISOString().substring(0, 10)
expect(result?.stripe?.free).toEqual(endDate)
expect(result?.purchased?.gems).toEqual(14000)
findStripeSubscription customer.id, subscriptionID: joeData.stripe?.subscriptionID, (subscription) =>
expect(subscription).toBeNull()
done()
it 'User can redeem a prepaid code', (done) ->
loginSam (sam) ->
subscribeWithPrepaid joeCode, (err, res, result) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(200)
endDate = new moment().add(3, 'months').toISOString().substring(0, 10)
expect(result?.stripe?.free).toEqual(endDate)
expect(result?.purchased?.gems).toEqual(10500)
done()
it 'Wont allow the same person to redeem twice', (done) ->
loginSam (sam) ->
subscribeWithPrepaid joeCode, (err, res, result) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(403)
done()
it 'Will return redeemed code as part of codes list', (done) ->
loginSam (sam) ->
request.get "#{getURL('/db/user')}/#{sam.id}/prepaid_codes", (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(200)
codes = JSON.parse res.body
expect(codes.length).toEqual(1)
done()
it 'Third user can redeem a prepaid code', (done) ->
loginNewUser (user) ->
subscribeWithPrepaid joeCode, (err, res, result) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(200)
endDate = new moment().add(3, 'months').toISOString().substring(0, 10)
expect(result?.stripe?.free).toEqual(endDate)
expect(result?.purchased?.gems).toEqual(10500)
done()
it 'Fourth user cannot redeem code', (done) ->
loginNewUser (user) ->
subscribeWithPrepaid joeCode, (err, res, result) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(403)
done()
it 'Can fetch a list of purchased and redeemed prepaid codes', (done) ->
loginJoe (joe) ->
purchasePrepaid 'terminal_subscription', 3, 1, (err, res, prepaid) ->
request.get "#{getURL('/db/user')}/#{joe.id}/prepaid_codes", (err, res) ->
expect(err).toBeNull()
expect(res.statusCode).toEqual(200);
codes = JSON.parse res.body
expect(codes.length).toEqual(2)
expect(codes[0].maxRedeemers).toEqual(3)
expect(codes[0].properties).toBeDefined()
expect(codes[0].properties.months).toEqual(3)
done()
it 'Test for injection', (done) ->
loginNewUser (user) ->
code = { $exists: true }
subscribeWithPrepaid code, (err, res, result) ->
expect(err).toBeNull()
expect(res.statusCode).not.toEqual(200)
done()
# TODO: add a bunch of parallel tests trying to redeem a code with a high maxRedeemers (50?) to see what happens

View file

@ -297,6 +297,8 @@ describe 'Subscriptions', ->
return done() unless sponsorCustomerID and sponsorStripe.sponsorSubscriptionID
stripe.customers.retrieveSubscription sponsorCustomerID, sponsorStripe.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription?).toBe(true)
return done() unless subscription?
expect(subscription.plan.amount).toEqual(1)
expect(subscription.customer).toEqual(sponsorCustomerID)
expect(subscription.quantity).toEqual(utils.getSponsoredSubsAmount(subPrice, numSponsored, sponsorStripe.subscriptionID?))
@ -361,6 +363,7 @@ describe 'Subscriptions', ->
Payment.findOne paymentQuery, (err, payment) ->
expect(err).toBeNull()
expect(payment).not.toBeNull()
return done() if payment is null
expect(payment.get('amount')).toEqual(0)
expect(payment.get('gems')).toBeGreaterThan(subGems - 1)
done()
@ -406,6 +409,8 @@ describe 'Subscriptions', ->
expect(user.get('stripe').customerID).toBeDefined()
expect(user.get('stripe').planID).toBeUndefined()
expect(user.get('stripe').token).toBeUndefined()
expect(user.get('stripe').subscriptionID).toBeDefined()
return done() unless user.get('stripe').subscriptionID
stripe.customers.retrieveSubscription user.get('stripe').customerID, user.get('stripe').subscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
@ -422,11 +427,11 @@ describe 'Subscriptions', ->
expect(err).toBeNull()
return done() if err
expect(res.statusCode).toBe(200)
expect(body.stripe.customerID).toBeDefined()
expect(body.stripe?.customerID).toBeDefined()
updatedUser = body
# Call webhooks for invoices
options = customer: body.stripe.customerID, limit: 100
options = customer: body.stripe?.customerID, limit: 100
stripe.invoices.list options, (err, invoices) ->
expect(err).toBeNull()
expect(invoices).not.toBeNull()
@ -569,7 +574,7 @@ describe 'Subscriptions', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
@ -584,6 +589,8 @@ describe 'Subscriptions', ->
expect(err).toBeNull()
stripeInfo = user1.get('stripe')
expect(stripeInfo.prepaidCode).toEqual(prepaid.get('code'))
expect(stripeInfo.subscriptionID).toBeDefined()
return done() unless stripeInfo.subscriptionID
# Delete subscription
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.subscriptionID, (err, subscription) ->
@ -612,7 +619,7 @@ describe 'Subscriptions', ->
subscribeUser user1, token, null, ->
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
@ -627,6 +634,7 @@ describe 'Subscriptions', ->
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
return done() unless subscription
expect(subscription.discount?.coupon?.id).toEqual('free')
done()
@ -646,7 +654,7 @@ describe 'Subscriptions', ->
request.put {uri: userURL, json: requestBody, headers: headers }, (err, res, updatedUser) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
expect(err).toBeNull()
@ -660,7 +668,7 @@ describe 'Subscriptions', ->
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.discount?.coupon?.id).toEqual('free')
expect(subscription?.discount?.coupon?.id).toEqual('free')
done()
it 'Subscribe with prepaid, then cancel', (done) ->
@ -669,7 +677,7 @@ describe 'Subscriptions', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
@ -691,7 +699,7 @@ describe 'Subscriptions', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
loginNewUser (user2) ->
@ -714,7 +722,7 @@ describe 'Subscriptions', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 2, (err, res, prepaid) ->
createPrepaid 'subscription', 2, 0, (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
loginNewUser (user2) ->
@ -730,7 +738,7 @@ describe 'Subscriptions', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
Prepaid.findById prepaid._id, (err, prepaid) ->
@ -746,7 +754,7 @@ describe 'Subscriptions', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 2, (err, res, prepaid) ->
createPrepaid 'subscription', 2, 0, (err, res, prepaid) ->
expect(err).toBeNull()
Prepaid.findById prepaid._id, (err, prepaid) ->
expect(err).toBeNull()
@ -795,8 +803,13 @@ describe 'Subscriptions', ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeRecipients user1, [user2], token, (updatedUser) ->
customerID = updatedUser.stripe.customerID
subscriptionID = updatedUser.stripe.recipients[0].subscriptionID
expect(updatedUser).not.toBeNull()
return done() unless updatedUser
customerID = updatedUser.stripe?.customerID
expect(customerID).toBeDefined()
subscriptionID = updatedUser.stripe?.recipients[0]?.subscriptionID
expect(subscriptionID).toBeDefined()
return done() unless customerID and subscriptionID
loginUser user2, (user2) ->
request.del {uri: "#{userURL}/#{user2.id}"}, (err, res) ->
expect(err).toBeNull()
@ -987,7 +1000,7 @@ describe 'Subscriptions', ->
user2.save (err, user1) ->
expect(err).toBeNull()
expect(user2.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
requestBody = user2.toObject()
requestBody.stripe =
@ -1022,8 +1035,11 @@ describe 'Subscriptions', ->
createNewUser (user2) ->
loginNewUser (user1) ->
subscribeRecipients user1, [user2, user3], token, (updatedUser) ->
customerID = updatedUser.stripe.customerID
subscriptionID = updatedUser.stripe.sponsorSubscriptionID
customerID = updatedUser?.stripe?.customerID
subscriptionID = updatedUser?.stripe?.sponsorSubscriptionID
expect(customerID).toBeDefined()
expect(subscriptionID).toBeDefined()
return done() unless customerID and subscriptionID
# Find Stripe sponsor subscription
stripe.customers.retrieveSubscription customerID, subscriptionID, (err, subscription) ->
@ -1041,6 +1057,7 @@ describe 'Subscriptions', ->
expect(err).toBeNull()
# Should have 2 cancelled recipient subs with cancel_at_period_end = true
# TODO: is this correct, or do we terminate recipient subs immediately now?
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
stripeInfo = user1.get('stripe')
@ -1159,7 +1176,7 @@ describe 'Subscriptions', ->
user1.save (err, user1) ->
expect(err).toBeNull()
expect(user1.isAdmin()).toEqual(true)
createPrepaid 'subscription', 1, (err, res, prepaid) ->
createPrepaid 'subscription', 1, 0, (err, res, prepaid) ->
expect(err).toBeNull()
subscribeUser user1, null, prepaid.code, ->
stripe.tokens.create {
@ -1233,7 +1250,7 @@ describe 'Subscriptions', ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(1))
expect(subscription?.quantity).toEqual(getSubscribedQuantity(1))
# Unsubscribe recipient1
unsubscribeRecipient user1, recipients.get(1), ->
@ -1245,6 +1262,7 @@ describe 'Subscriptions', ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
return done() unless subscription
expect(subscription.quantity).toEqual(0)
done()
@ -1274,13 +1292,16 @@ describe 'Subscriptions', ->
unsubscribeRecipient user1, recipients.get(0), ->
User.findById user1.id, (err, user1) ->
stripeInfo = user1.get('stripe')
expect(stripeInfo.customerID).toBeDefined()
expect(stripeInfo.sponsorSubscriptionID).toBeDefined()
return done() unless stripeInfo.customerID and stripeInfo.sponsorSubscriptionID
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))
expect(subscription?.quantity).toEqual(getSubscribedQuantity(recipientCount - 1))
# Unsubscribe second recipient
unsubscribeRecipient user1, recipients.get(1), ->
@ -1292,7 +1313,7 @@ describe 'Subscriptions', ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
expect(subscription?.quantity).toEqual(getSubscribedQuantity(recipientCount - 2))
# Unsubscribe self
User.findById user1.id, (err, user1) ->
@ -1312,7 +1333,7 @@ describe 'Subscriptions', ->
stripe.customers.retrieveSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, (err, subscription) ->
expect(err).toBeNull()
expect(subscription).not.toBeNull()
expect(subscription.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
expect(subscription?.quantity).toEqual(getSubscribedQuantity(recipientCount - 3))
done()
xit 'Unsubscribed user1 subscribes 13 users, unsubcribes 2', (done) ->