From cb29d7068b32012aa9411faeb57c8d6e281048ba Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Wed, 17 Feb 2016 05:34:12 -0800 Subject: [PATCH] Add prepaids to payments Closes #3402 --- app/schemas/models/payment.schema.coffee | 1 + app/views/account/PrepaidView.coffee | 6 +-- server/lib/stripe_utils.coffee | 3 +- server/payments/subscription_handler.coffee | 4 +- server/prepaids/prepaid_handler.coffee | 42 ++++++++++----------- spec/server/functional/prepaid.spec.coffee | 37 +++++++++++------- 6 files changed, 50 insertions(+), 43 deletions(-) diff --git a/app/schemas/models/payment.schema.coffee b/app/schemas/models/payment.schema.coffee index cfb24eae2..d15db14a0 100644 --- a/app/schemas/models/payment.schema.coffee +++ b/app/schemas/models/payment.schema.coffee @@ -10,6 +10,7 @@ PaymentSchema = c.object({title: 'Payment', required: []}, { gems: { type: 'integer', description: 'The number of gems acquired.' } productID: { enum: ['gems_5', 'gems_10', 'gems_20', 'custom']} description: { type: 'string' } + prepaidID: c.objectId() ios: c.object({title: 'iOS IAP Data'}, { transactionID: { type: 'string' } diff --git a/app/views/account/PrepaidView.coffee b/app/views/account/PrepaidView.coffee index dcc503010..18814a666 100644 --- a/app/views/account/PrepaidView.coffee +++ b/app/views/account/PrepaidView.coffee @@ -37,7 +37,7 @@ module.exports = class PrepaidView extends RootView @codes = new CocoCollection([], { url: '/db/user/'+me.id+'/prepaid_codes', model: Prepaid }) @codes.on 'sync', (code) => @render?() - @supermodel.loadCollection(@codes, 'prepaid', {cache: false}) + @supermodel.loadCollection(@codes, {cache: false}) @ppc = utils.getQueryVariable('_ppc') ? '' unless _.isEmpty(@ppc) @@ -45,7 +45,7 @@ module.exports = class PrepaidView extends RootView @loadPrepaid(@ppc) @products = new Products() - @supermodel.loadCollection(@products, 'products') + @supermodel.loadCollection(@products) onLoaded: -> @prepaidProduct = @products.findWhere { name: 'prepaid_subscription' } @@ -190,7 +190,7 @@ module.exports = class PrepaidView extends RootView @renderSelectors('#codes-panel') @statusMessage "Finalizing purchase...", "information" - @supermodel.addRequestResource('purchase_prepaid', options, 0).load() + @supermodel.addRequestResource(options, 0).load() loadPrepaid: (ppc) -> return unless ppc diff --git a/server/lib/stripe_utils.coffee b/server/lib/stripe_utils.coffee index 91a239fc5..3b7fa9727 100644 --- a/server/lib/stripe_utils.coffee +++ b/server/lib/stripe_utils.coffee @@ -21,7 +21,7 @@ module.exports = return done(err) done(err, charge) - createPayment: (user, stripeCharge, done) -> + createPayment: (user, stripeCharge, extraProps, done) -> payment = new Payment purchaser: user._id recipient: user._id @@ -34,6 +34,7 @@ module.exports = customerID: stripeCharge.customer timestamp: parseInt(stripeCharge.metadata.timestamp) chargeID: stripeCharge.id + payment.set(prop, val) for prop, val of extraProps validation = PaymentHandler.validateDocumentInput(payment.toObject()) if validation.valid is false @logError(user, 'Invalid stripe payment object.') diff --git a/server/payments/subscription_handler.coffee b/server/payments/subscription_handler.coffee index de351c550..bfd957faa 100644 --- a/server/payments/subscription_handler.coffee +++ b/server/payments/subscription_handler.coffee @@ -156,7 +156,7 @@ class SubscriptionHandler extends Handler @logSubscriptionError(req.user, "Purchase year sale create charge: #{JSON.stringify(err)}") return @sendDatabaseError(res, err) - StripeUtils.createPayment req.user, charge, (err, payment) => + StripeUtils.createPayment req.user, charge, {}, (err, payment) => if err @logSubscriptionError(req.user, "Purchase year sale create payment: #{JSON.stringify(err)}") return @sendDatabaseError(res, err) @@ -200,7 +200,7 @@ class SubscriptionHandler extends Handler @logSubscriptionError(req.user, "Redeem Prepaid Code find: #{JSON.stringify(err)}") return @sendDatabaseError(res, err) unless prepaid - @logSubscriptionError(req.user, "Could not find prepaid code #{req.body.ppc}") + @logSubscriptionError(req.user, "Could not find prepaid code #{req.body.ppc?.toString()}") return @sendNotFoundError(res, "Prepaid not found") oldRedeemers = prepaid.get('redeemers') ? [] diff --git a/server/prepaids/prepaid_handler.coffee b/server/prepaids/prepaid_handler.coffee index 0d66edb5c..b92af6a72 100644 --- a/server/prepaids/prepaid_handler.coffee +++ b/server/prepaids/prepaid_handler.coffee @@ -202,13 +202,18 @@ PrepaidHandler = class PrepaidHandler extends Handler @logError(user, "createCharge error: #{JSON.stringify(err)}") return done(err) - StripeUtils.createPayment user, charge, (err, payment) => + @createPrepaid user, type, maxRedeemers, {}, (err, prepaid) => if err - @logError(user, "createPayment error: #{JSON.stringify(err)}") + @logError(user, "createPrepaid error: #{JSON.stringify(err)}") return done(err) - msg = "Prepaid code purchased: #{type} seats=#{maxRedeemers} #{user.get('email')}" - hipchat.sendHipChatMessage msg, ['tower'] - @createPrepaid(user, type, maxRedeemers, {}, done) + + StripeUtils.createPayment user, charge, {prepaidID: prepaid._id}, (err, payment) => + if err + @logError(user, "createPayment error: #{JSON.stringify(err)}") + return done(err) + msg = "Prepaid code purchased: #{type} seats=#{maxRedeemers} #{user.get('email')}" + hipchat.sendHipChatMessage msg, ['tower'] + done(null, prepaid) purchasePrepaidTerminalSubscription: (user, description, maxRedeemers, months, timestamp, token, product, done) -> type = 'terminal_subscription' @@ -234,27 +239,18 @@ PrepaidHandler = class PrepaidHandler extends Handler @logError(user, "createCharge error: #{JSON.stringify(err)}") return done(err) - StripeUtils.createPayment user, charge, (err, payment) => + @createPrepaid user, type, maxRedeemers, {months: months}, (err, prepaid) => if err - @logError(user, "createPayment error: #{JSON.stringify(err)}") + @logError(user, "createPrepaid error: #{JSON.stringify(err)}") return done(err) - Prepaid.generateNewCode (code) => - return done('Database error.') unless code - prepaid = new Prepaid - creator: user._id - type: type - code: code - maxRedeemers: parseInt(maxRedeemers) - redeemers: [] - properties: - months: months - prepaid.save (err) => - return done(err) if err - msg = "Prepaid code purchased: #{type} users=#{maxRedeemers} months=#{months} #{user.get('email')}" - hipchat.sendHipChatMessage msg, ['tower'] - return done(null, prepaid) - + StripeUtils.createPayment user, charge, {prepaidID: prepaid._id}, (err, payment) => + if err + @logError(user, "createPayment error: #{JSON.stringify(err)}") + return done(err) + msg = "Prepaid code purchased: #{type} users=#{maxRedeemers} months=#{months} #{user.get('email')}" + hipchat.sendHipChatMessage msg, ['tower'] + done(null, prepaid) get: (req, res) -> if creator = req.query.creator diff --git a/spec/server/functional/prepaid.spec.coffee b/spec/server/functional/prepaid.spec.coffee index bb653794b..442baf6cf 100644 --- a/spec/server/functional/prepaid.spec.coffee +++ b/spec/server/functional/prepaid.spec.coffee @@ -20,7 +20,12 @@ describe '/db/prepaid', -> expect(prepaid.type).toEqual('course') expect(prepaid.maxRedeemers).toBeGreaterThan(0) expect(prepaid.code).toMatch(/^\w{8}$/) - done() + return done() if user.isAdmin() + Payment.findOne {prepaidID: new ObjectId(prepaid._id)}, (err, payment) -> + expect(err).toBeNull() + expect(payment).not.toBeNull() + expect(payment?.get('purchaser')).toEqual(user._id) + done() verifySubscriptionPrepaid = (user, prepaid, done) -> expect(prepaid.creator).toEqual(user.id) @@ -28,13 +33,18 @@ describe '/db/prepaid', -> expect(prepaid.maxRedeemers).toBeGreaterThan(0) expect(prepaid.code).toMatch(/^\w{8}$/) expect(prepaid.properties?.couponID).toEqual('free') - done() + return done() if user.isAdmin() + Payment.findOne {prepaidID: new ObjectId(prepaid._id)}, (err, payment) -> + expect(err).toBeNull() + expect(payment).not.toBeNull() + expect(payment?.get('purchaser')).toEqual(user._id) + done() it 'Clear database', (done) -> clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) -> throw err if err done() - + describe 'POST /db/prepaid//redeemers', -> it 'adds a given user to the redeemers property', (done) -> @@ -60,7 +70,7 @@ describe '/db/prepaid', -> User.findById otherUser.id, (err, user) -> expect(user.get('coursePrepaidID').equals(prepaid.get('_id'))).toBe(true) done() - + it 'does not allow more redeemers than maxRedeemers', (done) -> loginNewUser (user1) -> prepaid = new Prepaid({ @@ -97,7 +107,7 @@ describe '/db/prepaid', -> request.post {uri: url, json: redeemer }, (err, res, body) -> expect(res.statusCode).toBe(403) done() - + it 'is idempotent across prepaids collection', (done) -> loginNewUser (user1) -> otherUser = new User({ @@ -279,7 +289,7 @@ describe '/db/prepaid', -> request.post {uri: url, json: redeemer }, (err, res, body) -> expect(res.statusCode).toBe(403) done() - + it 'Clear database', (done) -> clearModels [Course, CourseInstance, Payment, Prepaid, User], (err) -> throw err if err @@ -392,7 +402,7 @@ describe '/db/prepaid', -> describe 'Purchase course', -> afterEach nockUtils.teardownNock - + it 'Standard user purchases a prepaid for 0 seats', (done) -> nockUtils.setupNock 'db-prepaid-purchase-course-test-1.json', (err, nockDone) -> stripe.tokens.create { @@ -404,7 +414,7 @@ describe '/db/prepaid', -> expect(res.statusCode).toBe(422) nockDone() done() - + it 'Standard user purchases a prepaid for 1 seat', (done) -> nockUtils.setupNock 'db-prepaid-purchase-course-test-2.json', (err, nockDone) -> stripe.tokens.create { @@ -430,7 +440,7 @@ describe '/db/prepaid', -> verifyCoursePrepaid user1, prepaid, -> nockDone() done() - + describe 'Purchase terminal_subscription', -> afterEach nockUtils.teardownNock @@ -624,7 +634,7 @@ describe '/db/prepaid', -> 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() @@ -635,7 +645,7 @@ describe '/db/prepaid', -> else expect(stripeSubscriptionPeriodEndDate).toBeDefined() return done() - + subscribeWithPrepaid joeCode, (err, res, result) => expect(err).toBeNull() expect(res.statusCode).toEqual(200) @@ -690,7 +700,6 @@ describe '/db/prepaid', -> expect(res.statusCode).toEqual(403) done() - it 'Can fetch a list of purchased and redeemed prepaid codes', (done) -> nockUtils.setupNock 'db-sub-redeem-test-2.json', (err, nockDone) -> stripe.tokens.create { @@ -735,12 +744,12 @@ describe '/db/prepaid', -> return fnDone(null, {status: "ok", msg: "Redeemed " + retry}) else return fnDone(null, {status: 'error', msg: "Redeem attempt Error #{result} (#{userX.id})" + retry }) - + redeemPrepaidFn = (code, testnum) => (fnDone) => loginNewUser (user1) => doRedeem(user1, code, testnum, 0, fnDone) - + stripe.tokens.create { card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' } }, (err, token) ->