This commit is contained in:
Nick Winter 2014-12-05 17:27:33 -08:00
commit 07f7e7473d
37 changed files with 52 additions and 29 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 441 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 KiB

View file

@ -112,6 +112,13 @@ module.exports = class User extends CocoModel
application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin() application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin()
@gemPromptGroup @gemPromptGroup
isPremium: ->
return false unless stripe = @get('stripe')
return true if stripe.subscriptionID
return true if stripe.free is true
return true if _.isString(stripe.free) and new Date() < new Date(stripe.free)
return false
tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96, tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15
] ]

View file

@ -24,7 +24,7 @@ module.exports = class PaymentsView extends RootView
c = super() c = super()
c.payments = @payments c.payments = @payments
c.subscribed = me.get('stripe')?.planID c.subscribed = me.get('stripe')?.planID
c.active = me.get('stripe')?.subscriptionID c.active = me.isPremium()
c c
onClickStartSubscription: (e) -> onClickStartSubscription: (e) ->

View file

@ -81,7 +81,7 @@ module.exports = class WorldMapView extends RootView
$('body').append($('<img src="http://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">')) $('body').append($('<img src="http://code.org/api/hour/begin_codecombat.png" style="visibility: hidden;">'))
trackedHourOfCode = true trackedHourOfCode = true
@requiresSubscription = @terrain isnt 'dungeon' and not me.get('stripe')?.subscriptionID @requiresSubscription = @terrain isnt 'dungeon' and not me.isPremium()
destroy: -> destroy: ->
@setupManager?.destroy() @setupManager?.destroy()

View file

@ -72,7 +72,7 @@ LevelHandler = class LevelHandler extends Handler
Session.findOne(sessionQuery).exec (err, doc) => Session.findOne(sessionQuery).exec (err, doc) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
return @sendSuccess(res, doc) if doc? return @sendSuccess(res, doc) if doc?
return @sendPaymentRequiredError(res, err) if (not req.user.get('stripe')?.subscriptionID) and level.get('requiresSubscription') return @sendPaymentRequiredError(res, err) if (not req.user.isPremium()) and level.get('requiresSubscription')
@createAndSaveNewSession sessionQuery, req, res @createAndSaveNewSession sessionQuery, req, res
createAndSaveNewSession: (sessionQuery, req, res) => createAndSaveNewSession: (sessionQuery, req, res) =>

View file

