From 21a4b778eee6e0ae1aeb9077b79e9cdd04c7d4bb Mon Sep 17 00:00:00 2001 From: thejsj Date: Sun, 21 Aug 2016 20:56:01 -0700 Subject: [PATCH 1/2] Add functionality to add discounts to customers --- lib/http/routes/organization.js | 15 ++++++++++++-- lib/services/discount-service.js | 35 ++++++++++++++++++++++++++++++++ lib/util/stripe.js | 20 ++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 lib/services/discount-service.js diff --git a/lib/http/routes/organization.js b/lib/http/routes/organization.js index dc70b18..312d172 100644 --- a/lib/http/routes/organization.js +++ b/lib/http/routes/organization.js @@ -10,6 +10,7 @@ const logger = require('util/logger').child({ module: 'OrganizationRouter' }) const stripe = require('util/stripe') const util = require('util/index') const bigPoppa = require('util/big-poppa') +const DiscountService = require('services/discount-service') const _ = require('lodash') const BaseRouter = require('http/routes/base') @@ -213,7 +214,7 @@ class OrganizationRouter extends BaseRouter { const log = logger.child({ validatedReq: validatedReq }) log.info('postPaymentMethod called') return bigPoppa.getOrganization(validatedReq.params.id) - .then(function updatePlanForOrg (org) { + .tap(function updatePlanForOrg (org) { log.trace({ org: org }, 'Organization fetched') let user = org.users.find(user => user.id === validatedReq.body.user.id) if (!user) { @@ -221,11 +222,21 @@ class OrganizationRouter extends BaseRouter { } return stripe.updatePaymentMethodForOrganization(org, validatedReq.body.stripeToken, user) }) - .then(function updateHasPaymentMethod () { + .tap(function updateHasPaymentMethod () { return bigPoppa.updateOrganization(validatedReq.params.id, { hasPaymentMethod: true }) }) + .tap(function addDiscountToOrganization (org) { + /** + * These is currently a very simple implementation for discounts. In + * the future this should be much more robust but this will do for now + */ + const discount = DiscountService.getDiscountAtPaymentMethodTime(org) + if (discount) { + return stripe.updateDiscountForCustomer(org.stripeCustomerId, discount) + } + }) .then(() => { return res.status(201).send('Successfully updated') }) diff --git a/lib/services/discount-service.js b/lib/services/discount-service.js new file mode 100644 index 0000000..876f9e0 --- /dev/null +++ b/lib/services/discount-service.js @@ -0,0 +1,35 @@ +'use strict' + +const moment = require('moment') + +const PREVIEW_DISCOUNT_SIGNUP_DEADLINE = moment('2016-08-24T07:00:00.000Z') // Wednesday, August 24th 2016, 12:00:00 am PST +const PREVIEW_DISCOUNT_PAYMENT_METHOD_DEADLINE = moment('2016-09-10T07:00:00.000Z') // Saturday, September 10th 2016, 12:00:00 am PST +const BETA_DISCOUNT_SIGNUP_DEADLINE = moment('2016-09-21T07:00:00.000Z') // Wednesday, September 21st 2016, 12:00:00 am PST + +const PREVIEW_PLAN_ID = 'Preview' +const BETA_PLAN_ID = 'Beta' + +module.exports = class DiscountService { + + static getDiscountAtPaymentMethodTime (org) { + const orgCreatedTime = moment(org.created) + const paymentMethodCreatedTime = moment() + const trialEndTime = moment(org.trialEnd) + // Preview + if ( + orgCreatedTime.isBefore(PREVIEW_DISCOUNT_SIGNUP_DEADLINE) && + paymentMethodCreatedTime.isBefore(PREVIEW_DISCOUNT_PAYMENT_METHOD_DEADLINE) + ) { + return PREVIEW_PLAN_ID + } + // Beta + if ( + orgCreatedTime.isBefore(BETA_DISCOUNT_SIGNUP_DEADLINE) && + paymentMethodCreatedTime.isBefore(trialEndTime) // Trial should not be over + ) { + return BETA_PLAN_ID + } + return null + } + +} diff --git a/lib/util/stripe.js b/lib/util/stripe.js index 0510f6b..00e59f5 100644 --- a/lib/util/stripe.js +++ b/lib/util/stripe.js @@ -148,6 +148,26 @@ module.exports = class Stripe { }) } + /** + * Update discount for organiation + * + * @param {String} stripeCustomerId - Stripe Organization ID + * @param {String} discountId - Stripe Discount ID + * @returns {Promise} + */ + static updateDiscountForCustomer (orgStripeCustomerId, discountId) { + const log = logger.child({ orgStripeCustomerId: orgStripeCustomerId, discountId: discountId }) + log.info('Stripe.updateDiscountForCustomer called') + let updates = { + coupon: discountId, + metadata: { + discountAdded: (new Date()).toISOString() + } + } + log.trace({ updates: updates }, 'Update Stripe customer') + return stripeClient.customers.update(orgStripeCustomerId, updates) + } + /** * Add payment-method owner to the invoice. This facilitates knowing what * user actually paid the invoice From 1d8f3cf38cb15c14e4c55929678f979779b39b30 Mon Sep 17 00:00:00 2001 From: thejsj Date: Sun, 21 Aug 2016 21:00:18 -0700 Subject: [PATCH 2/2] Fix unit tests --- test/unit/http/routes/organization.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/unit/http/routes/organization.js b/test/unit/http/routes/organization.js index f060b65..b8bd12e 100644 --- a/test/unit/http/routes/organization.js +++ b/test/unit/http/routes/organization.js @@ -396,6 +396,7 @@ describe('HTTP /organization', () => { let getOrganizationStub let updatePaymentMethodForOrganizationStub let updateOrganizationStub + let updateDiscountForCustomerStub let org = Object.assign({}, OrganizationWithStripeCustomerIdFixture) let orgId = org.id let user = org.users[0] @@ -416,11 +417,13 @@ describe('HTTP /organization', () => { getOrganizationStub = sinon.stub(bigPoppa, 'getOrganization').resolves(org) updateOrganizationStub = sinon.stub(bigPoppa, 'updateOrganization').resolves(org) updatePaymentMethodForOrganizationStub = sinon.stub(stripe, 'updatePaymentMethodForOrganization').resolves() + updateDiscountForCustomerStub = sinon.stub(stripe, 'updateDiscountForCustomer').resolves() }) afterEach('Restore stub', () => { getOrganizationStub.restore() updateOrganizationStub.restore() updatePaymentMethodForOrganizationStub.restore() + updateDiscountForCustomerStub.restore() }) it('should call `getOrganization`', () => {