Bläddra i källkod

Display Pricing Exceptions on Subscription Dashboard (#1720)

Display Pricing Exceptions on Subscription Dashboard

GitOrigin-RevId: 31de89824db70b7af1f8704e6da592064ce44bfd
Timothée Alby 7 år sedan
förälder
incheckning
85e7f688d5

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

@@ -321,6 +321,27 @@ module.exports = RecurlyWrapper =
 			RecurlyWrapper._parseAccountXml body, callback
 		)
 
+	getAccountActiveCoupons: (accountId, callback) ->
+		RecurlyWrapper.apiRequest({
+			url: "accounts/#{accountId}/redemptions"
+		}, (error, response, body) =>
+			return callback(error) if error?
+			RecurlyWrapper._parseRedemptionsXml body, (error, redemptions) ->
+				return callback(error) if error?
+				activeRedemptions = redemptions.filter (redemption) ->
+					redemption.state == 'active'
+				couponCodes = activeRedemptions.map (redemption) ->
+					redemption.coupon_code
+				Async.map couponCodes, RecurlyWrapper.getCoupon, (error, coupons) ->
+					return callback(error) if error?
+					callback(null, coupons)
+		)
+
+	getCoupon: (couponCode, callback) ->
+		opts = { url: "coupons/#{couponCode}" }
+		RecurlyWrapper.apiRequest opts, (error, response, body) ->
+			RecurlyWrapper._parseCouponXml body, callback
+
 	getBillingInfo: (accountId, callback)->
 		RecurlyWrapper.apiRequest({
 			url: "accounts/#{accountId}/billing_info"
@@ -465,6 +486,12 @@ module.exports = RecurlyWrapper =
 	_parseBillingInfoXml: (xml, callback) ->
 		RecurlyWrapper._parseXmlAndGetAttribute xml, "billing_info", callback
 
+	_parseRedemptionsXml: (xml, callback) ->
+		RecurlyWrapper._parseXmlAndGetAttribute xml, "redemptions", callback
+
+	_parseCouponXml: (xml, callback) ->
+		RecurlyWrapper._parseXmlAndGetAttribute xml, "coupon", callback
+
 	_parseXmlAndGetAttribute: (xml, attribute, callback) ->
 		RecurlyWrapper._parseXml xml, (error, data) ->
 			return callback(error) if error?
@@ -505,6 +532,7 @@ module.exports = RecurlyWrapper =
 		parser = new xml2js.Parser(
 			explicitRoot : true
 			explicitArray : false
+			emptyTag: ''
 		)
 		parser.parseString xml, (error, data) ->
 			return callback(error) if error?

+ 1 - 1
services/web/app/coffee/Features/Subscription/SubscriptionController.coffee

@@ -150,7 +150,7 @@ module.exports = SubscriptionController =
 				return res.redirect '/user/subscription/plans'
 			res.render "subscriptions/successful_subscription",
 				title: "thank_you"
-				subscription:personalSubscription
+				personalSubscription: personalSubscription
 
 	cancelSubscription: (req, res, next) ->
 		user = AuthenticationController.getSessionUser(req)

+ 8 - 0
services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee

@@ -34,6 +34,11 @@ module.exports =
 					return cb(null, null) 
 				RecurlyWrapper.getSubscription personalSubscription.recurlySubscription_id, includeAccount: true, cb
 			]
+			recurlyCoupons: ['recurlySubscription', (cb, {recurlySubscription}) ->
+				return cb(null, null) if !recurlySubscription
+				accountId = recurlySubscription.account.account_code
+				RecurlyWrapper.getAccountActiveCoupons accountId, cb
+			]
 			plan: ['personalSubscription', (cb, {personalSubscription}) ->
 				return cb() if !personalSubscription?
 				plan = PlansLocator.findLocalPlanInSettings(personalSubscription.planCode)
@@ -65,6 +70,7 @@ module.exports =
 				managedPublishers,
 				v1SubscriptionStatus,
 				recurlySubscription,
+				recurlyCoupons,
 				plan
 			} = results
 			memberGroupSubscriptions ?= []
@@ -72,6 +78,7 @@ module.exports =
 			confirmedMemberInstitutions ?= []
 			managedInstitutions ?= []
 			v1SubscriptionStatus ?= {}
+			recurlyCoupons ?= []
 
 
 			if personalSubscription?.toObject?
@@ -93,6 +100,7 @@ module.exports =
 					state: recurlySubscription.state
 					trialEndsAtFormatted: SubscriptionFormatters.formatDate(recurlySubscription?.trial_ends_at)
 					trial_ends_at: recurlySubscription.trial_ends_at
+					activeCoupons: recurlyCoupons
 				}
 
 			for memberGroupSubscription in memberGroupSubscriptions

