Просмотр исходного кода

added confirm you want to change plan modal
added page and corisponding endpoint to migrate to annual plan

Henry Oswald 12 лет назад
Родитель
Сommit
f5618e9d9c

+ 19 - 0
services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee

@@ -72,6 +72,8 @@ module.exports = RecurlyWrapper =
 					@getAccount accountId, (error, account) ->
 					@getAccount accountId, (error, account) ->
 						return callback(error) if error?
 						return callback(error) if error?
 						recurlySubscription.account = account
 						recurlySubscription.account = account
+						console.log recurlySubscription
+
 						callback null, recurlySubscription
 						callback null, recurlySubscription
 
 
 				else
 				else
@@ -121,6 +123,23 @@ module.exports = RecurlyWrapper =
 			callback(error)
 			callback(error)
 		)
 		)
 		
 		
+
+	redeemCoupon: (account_code, coupon_code, callback)->
+		requestBody = """
+			<subscription>
+				<account_code>#{account_code}</account_code>
+				<currency>USD</currency>
+			</subscription>
+		"""
+		@apiRequest({
+			url    : "coupons/#{coupon_code}/redeem"
+			method : "post"
+			body   : requestBody
+		}, (error, response, responseBody) =>
+			callback(error)
+		)
+
+
 	_parseSubscriptionXml: (xml, callback) ->
 	_parseSubscriptionXml: (xml, callback) ->
 		@_parseXml xml, (error, data) ->
 		@_parseXml xml, (error, data) ->
 			return callback(error) if error?
 			return callback(error) if error?

+ 19 - 2
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"
 					logger.err err:err, user_id:user._id, "something went wrong canceling subscription"
 				res.redirect "/user/subscription"
 				res.redirect "/user/subscription"
  
  
-
 	updateSubscription: (req, res)->
 	updateSubscription: (req, res)->
 		SecurityManager.getCurrentUser req, (error, user) ->
 		SecurityManager.getCurrentUser req, (error, user) ->
 			return next(error) if error?
 			return next(error) if error?
 			planCode = req.body.plan_code
 			planCode = req.body.plan_code
 			logger.log planCode: planCode, user_id:user._id, "updating subscription"
 			logger.log planCode: planCode, user_id:user._id, "updating subscription"
-			SubscriptionHandler.updateSubscription user, planCode, (err)->
+			SubscriptionHandler.updateSubscription user, planCode, null, (err)->
 				if err?
 				if err?
 					logger.err err:err, user_id:user._id, "something went wrong updating subscription"
 					logger.err err:err, user_id:user._id, "something went wrong updating subscription"
 				res.redirect "/user/subscription"
 				res.redirect "/user/subscription"
@@ -161,6 +160,24 @@ module.exports = SubscriptionController =
 		else
 		else
 			res.send 200
 			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) ->
 	recurlyNotificationParser: (req, res, next) ->
 		xml = ""
 		xml = ""
 		req.on "data", (chunk) ->
 		req.on "data", (chunk) ->

+ 17 - 6
services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee

@@ -1,3 +1,4 @@
+async = require("async")
 RecurlyWrapper = require("./RecurlyWrapper")
 RecurlyWrapper = require("./RecurlyWrapper")
 Settings = require "settings-sharelatex"
 Settings = require "settings-sharelatex"
 User = require('../../models/User').User
 User = require('../../models/User').User
@@ -16,15 +17,25 @@ module.exports =
 				return callback(error) if error?
 				return callback(error) if error?
 				callback()
 				callback()
 
 
-	updateSubscription: (user, plan_code, callback)->
+	updateSubscription: (user, plan_code, coupon_code, callback)->
 		logger.log user:user, plan_code:plan_code, "updating subscription"
 		logger.log user:user, plan_code:plan_code, "updating subscription"
 		LimitationsManager.userHasSubscription user, (err, hasSubscription, 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
 			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) ->
 	cancelSubscription: (user, callback) ->
 		LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)->
 		LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)->

+ 4 - 1
services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee

@@ -26,11 +26,14 @@ module.exports =
 		app.post '/user/subscription/callback',   SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback
 		app.post '/user/subscription/callback',   SubscriptionController.recurlyNotificationParser, SubscriptionController.recurlyCallback
 		app.ignoreCsrf("post", '/user/subscription/callback')
 		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/create',     AuthenticationController.requireLogin(), SubscriptionController.createSubscription
 		app.post '/user/subscription/update',     AuthenticationController.requireLogin(), SubscriptionController.updateSubscription
 		app.post '/user/subscription/update',     AuthenticationController.requireLogin(), SubscriptionController.updateSubscription
 		app.post '/user/subscription/cancel',     AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription
 		app.post '/user/subscription/cancel',     AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription
 		app.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription
 		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