@ -8,7 +8,6 @@ sendwithus = require '../sendwithus'
hipchat = require '../hipchat' hipchat = require '../hipchat'
config = require '../../server_config' config = require '../../server_config'
request = require 'request' request = require 'request'
stripe = require('stripe')(config.stripe.secretKey)
async = require 'async' async = require 'async'
products = { products = {

View file

@ -2,8 +2,6 @@
# the stripe property in the user with what's being stored in Stripe. # the stripe property in the user with what's being stored in Stripe.
Handler = require '../commons/Handler' Handler = require '../commons/Handler'
config = require '../../server_config'
stripe = require('stripe')(config.stripe.secretKey)
discountHandler = require './discount_handler' discountHandler = require './discount_handler'
subscriptions = { subscriptions = {
@ -13,17 +11,17 @@ subscriptions = {
} }
class SubscriptionHandler extends Handler class SubscriptionHandler extends Handler
logSubscriptionError: (req, msg) -> logSubscriptionError: (user, msg) ->
console.warn "Subscription Error: #{req.user.get('slug')} (#{req.user._id}): '#{msg}'" console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'"
subscribeUser: (req, user, done) -> subscribeUser: (req, user, done) ->
if (not req.user) or req.user.isAnonymous() if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
return done({res: 'You must be signed in to subscribe.', code: 403}) return done({res: 'You must be signed in to subscribe.', code: 403})
token = req.body.stripe.token token = req.body.stripe.token
customerID = user.get('stripe')?.customerID customerID = user.get('stripe')?.customerID
if not (token or customerID) if not (token or customerID)
@logSubscriptionError(req, 'Missing stripe token or customer ID.') @logSubscriptionError(user, 'Missing stripe token or customer ID.')
return done({res: 'Missing stripe token or customer ID.', code: 422}) return done({res: 'Missing stripe token or customer ID.', code: 422})
if token if token
@ -31,15 +29,15 @@ class SubscriptionHandler extends Handler
stripe.customers.update customerID, { card: token }, (err, customer) => stripe.customers.update customerID, { card: token }, (err, customer) =>
if err or not customer if err or not customer
# should not happen outside of test and production polluting each other # should not happen outside of test and production polluting each other
@logSubscriptionError(req, 'Cannot find customer: ', +customer.id + '\n\n' + err) @logSubscriptionError(user, 'Cannot find customer: ', +customer.id + '\n\n' + err)
return done({res: 'Cannot find customer.', code: 404}) return done({res: 'Cannot find customer.', code: 404})
@checkForExistingSubscription(req, user, customer, done) @checkForExistingSubscription(req, user, customer, done)
else else
newCustomer = { newCustomer = {
card: token card: token
email: req.user.get('email') email: user.get('email')
metadata: { id: req.user._id + '', slug: req.user.get('slug') } metadata: { id: user._id + '', slug: user.get('slug') }
} }
stripe.customers.create newCustomer, (err, customer) => stripe.customers.create newCustomer, (err, customer) =>
@ -47,22 +45,22 @@ class SubscriptionHandler extends Handler
if err.type in ['StripeCardError', 'StripeInvalidRequestError'] if err.type in ['StripeCardError', 'StripeInvalidRequestError']
return done({res: 'Card error', code: 402}) return done({res: 'Card error', code: 402})
else else
@logSubscriptionError(req, 'Stripe customer creation error. '+err) @logSubscriptionError(user, 'Stripe customer creation error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {}) stripeInfo = _.cloneDeep(user.get('stripe') ? {})
stripeInfo.customerID = customer.id stripeInfo.customerID = customer.id
req.user.set('stripe', stripeInfo) user.set('stripe', stripeInfo)
req.user.save (err) => user.save (err) =>
if err if err
@logSubscriptionError(req, 'Stripe customer id save db error. '+err) @logSubscriptionError(user, 'Stripe customer id save db error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
@checkForExistingSubscription(req, user, customer, done) @checkForExistingSubscription(req, user, customer, done)
else else
stripe.customers.retrieve(customerID, (err, customer) => stripe.customers.retrieve(customerID, (err, customer) =>
if err if err
@logSubscriptionError(req, 'Stripe customer creation error. '+err) @logSubscriptionError(user, 'Stripe customer creation error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
@checkForExistingSubscription(req, user, customer, done) @checkForExistingSubscription(req, user, customer, done)
) )
@ -79,14 +77,14 @@ class SubscriptionHandler extends Handler
# subscription a trial period that ends when the cancelled subscription would have ended. # subscription a trial period that ends when the cancelled subscription would have ended.
stripe.customers.cancelSubscription subscription.customer, subscription.id, (err) => stripe.customers.cancelSubscription subscription.customer, subscription.id, (err) =>
if err if err
@logSubscriptionError(req, 'Stripe cancel subscription error. '+err) @logSubscriptionError(user, 'Stripe cancel subscription error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
options = { plan: 'basic', trial_end: subscription.current_period_end } options = { plan: 'basic', trial_end: subscription.current_period_end }
options.coupon = couponID if couponID options.coupon = couponID if couponID
stripe.customers.update req.user.get('stripe').customerID, options, (err, customer) => stripe.customers.update user.get('stripe').customerID, options, (err, customer) =>
if err if err
@logSubscriptionError(req, 'Stripe customer plan setting error. '+err) @logSubscriptionError(user, 'Stripe customer plan setting error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
@updateUser(req, user, customer, false, done) @updateUser(req, user, customer, false, done)
@ -98,9 +96,9 @@ class SubscriptionHandler extends Handler
else else
options = { plan: 'basic' } options = { plan: 'basic' }
options.coupon = couponID if couponID options.coupon = couponID if couponID
stripe.customers.update req.user.get('stripe').customerID, options, (err, customer) => stripe.customers.update user.get('stripe').customerID, options, (err, customer) =>
if err if err
@logSubscriptionError(req, 'Stripe customer plan setting error. '+err) @logSubscriptionError(user, 'Stripe customer plan setting error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
@updateUser(req, user, customer, true, done) @updateUser(req, user, customer, true, done)
@ -123,25 +121,25 @@ class SubscriptionHandler extends Handler
user.save (err) => user.save (err) =>
if err if err
@logSubscriptionError(req, 'Stripe user plan saving error. '+err) @logSubscriptionError(user, 'Stripe user plan saving error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
req.user?.saveActiveUser 'subscribe' user?.saveActiveUser 'subscribe'
return done() return done()
unsubscribeUser: (req, user, done) -> unsubscribeUser: (req, user, done) ->
stripeInfo = _.cloneDeep(user.get('stripe')) stripeInfo = _.cloneDeep(user.get('stripe'))
stripe.customers.cancelSubscription stripeInfo.customerID, stripeInfo.subscriptionID, { at_period_end: true }, (err) => stripe.customers.cancelSubscription stripeInfo.customerID, stripeInfo.subscriptionID, { at_period_end: true }, (err) =>
if err if err
@logSubscriptionError(req, 'Stripe cancel subscription error. '+err) @logSubscriptionError(user, 'Stripe cancel subscription error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
delete stripeInfo.planID delete stripeInfo.planID
user.set('stripe', stripeInfo) user.set('stripe', stripeInfo)
req.body.stripe = stripeInfo req.body.stripe = stripeInfo
user.save (err) => user.save (err) =>
if err if err
@logSubscriptionError(req, 'User save unsubscribe error. '+err) @logSubscriptionError(user, 'User save unsubscribe error. '+err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
req.user?.saveActiveUser 'unsubscribe' user?.saveActiveUser 'unsubscribe'
return done() return done()
module.exports = new SubscriptionHandler() module.exports = new SubscriptionHandler()

View file

@ -179,6 +179,13 @@ UserSchema.methods.register = (done) ->
delighted.addDelightedUser @ delighted.addDelightedUser @
@saveActiveUser 'register' @saveActiveUser 'register'
UserSchema.methods.isPremium = ->
return false unless stripe = @get('stripe')
return true if stripe.subscriptionID
return true if stripe.free is true
return true if _.isString(stripe.free) and new Date() < new Date(stripe.free)
return false
UserSchema.statics.saveActiveUser = (id, event, done=null) -> UserSchema.statics.saveActiveUser = (id, event, done=null) ->
id = mongoose.Types.ObjectId id if _.isString id id = mongoose.Types.ObjectId id if _.isString id
@findById id, (err, user) -> @findById id, (err, user) ->

View file

@ -230,9 +230,19 @@ UserHandler = class UserHandler extends Handler
return @trackActivity(req, res, args[0], args[2], args[3]) if args[1] is 'track' and args[2] return @trackActivity(req, res, args[0], args[2], args[3]) if args[1] is 'track' and args[2]
return @getRemark(req, res, args[0]) if args[1] is 'remark' return @getRemark(req, res, args[0]) if args[1] is 'remark'
return @searchForUser(req, res) if args[1] is 'admin_search' return @searchForUser(req, res) if args[1] is 'admin_search'
return @getStripeInfo(req, res, args[0]) if args[1] is 'stripe'
return @sendNotFoundError(res) return @sendNotFoundError(res)
super(arguments...) super(arguments...)
getStripeInfo: (req, res, handle) ->
@getDocumentForIdOrSlug handle, (err, user) =>
return @sendNotFoundError(res) if not user
return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or req.user.get('_id').equals(user.get('_id')))
return @sendNotFoundError(res) #if not customerID = user.get('stripe')?.customerID
stripe.customers.retrieve customerID, (err, customer) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, JSON.stringify(customer, null, '\t'))
agreeToCLA: (req, res) -> agreeToCLA: (req, res) ->
return @sendForbiddenError(res) unless req.user return @sendForbiddenError(res) unless req.user
doc = doc =

View file

@ -16,6 +16,8 @@ UserHandler = require './server/users/user_handler'
hipchat = require './server/hipchat' hipchat = require './server/hipchat'
global.tv4 = require 'tv4' # required for TreemaUtils to work global.tv4 = require 'tv4' # required for TreemaUtils to work
global.jsondiffpatch = require 'jsondiffpatch' global.jsondiffpatch = require 'jsondiffpatch'
global.stripe = require('stripe')(config.stripe.secretKey)
productionLogging = (tokens, req, res) -> productionLogging = (tokens, req, res) ->
status = res.statusCode status = res.statusCode