+ 9 - 0
services/web/app/views/subscriptions/_price_exceptions.pug

@@ -0,0 +1,9 @@
+p
+	i * !{translate("subject_to_additional_vat")}
+
+if (personalSubscription.recurly.activeCoupons.length > 0)
+	i * !{translate("coupons_not_included")}:
+	ul
+		each coupon in personalSubscription.recurly.activeCoupons
+			li
+				i= coupon.description || coupon.name

+ 1 - 0
services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug

@@ -13,6 +13,7 @@ div(ng-controller="RecurlySubscriptionController")
 				-if (personalSubscription.recurly.trialEndsAtFormatted && personalSubscription.recurly.trial_ends_at > Date.now())
 					p You're on a free trial which ends on <strong ng-non-bindable>#{personalSubscription.recurly.trialEndsAtFormatted}</strong>
 				p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>" + personalSubscription.recurly.price + "</strong>", collectionDate:"<strong>" + personalSubscription.recurly.nextPaymentDueAt + "</strong>"})}
+				include ./../_price_exceptions
 				p.pull-right
 				p
 					a(href=personalSubscription.recurly.billingDetailsLink, target="_blank").btn.btn-info #{translate("update_your_billing_details")}

+ 4 - 3
services/web/app/views/subscriptions/successful_subscription.pug

@@ -9,14 +9,15 @@ block content
 						.page-header
 							h2 #{translate("thanks_for_subscribing")}
 						.alert.alert-success
-							p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>"+subscription.recurly.price+"</strong>", collectionDate:"<strong>"+subscription.recurly.nextPaymentDueAt+"</strong>"})}
+							p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>"+personalSubscription.recurly.price+"</strong>", collectionDate:"<strong>"+personalSubscription.recurly.nextPaymentDueAt+"</strong>"})}
+							include ./_price_exceptions
 							p #{translate("to_modify_your_subscription_go_to")} 
 								a(href="/user/subscription")  #{translate("manage_subscription")}.
 						p
-							- if (subscription.groupPlan == true)
+							- if (personalSubscription.groupPlan == true)
 								a.btn.btn-success.btn-large(href="/subscription/group") #{translate("add_your_first_group_member_now")}
 						p.letter-from-founders
-							p #{translate("thanks_for_subscribing_you_help_sl", {planName:subscription.plan.name})}
+							p #{translate("thanks_for_subscribing_you_help_sl", {planName:personalSubscription.plan.name})}
 							p #{translate("need_anything_contact_us_at")} 
 								a(href=`mailto:${settings.adminEmail}`, ng-non-bindable) #{settings.adminEmail}
 								| .

+ 32 - 0
services/web/test/acceptance/coffee/SubscriptionTests.coffee

@@ -42,6 +42,11 @@ describe 'Subscriptions', ->
 					account_id: 'mock-account-id',
 					trial_ends_at: new Date(2018, 6, 7)
 				}