+
 
 

+ 25 - 0
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")
+
+

+ 18 - 0
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("<account_code>#{@recurlyAccountId}</account_code>").should.not.equal -1
+				options.body.indexOf("<currency>USD</currency>").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
+	
 
 
 
 
 
 

+ 26 - 3
services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee

@@ -28,7 +28,7 @@ describe "Subscription controller sanboxed", ->
 			getCurrentUser: sinon.stub().callsArgWith(1, null, @user)
 			getCurrentUser: sinon.stub().callsArgWith(1, null, @user)
 		@SubscriptionHandler = 
 		@SubscriptionHandler = 
 			createSubscription: sinon.stub().callsArgWith(2)
 			createSubscription: sinon.stub().callsArgWith(2)
-			updateSubscription: sinon.stub().callsArgWith(2)
+			updateSubscription: sinon.stub().callsArgWith(3)
 			reactivateSubscription: sinon.stub().callsArgWith(1)
 			reactivateSubscription: sinon.stub().callsArgWith(1)
 			cancelSubscription: sinon.stub().callsArgWith(1)
 			cancelSubscription: sinon.stub().callsArgWith(1)
 			recurlyCallback: sinon.stub().callsArgWith(1)
 			recurlyCallback: sinon.stub().callsArgWith(1)
@@ -193,7 +193,7 @@ describe "Subscription controller sanboxed", ->
 			done()
 			done()
 
 
 
 
-	describe "updateSubscription", ->
+	describe "updateSubscription via post", ->
 		beforeEach (done)->
 		beforeEach (done)->
 			@res =
 			@res =
 				redirect:->
 				redirect:->
@@ -211,7 +211,6 @@ describe "Subscription controller sanboxed", ->
 			@res.redirect.calledWith("/user/subscription").should.equal true
 			@res.redirect.calledWith("/user/subscription").should.equal true
 			done()
 			done()
 
 
-
 	describe "reactivateSubscription", ->
 	describe "reactivateSubscription", ->
 		beforeEach (done)->
 		beforeEach (done)->
 			@res =
 			@res =
@@ -287,5 +286,29 @@ describe "Subscription controller sanboxed", ->
 				@res.send.calledWith(200)
 				@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

+ 24 - 13
services/web/test/UnitTests/coffee/Subscription/SubscriptionHandlerTests.coffee

@@ -42,6 +42,7 @@ describe "Subscription Handler sanboxed", ->
 			updateSubscription: sinon.stub().callsArgWith(2, null, @activeRecurlySubscription)
 			updateSubscription: sinon.stub().callsArgWith(2, null, @activeRecurlySubscription)
 			cancelSubscription: sinon.stub().callsArgWith(1)
 			cancelSubscription: sinon.stub().callsArgWith(1)
 			reactivateSubscription: sinon.stub().callsArgWith(1)
 			reactivateSubscription: sinon.stub().callsArgWith(1)
+			redeemCoupon:sinon.stub().callsArgWith(2)
 
 
 		@SubscriptionUpdater = 
 		@SubscriptionUpdater = 
 			syncSubscription: sinon.stub().callsArgWith(2)
 			syncSubscription: sinon.stub().callsArgWith(2)
@@ -86,7 +87,7 @@ describe "Subscription Handler sanboxed", ->
 				beforeEach (done) ->
 				beforeEach (done) ->
 					@plan_code = "collaborator"
 					@plan_code = "collaborator"
 					@LimitationsManager.userHasSubscription.callsArgWith(1, null, true, @subscription)
 					@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", ->
 				it "should update the subscription", ->
 					@RecurlyWrapper.updateSubscription.calledWith(@subscription.recurlySubscription_id).should.equal true
 					@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][0].should.deep.equal @activeRecurlySubscription
 					@SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id
 					@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", ->
 		describe "with a user without a subscription", ->
 			beforeEach (done) ->
 			beforeEach (done) ->
 				@LimitationsManager.userHasSubscription.callsArgWith(1, null, false)
 				@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", ->
 			it "should redirect to the subscription dashboard", ->
 				@RecurlyWrapper.updateSubscription.called.should.equal false
 				@RecurlyWrapper.updateSubscription.called.should.equal false
 				@SubscriptionHandler.syncSubscriptionToUser.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 "cancelSubscription", ->
 		describe "with a user without a subscription", ->
 		describe "with a user without a subscription", ->
 			beforeEach (done) ->
 			beforeEach (done) ->