From f5618e9d9c8315748aeb3dd4ca47e64bfa51d6b9 Mon Sep 17 00:00:00 2001 From: Henry Oswald Date: Wed, 27 Aug 2014 17:51:10 +0100 Subject: [PATCH] added confirm you want to change plan modal added page and corisponding endpoint to migrate to annual plan --- .../Subscription/RecurlyWrapper.coffee | 19 ++++++++++ .../SubscriptionController.coffee | 21 ++++++++++- .../Subscription/SubscriptionHandler.coffee | 23 +++++++++--- .../Subscription/SubscriptionRouter.coffee | 5 ++- .../views/subscriptions/upgradeToAnnual.jade | 25 +++++++++++++ .../Subscription/RecurlyWrapperTests.coffee | 20 +++++++++- .../SubscriptionControllerTests.coffee | 29 +++++++++++++-- .../SubscriptionHandlerTests.coffee | 37 ++++++++++++------- 8 files changed, 153 insertions(+), 26 deletions(-) create mode 100644 services/web/app/views/subscriptions/upgradeToAnnual.jade diff --git a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee b/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee index 86130e1b28..76bfd78a57 100644 --- a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee +++ b/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee @@ -72,6 +72,8 @@ module.exports = RecurlyWrapper = @getAccount accountId, (error, account) -> return callback(error) if error? recurlySubscription.account = account + console.log recurlySubscription + callback null, recurlySubscription else @@ -121,6 +123,23 @@ module.exports = RecurlyWrapper = callback(error) ) + + redeemCoupon: (account_code, coupon_code, callback)-> + requestBody = """ + + #{account_code} + USD + + """ + @apiRequest({ + url : "coupons/#{coupon_code}/redeem" + method : "post" + body : requestBody + }, (error, response, responseBody) => + callback(error) + ) + + _parseSubscriptionXml: (xml, callback) -> @_parseXml xml, (error, data) -> return callback(error) if error? diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index db63af2811..a0e4ee4d4a 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -131,13 +131,12 @@ module.exports = SubscriptionController = logger.err err:err, user_id:user._id, "something went wrong canceling subscription" res.redirect "/user/subscription" - updateSubscription: (req, res)-> SecurityManager.getCurrentUser req, (error, user) -> return next(error) if error? planCode = req.body.plan_code logger.log planCode: planCode, user_id:user._id, "updating subscription" - SubscriptionHandler.updateSubscription user, planCode, (err)-> + SubscriptionHandler.updateSubscription user, planCode, null, (err)-> if err? logger.err err:err, user_id:user._id, "something went wrong updating subscription" res.redirect "/user/subscription" @@ -161,6 +160,24 @@ module.exports = SubscriptionController = else res.send 200 + + renderUpgradeToAnnualPlanPage: (req, res)-> + SecurityManager.getCurrentUser req, (error, user) -> + + LimitationsManager.userHasSubscription user, (err, hasSubscription)-> + if !hasSubscription + return res.redirect("/user/subscription/plans") + res.render "subscriptions/upgradeToAnnual", + title: "Upgrade to annual" + planName: req.query.planName + + + processUpgradeToAnnualPlan: (req, res)-> + SecurityManager.getCurrentUser req, (error, user) -> + {plan_code, coupon_code} = req.body + SubscriptionHandler.updateSubscription user, plan_code, coupon_code, -> + res.send 200 + recurlyNotificationParser: (req, res, next) -> xml = "" req.on "data", (chunk) -> diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee index 3749ba89b6..804c8b0064 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee @@ -1,3 +1,4 @@ +async = require("async") RecurlyWrapper = require("./RecurlyWrapper") Settings = require "settings-sharelatex" User = require('../../models/User').User @@ -16,15 +17,25 @@ module.exports = return callback(error) if error? callback() - updateSubscription: (user, plan_code, callback)-> + updateSubscription: (user, plan_code, coupon_code, callback)-> logger.log user:user, plan_code:plan_code, "updating subscription" LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)-> - if hasSubscription - RecurlyWrapper.updateSubscription subscription.recurlySubscription_id, {plan_code: plan_code, timeframe: "now"}, (error, recurlySubscription) -> - return callback(error) if error? - SubscriptionUpdater.syncSubscription recurlySubscription, user._id, callback + if !hasSubscription + return callback() else - callback() + async.series [ + (cb)-> + return cb() if !coupon_code? + RecurlyWrapper.getSubscription subscription.recurlySubscription_id, includeAccount: true, (err, usersSubscription)-> + return callback(err) if err? + account_code = usersSubscription.account.account_code + RecurlyWrapper.redeemCoupon account_code, coupon_code, cb + (cb)-> + RecurlyWrapper.updateSubscription subscription.recurlySubscription_id, {plan_code: plan_code, timeframe: "now"}, (error, recurlySubscription) -> + return callback(error) if error? + SubscriptionUpdater.syncSubscription recurlySubscription, user._id, cb + ], callback + cancelSubscription: (user, callback) -> LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)-> diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee index cb23864dcf..45d4f2b4bf 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee @@ -26,11 +26,14 @@ module.exports = app.post '/user/subscription/callback', SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback app.ignoreCsrf("post", '/user/subscription/callback') - #user changes there account state + #user changes their account state app.post '/user/subscription/create', AuthenticationController.requireLogin(), SubscriptionController.createSubscription app.post '/user/subscription/update', AuthenticationController.requireLogin(), SubscriptionController.updateSubscription app.post '/user/subscription/cancel', AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription app.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription + app.get "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage + app.post "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.processUpgradeToAnnualPlan + diff --git a/services/web/app/views/subscriptions/upgradeToAnnual.jade b/services/web/app/views/subscriptions/upgradeToAnnual.jade new file mode 100644 index 0000000000..0cd2b0a656 --- /dev/null +++ b/services/web/app/views/subscriptions/upgradeToAnnual.jade @@ -0,0 +1,25 @@ +extends ../layout + +block content + + .content.content-alt + .container + .row + .col-md-6.col-md-offset-3 + .card + .page-header + h1.text-centered Move to Annual Billing + + if planName.indexOf("student") != -1 + | Upgarde from Student to Student Annual and save 20% equivilent to $19.2 + + if planName.indexOf("collaborator") != -1 + | Upgarde from Collaborator to Collaborator Annual and save 20% equivilent to $36 + + + form(action="/user/subscription/upgrade-annual", method="post") + input(name="_csrf", type="hidden", value=csrfToken) + input(name="planName", type="hidden", value=planName) + input.btn.btn-success(type="submit", value="Move to annual billing now") + + diff --git a/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee b/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee index 4560406017..34cb179f25 100644 --- a/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee @@ -309,7 +309,25 @@ describe "RecurlyWrapper", -> - + describe "redeemCoupon", -> + + beforeEach (done) -> + @recurlyAccountId = "account-id-123" + @coupon_code = "312321312" + @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) => + options.url.should.equal "coupons/#{@coupon_code}/redeem" + options.body.indexOf("#{@recurlyAccountId}").should.not.equal -1 + options.body.indexOf("USD").should.not.equal -1 + options.method.should.equal "post" + callback() + RecurlyWrapper.redeemCoupon(@recurlyAccountId, @coupon_code, done) + + afterEach -> + RecurlyWrapper.apiRequest.restore() + + it "should send the request to redem the coupon", -> + @apiRequest.called.should.equal true + diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee index c8474a04ba..2ddf0bbc3b 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee @@ -28,7 +28,7 @@ describe "Subscription controller sanboxed", -> getCurrentUser: sinon.stub().callsArgWith(1, null, @user) @SubscriptionHandler = createSubscription: sinon.stub().callsArgWith(2) - updateSubscription: sinon.stub().callsArgWith(2) + updateSubscription: sinon.stub().callsArgWith(3) reactivateSubscription: sinon.stub().callsArgWith(1) cancelSubscription: sinon.stub().callsArgWith(1) recurlyCallback: sinon.stub().callsArgWith(1) @@ -193,7 +193,7 @@ describe "Subscription controller sanboxed", -> done() - describe "updateSubscription", -> + describe "updateSubscription via post", -> beforeEach (done)-> @res = redirect:-> @@ -211,7 +211,6 @@ describe "Subscription controller sanboxed", -> @res.redirect.calledWith("/user/subscription").should.equal true done() - describe "reactivateSubscription", -> beforeEach (done)-> @res = @@ -287,5 +286,29 @@ describe "Subscription controller sanboxed", -> @res.send.calledWith(200) + describe "renderUpgradeToAnnualPlanPage", -> + it "should redirect to the plans page if the user does not have a subscription", (done)-> + @LimitationsManager.userHasSubscription.callsArgWith(1, null, false) + @res.redirect = (url)-> + url.should.equal "/user/subscription/plans" + done() + @SubscriptionController.renderUpgradeToAnnualPlanPage @req, @res + + + describe "processUpgradeToAnnualPlan", -> + + beforeEach -> + @req.body = + coupon_code:"1234" + plan_code:"student-annual" + + @res = {} + + it "should tell the subscription handler to update the subscription with the annual plan and apply a coupon code", (done)-> + @res.send = ()=> + @SubscriptionHandler.updateSubscription.calledWith(@user, "student-annual", "1234").should.equal true + done() + + @SubscriptionController.processUpgradeToAnnualPlan @req, @res \ No newline at end of file diff --git a/services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee index daa677d2e0..247bb14535 100644 --- a/services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee @@ -42,6 +42,7 @@ describe "Subscription Handler sanboxed", -> updateSubscription: sinon.stub().callsArgWith(2, null, @activeRecurlySubscription) cancelSubscription: sinon.stub().callsArgWith(1) reactivateSubscription: sinon.stub().callsArgWith(1) + redeemCoupon:sinon.stub().callsArgWith(2) @SubscriptionUpdater = syncSubscription: sinon.stub().callsArgWith(2) @@ -86,7 +87,7 @@ describe "Subscription Handler sanboxed", -> beforeEach (done) -> @plan_code = "collaborator" @LimitationsManager.userHasSubscription.callsArgWith(1, null, true, @subscription) - @SubscriptionHandler.updateSubscription(@user, @plan_code, done) + @SubscriptionHandler.updateSubscription(@user, @plan_code, null, done) it "should update the subscription", -> @RecurlyWrapper.updateSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true @@ -102,26 +103,36 @@ describe "Subscription Handler sanboxed", -> @SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription @SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id - # describe "without a valid plan code", -> - # beforeEach (done) -> - # @plan_code = "not-a-plan" - # @LimitationsManager.userHasSubscription.callsArgWith(1, null, true) - - # @SubscriptionHandler.updateSubscription(@user, @plan_code, done) - - # it "should not update the subscription", -> - # @RecurlyWrapper.updateSubscription.called.should.equal false - # @SubscriptionHandler.syncSubscriptionToUser.called.should.equal false - describe "with a user without a subscription", -> beforeEach (done) -> @LimitationsManager.userHasSubscription.callsArgWith(1, null, false) - @SubscriptionHandler.updateSubscription(@user, @plan_code, done) + @SubscriptionHandler.updateSubscription(@user, @plan_code, null, done) it "should redirect to the subscription dashboard", -> @RecurlyWrapper.updateSubscription.called.should.equal false @SubscriptionHandler.syncSubscriptionToUser.called.should.equal false + describe "with a coupon code", -> + beforeEach (done) -> + @plan_code = "collaborator" + @coupon_code = "1231312" + @LimitationsManager.userHasSubscription.callsArgWith(1, null, true, @subscription) + @SubscriptionHandler.updateSubscription(@user, @plan_code, @coupon_code, done) + + it "should get the users account", -> + @RecurlyWrapper.getSubscription.calledWith(@activeRecurlySubscription.uuid).should.equal true + + it "should redeme the coupon", (done)-> + @RecurlyWrapper.redeemCoupon.calledWith(@activeRecurlySubscription.account.account_code, @coupon_code).should.equal true + done() + + it "should update the subscription", -> + @RecurlyWrapper.updateSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true + updateOptions = @RecurlyWrapper.updateSubscription.args[0][1] + updateOptions.plan_code.should.equal @plan_code + + + describe "cancelSubscription", -> describe "with a user without a subscription", -> beforeEach (done) ->