+				MockRecurlyApi.coupons = @coupons = {
+					'test-coupon-1': { description: 'Test Coupon 1' }
+					'test-coupon-2': { description: 'Test Coupon 2' }
+					'test-coupon-3': { name: 'TestCoupon3' }
+				}
 				Subscription.create {
 					admin_id: @user._id,
 					manager_ids: [@user._id],
@@ -57,6 +62,8 @@ describe 'Subscriptions', ->
 			after (done) ->
 				MockRecurlyApi.accounts = {}
 				MockRecurlyApi.subscriptions = {}
+				MockRecurlyApi.coupons = {}
+				MockRecurlyApi.redemptions = {}
 				Subscription.remove {
 					admin_id: @user._id
 				}, done
@@ -68,6 +75,7 @@ describe 'Subscriptions', ->
 				expect(subscription.planCode).to.equal 'collaborator'
 				expect(subscription.recurly).to.exist
 				expect(subscription.recurly).to.deep.equal {
+					"activeCoupons": []
 					"billingDetailsLink": "https://test.recurly.com/account/billing_info/edit?ht=mock-login-token"
 					"currency": "GBP"
 					"nextPaymentDueAt": "5th May 2018"
@@ -82,6 +90,30 @@ describe 'Subscriptions', ->
 			it 'should return no memberGroupSubscriptions', ->
 				expect(@data.memberGroupSubscriptions).to.deep.equal []
 
+			it 'should include redeemed coupons', (done) ->
+				MockRecurlyApi.redemptions['mock-account-id'] = [
+					{ state: 'active', coupon_code: 'test-coupon-1' }
+					{ state: 'inactive', coupon_code: 'test-coupon-2' }
+					{ state: 'active', coupon_code: 'test-coupon-3' }
+				]
+
+				# rebuild the view model with the redemptions
+				SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel @user, (error, data) ->
+					expect(error).to.not.exist
+					expect(data.personalSubscription.recurly.activeCoupons).to.deep.equal [
+						{
+							coupon_code: 'test-coupon-1',
+							name: '',
+							description: 'Test Coupon 1'
+						}
+						{
+							coupon_code: 'test-coupon-3',
+							name: 'TestCoupon3',
+							description: ''
+						}
+					]
+					done()
+
 		describe 'when the user has a subscription without recurly', ->
 			before (done) ->
 				Subscription.create {

+ 35 - 0
services/web/test/acceptance/coffee/helpers/MockRecurlyApi.coffee

@@ -9,6 +9,10 @@ module.exports = MockRecurlyApi =
 
 	accounts: {}
 
+	redemptions: {}
+
+	coupons: {}
+
 	run: () ->
 		app.get '/subscriptions/:id', (req, res, next) =>
 			subscription = @subscriptions[req.params.id]
@@ -36,10 +40,41 @@ module.exports = MockRecurlyApi =
 			else
 				res.send """
 				<account>
+					<account_code>#{req.params.id}</account_code>
 					<hosted_login_token>#{account.hosted_login_token}</hosted_login_token>
 				</account>
 				"""
 
+		app.get '/coupons/:code', (req, res, next) =>
+			coupon = @coupons[req.params.code]
+			if !coupon?
+				res.status(404).end()
+			else
+				res.send """
+				<coupon>
+					<coupon_code>#{req.params.code}</coupon_code>
+					<name>#{coupon.name or ''}</name>
+					<description>#{coupon.description or ''}</description>
+				</coupon>
+				"""
+
+		app.get '/accounts/:id/redemptions', (req, res, next) =>
+			redemptions = @redemptions[req.params.id] or []
+			redemptionsListXml = ''
+			for redemption in redemptions
+				redemptionsListXml += """
+				<redemption>
+					<state>#{redemption.state}</state>
+					<coupon_code>#{redemption.coupon_code}</coupon_code>
+				</redemption>
+				"""
+
+			res.send """
+			<redemptions type="array">
+				#{redemptionsListXml}
+			</redemptions>
+			"""
+
 		app.listen 6034, (error) ->
 			throw error if error?