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) ->