2015-10-01 18:23:20 -04:00
|
|
|
Course = require '../courses/Course'
|
2015-03-19 18:02:45 -04:00
|
|
|
Handler = require '../commons/Handler'
|
2015-10-01 18:23:20 -04:00
|
|
|
hipchat = require '../hipchat'
|
2015-03-19 18:02:45 -04:00
|
|
|
Prepaid = require './Prepaid'
|
2015-11-02 18:23:08 -05:00
|
|
|
User = require '../users/User'
|
2015-09-25 13:03:44 -04:00
|
|
|
StripeUtils = require '../lib/stripe_utils'
|
2015-10-01 18:23:20 -04:00
|
|
|
utils = require '../../app/core/utils'
|
2015-11-05 18:29:22 -05:00
|
|
|
mongoose = require 'mongoose'
|
2015-12-14 14:10:37 -05:00
|
|
|
Product = require '../models/Product'
|
2015-03-19 18:02:45 -04:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2015-11-10 19:32:31 -05:00
|
|
|
cutoffID = mongoose.Types.ObjectId('5642877accc6494a01cc6bfe')
|
|
|
|
|
2015-03-19 18:02:45 -04:00
|
|
|
PrepaidHandler = class PrepaidHandler extends Handler
|
|
|
|
modelClass: Prepaid
|
|
|
|
jsonSchema: require '../../app/schemas/models/prepaid.schema'
|
2015-09-25 13:03:44 -04:00
|
|
|
allowedMethods: ['GET','POST']
|
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
logError: (user, msg) ->
|
|
|
|
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
|
2015-03-19 18:02:45 -04:00
|
|
|
|
|
|
|
hasAccess: (req) ->
|
2015-10-06 14:20:53 -04:00
|
|
|
req.method is 'GET' || req.user?.isAdmin()
|
2015-03-19 18:02:45 -04:00
|
|
|
|
|
|
|
getByRelationship: (req, res, args...) ->
|
|
|
|
relationship = args[1]
|
2016-02-06 17:02:44 -05:00
|
|
|
return @getCoursePrepaidsAPI(req, res) if relationship is 'courses'
|
2015-10-01 18:23:20 -04:00
|
|
|
return @getPrepaidAPI(req, res, args[2]) if relationship is 'code'
|
|
|
|
return @createPrepaidAPI(req, res) if relationship is 'create'
|
|
|
|
return @purchasePrepaidAPI(req, res) if relationship is 'purchase'
|
2015-11-02 18:23:08 -05:00
|
|
|
return @postRedeemerAPI(req, res, args[0]) if relationship is 'redeemers'
|
2015-03-19 18:02:45 -04:00
|
|
|
super arguments...
|
|
|
|
|
2016-02-06 17:02:44 -05:00
|
|
|
getCoursePrepaidsAPI: (req, res, code) ->
|
|
|
|
return @sendSuccess(res, []) unless req.user?.isAdmin()
|
|
|
|
query = {$and: [
|
|
|
|
{type: 'course'},
|
|
|
|
{maxRedeemers: {$ne: "9999"}},
|
|
|
|
{'properties.courseIDs': {$exists: false}},
|
|
|
|
{_id: {$gt: cutoffID}}
|
|
|
|
]}
|
|
|
|
Prepaid.find(query, req.body.project).exec (err, documents) =>
|
|
|
|
return @sendDatabaseError(res, err) if err
|
|
|
|
@sendSuccess(res, documents)
|
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
getPrepaidAPI: (req, res, code) ->
|
2015-09-25 13:03:44 -04:00
|
|
|
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())
|
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
createPrepaidAPI: (req, res) ->
|
2015-03-19 18:02:45 -04:00
|
|
|
return @sendForbiddenError(res) unless @hasAccess(req)
|
2015-10-01 18:23:20 -04:00
|
|
|
return @sendForbiddenError(res) unless req.body.type in ['course', 'subscription','terminal_subscription']
|
2015-11-10 14:27:46 -05:00
|
|
|
return @sendForbiddenError(res) unless parseInt(req.body.maxRedeemers) > 0
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
properties = {}
|
|
|
|
type = req.body.type
|
|
|
|
maxRedeemers = req.body.maxRedeemers
|
|
|
|
|
|
|
|
if req.body.type is 'course'
|
|
|
|
return @sendDatabaseError(res, "TODO: need to add courseIDs")
|
|
|
|
else if req.body.type is 'subscription'
|
|
|
|
properties.couponID = 'free'
|
|
|
|
else if req.body.type is 'terminal_subscription'
|
|
|
|
properties.months = req.body.months
|
|
|
|
|
|
|
|
@createPrepaid req.user, req.body.type, req.body.maxRedeemers, properties, (err, prepaid) =>
|
|
|
|
return @sendDatabaseError(res, err) if err
|
|
|
|
@sendSuccess(res, prepaid.toObject())
|
|
|
|
|
2015-11-02 18:23:08 -05:00
|
|
|
postRedeemerAPI: (req, res, prepaidID) ->
|
2015-11-10 19:32:31 -05:00
|
|
|
return @sendForbiddenError(res) if prepaidID.toString() < cutoffID.toString()
|
2015-11-02 18:23:08 -05:00
|
|
|
return @sendMethodNotAllowed(res, 'You may only POST redeemers.') if req.method isnt 'POST'
|
|
|
|
return @sendBadInputError(res, 'Need an object with a userID') unless req.body?.userID
|
|
|
|
Prepaid.findById(prepaidID).exec (err, prepaid) =>
|
|
|
|
return @sendDatabaseError(res, err) if err
|
|
|
|
return @sendNotFoundError(res) if not prepaid
|
|
|
|
return @sendForbiddenError(res) if prepaid.get('creator').toString() isnt req.user.id
|
2015-11-24 13:32:15 -05:00
|
|
|
return @sendForbiddenError(res) if prepaid.get('redeemers')? and _.size(prepaid.get('redeemers')) >= prepaid.get('maxRedeemers')
|
2015-11-10 19:32:31 -05:00
|
|
|
return @sendForbiddenError(res) unless prepaid.get('type') is 'course'
|
2015-11-24 13:32:15 -05:00
|
|
|
return @sendForbiddenError(res) if prepaid.get('properties')?.endDate < new Date()
|
2015-11-02 18:23:08 -05:00
|
|
|
User.findById(req.body.userID).exec (err, user) =>
|
|
|
|
return @sendDatabaseError(res, err) if err
|
|
|
|
return @sendNotFoundError(res, 'User for given ID not found') if not user
|
2015-11-20 16:17:33 -05:00
|
|
|
return @sendSuccess(res, @formatEntity(req, prepaid)) if user.get('coursePrepaidID')
|
2015-12-09 17:27:10 -05:00
|
|
|
userID = user.get('_id')
|
2015-11-10 19:32:31 -05:00
|
|
|
|
|
|
|
query =
|
|
|
|
_id: prepaid.get('_id')
|
2015-11-20 16:17:33 -05:00
|
|
|
'redeemers.userID': { $ne: user.get('_id') }
|
2015-11-24 13:32:15 -05:00
|
|
|
$where: "this.maxRedeemers > 0 && (!this.redeemers || this.redeemers.length < #{prepaid.get('maxRedeemers')})"
|
2015-11-10 19:32:31 -05:00
|
|
|
update = { $push: { redeemers : { date: new Date(), userID: userID } }}
|
2015-12-09 14:56:18 -05:00
|
|
|
Prepaid.update query, update, (err, result) =>
|
2015-11-02 18:23:08 -05:00
|
|
|
return @sendDatabaseError(res, err) if err
|
2015-12-09 14:56:18 -05:00
|
|
|
if result.nModified is 0
|
2015-11-10 19:32:31 -05:00
|
|
|
@logError(req.user, "POST prepaid redeemer lost race on maxRedeemers")
|
|
|
|
return @sendForbiddenError(res)
|
2015-11-24 13:32:15 -05:00
|
|
|
|
2015-11-10 19:32:31 -05:00
|
|
|
user.set('coursePrepaidID', prepaid.get('_id'))
|
|
|
|
user.save (err, user) =>
|
2015-11-02 18:23:08 -05:00
|
|
|
return @sendDatabaseError(res, err) if err
|
2015-11-10 19:32:31 -05:00
|
|
|
# return prepaid with new redeemer added locally
|
|
|
|
redeemers = _.clone(prepaid.get('redeemers') or [])
|
|
|
|
redeemers.push({ date: new Date(), userID: userID })
|
|
|
|
prepaid.set('redeemers', redeemers)
|
2015-11-24 13:32:15 -05:00
|
|
|
@sendSuccess(res, @formatEntity(req, prepaid))
|
2015-11-02 18:23:08 -05:00
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
createPrepaid: (user, type, maxRedeemers, properties, done) ->
|
2015-06-05 09:48:09 -04:00
|
|
|
Prepaid.generateNewCode (code) =>
|
2015-10-01 18:23:20 -04:00
|
|
|
return done('Database error.') unless code
|
2015-09-25 13:03:44 -04:00
|
|
|
options =
|
2015-10-01 18:23:20 -04:00
|
|
|
creator: user._id
|
|
|
|
type: type
|
2015-03-19 18:02:45 -04:00
|
|
|
code: code
|
2015-11-10 14:27:46 -05:00
|
|
|
maxRedeemers: parseInt(maxRedeemers)
|
2015-10-01 18:23:20 -04:00
|
|
|
properties: properties
|
2015-09-25 13:03:44 -04:00
|
|
|
redeemers: []
|
|
|
|
|
|
|
|
prepaid = new Prepaid options
|
2015-03-19 18:02:45 -04:00
|
|
|
prepaid.save (err) =>
|
2015-10-01 18:23:20 -04:00
|
|
|
return done(err) if err
|
|
|
|
done(err, prepaid)
|
|
|
|
|
|
|
|
purchasePrepaidAPI: (req, res) ->
|
|
|
|
return @sendUnauthorizedError(res) if not req.user? or req.user?.isAnonymous()
|
|
|
|
return @sendForbiddenError(res) unless req.body.type in ['course', 'terminal_subscription']
|
|
|
|
|
|
|
|
if req.body.type is 'terminal_subscription'
|
|
|
|
description = req.body.description
|
|
|
|
maxRedeemers = parseInt(req.body.maxRedeemers)
|
|
|
|
months = parseInt(req.body.months)
|
|
|
|
timestamp = req.body.stripe?.timestamp
|
|
|
|
token = req.body.stripe?.token
|
|
|
|
|
|
|
|
return @sendBadInputError(res) unless isNaN(maxRedeemers) is false and maxRedeemers > 0
|
|
|
|
return @sendBadInputError(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
|
|
|
|
|
2015-12-14 14:10:37 -05:00
|
|
|
Product.findOne({name: 'prepaid_subscription'}).exec (err, product) =>
|
2015-03-19 18:02:45 -04:00
|
|
|
return @sendDatabaseError(res, err) if err
|
2015-12-14 14:10:37 -05:00
|
|
|
return @sendNotFoundError(res, 'prepaid_subscription product not found') if not product
|
|
|
|
|
|
|
|
@purchasePrepaidTerminalSubscription req.user, description, maxRedeemers, months, timestamp, token, product, (err, prepaid) =>
|
|
|
|
return @sendDatabaseError(res, err) if err
|
|
|
|
@sendSuccess(res, prepaid.toObject())
|
2015-03-19 18:02:45 -04:00
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
else if req.body.type is 'course'
|
|
|
|
maxRedeemers = parseInt(req.body.maxRedeemers)
|
|
|
|
timestamp = req.body.stripe?.timestamp
|
|
|
|
token = req.body.stripe?.token
|
|
|
|
|
|
|
|
return @sendBadInputError(res) unless isNaN(maxRedeemers) is false and maxRedeemers > 0
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-12-14 14:10:37 -05:00
|
|
|
Product.findOne({name: 'course'}).exec (err, product) =>
|
2015-11-05 17:20:24 -05:00
|
|
|
return @sendDatabaseError(res, err) if err
|
2015-12-14 14:10:37 -05:00
|
|
|
return @sendNotFoundError(res, 'course product not found') if not product
|
|
|
|
|
|
|
|
@purchasePrepaidCourse req.user, maxRedeemers, timestamp, token, product, (err, prepaid) =>
|
|
|
|
# TODO: this badinput detection is fragile, in course instance handler as well
|
|
|
|
return @sendBadInputError(res, err) if err is 'Missing required Stripe token'
|
|
|
|
return @sendDatabaseError(res, err) if err
|
|
|
|
@sendSuccess(res, prepaid.toObject())
|
2015-10-01 18:23:20 -04:00
|
|
|
else
|
|
|
|
@sendForbiddenError(res)
|
|
|
|
|
2015-12-14 14:10:37 -05:00
|
|
|
purchasePrepaidCourse: (user, maxRedeemers, timestamp, token, product, done) ->
|
2015-10-01 18:23:20 -04:00
|
|
|
type = 'course'
|
|
|
|
|
2015-12-14 14:10:37 -05:00
|
|
|
amount = maxRedeemers * product.get('amount')
|
2015-10-29 17:09:16 -04:00
|
|
|
if amount > 0 and not (token or user.isAdmin())
|
2015-10-01 18:23:20 -04:00
|
|
|
@logError(user, "Purchase prepaid courses missing required Stripe token #{amount}")
|
|
|
|
return done('Missing required Stripe token')
|
|
|
|
|
2015-10-29 17:09:16 -04:00
|
|
|
if amount is 0 or user.isAdmin()
|
2015-11-05 17:20:24 -05:00
|
|
|
@createPrepaid(user, type, maxRedeemers, {}, done)
|
2015-10-01 18:23:20 -04:00
|
|
|
|
|
|
|
else
|
|
|
|
StripeUtils.getCustomer user, token, (err, customer) =>
|
|
|
|
if err
|
|
|
|
@logError(user, "Stripe getCustomer error: #{JSON.stringify(err)}")
|
|
|
|
return done(err)
|
|
|
|
|
|
|
|
metadata =
|
|
|
|
type: type
|
|
|
|
userID: user.id
|
|
|
|
timestamp: parseInt(timestamp)
|
|
|
|
maxRedeemers: maxRedeemers
|
|
|
|
productID: "prepaid #{type}"
|
|
|
|
|
|
|
|
StripeUtils.createCharge user, amount, metadata, (err, charge) =>
|
|
|
|
if err
|
|
|
|
@logError(user, "createCharge error: #{JSON.stringify(err)}")
|
|
|
|
return done(err)
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
StripeUtils.createPayment user, charge, (err, payment) =>
|
|
|
|
if err
|
|
|
|
@logError(user, "createPayment error: #{JSON.stringify(err)}")
|
|
|
|
return done(err)
|
2015-11-05 17:20:24 -05:00
|
|
|
msg = "Prepaid code purchased: #{type} seats=#{maxRedeemers} #{user.get('email')}"
|
2015-10-01 18:23:20 -04:00
|
|
|
hipchat.sendHipChatMessage msg, ['tower']
|
2015-11-05 17:20:24 -05:00
|
|
|
@createPrepaid(user, type, maxRedeemers, {}, done)
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-12-14 14:10:37 -05:00
|
|
|
purchasePrepaidTerminalSubscription: (user, description, maxRedeemers, months, timestamp, token, product, done) ->
|
2015-10-01 18:23:20 -04:00
|
|
|
type = 'terminal_subscription'
|
|
|
|
|
|
|
|
StripeUtils.getCustomer user, token, (err, customer) =>
|
2015-09-25 13:03:44 -04:00
|
|
|
if err
|
2015-10-01 18:23:20 -04:00
|
|
|
@logError(user, "getCustomer error: #{JSON.stringify(err)}")
|
|
|
|
return done(err)
|
2015-09-25 13:03:44 -04:00
|
|
|
|
|
|
|
metadata =
|
2015-10-01 18:23:20 -04:00
|
|
|
type: type
|
|
|
|
userID: user.id
|
|
|
|
timestamp: parseInt(timestamp)
|
|
|
|
description: description
|
2015-09-25 13:03:44 -04:00
|
|
|
maxRedeemers: maxRedeemers
|
|
|
|
months: months
|
2015-10-01 18:23:20 -04:00
|
|
|
productID: "prepaid #{type}"
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-12-14 14:10:37 -05:00
|
|
|
amount = utils.getPrepaidCodeAmount(product.get('amount'), maxRedeemers, months)
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
StripeUtils.createCharge user, amount, metadata, (err, charge) =>
|
2015-09-25 13:03:44 -04:00
|
|
|
if err
|
2015-10-01 18:23:20 -04:00
|
|
|
@logError(user, "createCharge error: #{JSON.stringify(err)}")
|
|
|
|
return done(err)
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-10-01 18:23:20 -04:00
|
|
|
StripeUtils.createPayment user, charge, (err, payment) =>
|
2015-09-25 13:03:44 -04:00
|
|
|
if err
|
2015-10-01 18:23:20 -04:00
|
|
|
@logError(user, "createPayment error: #{JSON.stringify(err)}")
|
|
|
|
return done(err)
|
2015-09-25 13:03:44 -04:00
|
|
|
|
|
|
|
Prepaid.generateNewCode (code) =>
|
2015-10-01 18:23:20 -04:00
|
|
|
return done('Database error.') unless code
|
2015-09-25 13:03:44 -04:00
|
|
|
prepaid = new Prepaid
|
2015-10-01 18:23:20 -04:00
|
|
|
creator: user._id
|
|
|
|
type: type
|
2015-09-25 13:03:44 -04:00
|
|
|
code: code
|
2015-11-10 14:27:46 -05:00
|
|
|
maxRedeemers: parseInt(maxRedeemers)
|
2015-09-25 13:03:44 -04:00
|
|
|
redeemers: []
|
|
|
|
properties:
|
2015-10-01 18:23:20 -04:00
|
|
|
months: months
|
2015-09-25 13:03:44 -04:00
|
|
|
prepaid.save (err) =>
|
2015-10-01 18:23:20 -04:00
|
|
|
return done(err) if err
|
|
|
|
msg = "Prepaid code purchased: #{type} users=#{maxRedeemers} months=#{months} #{user.get('email')}"
|
2015-09-30 15:13:33 -04:00
|
|
|
hipchat.sendHipChatMessage msg, ['tower']
|
2015-10-01 18:23:20 -04:00
|
|
|
return done(null, prepaid)
|
2015-09-25 13:03:44 -04:00
|
|
|
|
2015-11-05 18:29:22 -05:00
|
|
|
|
|
|
|
get: (req, res) ->
|
|
|
|
if creator = req.query.creator
|
|
|
|
return @sendForbiddenError(res) unless req.user and (req.user.isAdmin() or creator is req.user.id)
|
|
|
|
return @sendBadInputError(res, 'Bad creator') unless utils.isID creator
|
2015-11-10 19:32:31 -05:00
|
|
|
q = {
|
|
|
|
_id: {$gt: cutoffID}
|
2015-11-24 13:32:15 -05:00
|
|
|
creator: mongoose.Types.ObjectId(creator)
|
2015-11-10 19:32:31 -05:00
|
|
|
type: 'course'
|
|
|
|
}
|
|
|
|
Prepaid.find q, (err, prepaids) =>
|
2015-11-05 18:29:22 -05:00
|
|
|
return @sendDatabaseError(res, err) if err
|
2015-11-24 13:32:15 -05:00
|
|
|
documents = []
|
|
|
|
for prepaid in prepaids
|
|
|
|
documents.push(@formatEntity(req, prepaid)) unless prepaid.get('properties')?.endDate < new Date()
|
|
|
|
return @sendSuccess(res, documents)
|
2015-11-05 18:29:22 -05:00
|
|
|
else
|
|
|
|
super(arguments...)
|
2015-11-10 17:33:27 -05:00
|
|
|
|
|
|
|
makeNewInstance: (req) ->
|
|
|
|
prepaid = super(req)
|
|
|
|
prepaid.set('redeemers', [])
|
|
|
|
return prepaid
|
2015-11-24 13:32:15 -05:00
|
|
|
|
2015-03-19 18:02:45 -04:00
|
|
|
module.exports = new PrepaidHandler()
|