From 85e7f688d56a0e9d81464b6666ca045874d49220 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Alby?= Date: Tue, 23 Apr 2019 10:21:00 -0400 Subject: [PATCH] Display Pricing Exceptions on Subscription Dashboard (#1720) Display Pricing Exceptions on Subscription Dashboard GitOrigin-RevId: 31de89824db70b7af1f8704e6da592064ce44bfd --- .../Subscription/RecurlyWrapper.coffee | 28 +++++++++++++++ .../SubscriptionController.coffee | 2 +- .../SubscriptionViewModelBuilder.coffee | 8 +++++ .../views/subscriptions/_price_exceptions.pug | 9 +++++ .../_personal_subscription_recurly.pug | 1 + .../subscriptions/successful_subscription.pug | 7 ++-- .../coffee/SubscriptionTests.coffee | 32 +++++++++++++++++ .../coffee/helpers/MockRecurlyApi.coffee | 35 +++++++++++++++++++ 8 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 services/web/app/views/subscriptions/_price_exceptions.pug diff --git a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee b/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee index 2e47ea597d..0a179cd81f 100644 --- a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee +++ b/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? diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index c393b0c4a4..992f051806 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/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) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee index 2f590bd2df..2c55a1ee8e 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionViewModelBuilder.coffee +++ b/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 diff --git a/services/web/app/views/subscriptions/_price_exceptions.pug b/services/web/app/views/subscriptions/_price_exceptions.pug new file mode 100644 index 0000000000..be0018db61 --- /dev/null +++ b/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 diff --git a/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug b/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug index f376bb59bc..645a2e037a 100644 --- a/services/web/app/views/subscriptions/dashboard/_personal_subscription_recurly.pug +++ b/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 #{personalSubscription.recurly.trialEndsAtFormatted} p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"" + personalSubscription.recurly.price + "", collectionDate:"" + personalSubscription.recurly.nextPaymentDueAt + ""})} + include ./../_price_exceptions p.pull-right p a(href=personalSubscription.recurly.billingDetailsLink, target="_blank").btn.btn-info #{translate("update_your_billing_details")} diff --git a/services/web/app/views/subscriptions/successful_subscription.pug b/services/web/app/views/subscriptions/successful_subscription.pug index f8515007ab..ac21595e28 100644 --- a/services/web/app/views/subscriptions/successful_subscription.pug +++ b/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:""+subscription.recurly.price+"", collectionDate:""+subscription.recurly.nextPaymentDueAt+""})} + p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:""+personalSubscription.recurly.price+"", collectionDate:""+personalSubscription.recurly.nextPaymentDueAt+""})} + 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} | . diff --git a/services/web/test/acceptance/coffee/SubscriptionTests.coffee b/services/web/test/acceptance/coffee/SubscriptionTests.coffee index 8539084536..011af90f35 100644 --- a/services/web/test/acceptance/coffee/SubscriptionTests.coffee +++ b/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 { diff --git a/services/web/test/acceptance/coffee/helpers/MockRecurlyApi.coffee b/services/web/test/acceptance/coffee/helpers/MockRecurlyApi.coffee index 59ed6ee362..23692c6ac4 100644 --- a/services/web/test/acceptance/coffee/helpers/MockRecurlyApi.coffee +++ b/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 """ + #{req.params.id} #{account.hosted_login_token} """ + app.get '/coupons/:code', (req, res, next) => + coupon = @coupons[req.params.code] + if !coupon? + res.status(404).end() + else + res.send """ + + #{req.params.code} + #{coupon.name or ''} + #{coupon.description or ''} + + """ + + app.get '/accounts/:id/redemptions', (req, res, next) => + redemptions = @redemptions[req.params.id] or [] + redemptionsListXml = '' + for redemption in redemptions + redemptionsListXml += """ + + #{redemption.state} + #{redemption.coupon_code} + + """ + + res.send """ + + #{redemptionsListXml} + + """ + app.listen 6034, (error) -> throw error if error?