From 5dcdabfd62752aa26fe2a6877ed1c6c49cb024d3 Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 2 Dec 2014 11:47:15 -0800 Subject: [PATCH 1/3] Refactored stripeCustomerID to stripe object in the user object so we can put more stuff in there. --- app/schemas/models/user.coffee | 8 +++++++- server/payments/payment_handler.coffee | 14 ++++++++------ server/users/User.coffee | 2 +- test/server/functional/payment.spec.coffee | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee index 1067744e9..6ad09f013 100644 --- a/app/schemas/models/user.coffee +++ b/app/schemas/models/user.coffee @@ -271,7 +271,13 @@ _.extend UserSchema.properties, earned: c.RewardSchema 'earned by achievements' purchased: c.RewardSchema 'purchased with gems or money' spent: {type: 'number'} - stripeCustomerID: { type: 'string' } + stripeCustomerID: { type: 'string' } # TODO: Migrate away from this property + + stripe: c.object {}, { + customerID: { type: 'string' } + subscription: { enum: ['basic'] } + token: { type: 'string' } + } c.extendBasicProperties UserSchema, 'user' diff --git a/server/payments/payment_handler.coffee b/server/payments/payment_handler.coffee index fcf423445..84a500366 100644 --- a/server/payments/payment_handler.coffee +++ b/server/payments/payment_handler.coffee @@ -71,7 +71,7 @@ PaymentHandler = class PaymentHandler extends Handler @logPaymentError(req, 'Missing stripe productID') return @sendBadInputError(res, 'Need productID if paying with Stripe.') - if stripeTimestamp and (not stripeToken) and (not user.get('stripeCustomerID')) + if stripeTimestamp and (not stripeToken) and (not req.user.get('stripe')?.customerID) @logPaymentError(req, 'Missing stripe token') return @sendBadInputError(res, 'Need stripe.token if new customer.') @@ -160,12 +160,14 @@ PaymentHandler = class PaymentHandler extends Handler handleStripePaymentPost: (req, res, timestamp, productID, token) -> # First, make sure we save the payment info as a Customer object, if we haven't already. - if not req.user.get('stripeCustomerID') + if not req.user.get('stripe')?.customerID stripe.customers.create({ card: token description: req.user._id + '' }).then(((customer) => - req.user.set('stripeCustomerID', customer.id) + stripeInfo = _.cloneDeep(req.user.get('stripe') ? {}) + stripeInfo.customerID = customer.id + req.user.set('stripe', stripeInfo) req.user.save((err) => if err @logPaymentError(req, 'Stripe customer id save db error. '+err) @@ -193,7 +195,7 @@ PaymentHandler = class PaymentHandler extends Handler ) ), ((callback) -> - stripe.charges.list({customer: req.user.get('stripeCustomerID')}, (err, recentCharges) => + stripe.charges.list({customer: req.user.get('stripe')?.customerID}, (err, recentCharges) => return callback(err) if err charge = _.find recentCharges.data, (c) -> c.metadata.timestamp is timestamp callback(null, charge) @@ -232,7 +234,7 @@ PaymentHandler = class PaymentHandler extends Handler stripe.charges.create({ amount: product.amount currency: 'usd' - customer: req.user.get('stripeCustomerID') + customer: req.user.get('stripe')?.customerID metadata: { productID: product.id userID: req.user._id + '' @@ -262,7 +264,7 @@ PaymentHandler = class PaymentHandler extends Handler payment.set 'amount', product.amount payment.set 'gems', product.gems payment.set 'stripe', { - customerID: req.user.get('stripeCustomerID') + customerID: req.user.get('stripe')?.customerID timestamp: parseInt(req.body.stripe.timestamp) chargeID: charge.id } diff --git a/server/users/User.coffee b/server/users/User.coffee index 505d2375a..e289d64f0 100644 --- a/server/users/User.coffee +++ b/server/users/User.coffee @@ -192,7 +192,7 @@ UserSchema.statics.hashPassword = (password) -> UserSchema.statics.privateProperties = [ 'permissions', 'email', 'mailChimp', 'firstName', 'lastName', 'gender', 'facebookID', 'gplusID', 'music', 'volume', 'aceConfig', 'employerAt', 'signedEmployerAgreement', - 'emailSubscriptions', 'emails', 'activity', 'stripeCustomerID' + 'emailSubscriptions', 'emails', 'activity', 'stripe', 'stripeCustomerID' ] UserSchema.statics.jsonSchema = jsonschema UserSchema.statics.editableProperties = [ diff --git a/test/server/functional/payment.spec.coffee b/test/server/functional/payment.spec.coffee index ec316a621..34961b690 100644 --- a/test/server/functional/payment.spec.coffee +++ b/test/server/functional/payment.spec.coffee @@ -109,7 +109,7 @@ describe '/db/payment', -> expect(body.purchaser).toBe(joeID) User.findById(joe.get('_id'), (err, user) -> expect(user.get('purchased').gems).toBe(5000) - expect(user.get('stripeCustomerID')).toBe(body.stripe.customerID) + expect(user.get('stripe').customerID).toBe(body.stripe.customerID) done() ) ) From 3675c79b26ec129e8aefe46cde6c361e22fb3d8d Mon Sep 17 00:00:00 2001 From: Scott Erickson Date: Tue, 2 Dec 2014 13:19:05 -0800 Subject: [PATCH 2/3] Hotfix for #1828. Still doesn't figure out why user preferredLanguage properties are being set to null. --- app/core/ModuleLoader.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/ModuleLoader.coffee b/app/core/ModuleLoader.coffee index 5d8483a3e..707e42528 100644 --- a/app/core/ModuleLoader.coffee +++ b/app/core/ModuleLoader.coffee @@ -51,7 +51,7 @@ module.exports = ModuleLoader = class ModuleLoader extends CocoClass }) return true - loadLanguage: (langCode) -> + loadLanguage: (langCode='en-US') -> loading = @load("locale/#{langCode}") firstBit = langCode[...2] return loading if firstBit is langCode From 774260769d8863662100c2ada9df3ee2d2a81014 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Tue, 2 Dec 2014 13:25:12 -0800 Subject: [PATCH 3/3] Update pageview tracking Based on more feedback from segment.io support. Still trying to get Google Analytics virtual pageviews to work correctly. --- app/core/Tracker.coffee | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/app/core/Tracker.coffee b/app/core/Tracker.coffee index f8a00467b..c0db96c69 100644 --- a/app/core/Tracker.coffee +++ b/app/core/Tracker.coffee @@ -19,21 +19,37 @@ module.exports = class Tracker traits[userTrait] ?= me.get(userTrait) analytics.identify me.id, traits - trackPageView: (name=null, includeIntegrations=null) -> + trackPageView: (virtualName=null, includeIntegrations=null) -> + # console.log 'trackPageView', virtualName, includeIntegrations # Google Analytics does not support event-based funnels, so we have to use virtual pageviews instead # https://support.google.com/analytics/answer/1032720?hl=en - # https://segment.com/docs/libraries/analytics.js/#page - unless name? - name = Backbone.history.getFragment() - console.log "Would track analytics pageview: /#{name}" if debugAnalytics - return unless @isProduction and analytics? and not me.isAdmin() - if includeIntegrations + name = virtualName ? Backbone.history.getFragment() + + properties = {} + if virtualName? + # Override title and path properties for virtual page view + # https://segment.com/docs/libraries/analytics.js/#page + properties = + title: name + path: "/#{name}" + + options = {} + if includeIntegrations? options = integrations: {'All': false} for integration in includeIntegrations options.integrations[integration] = true - analytics.page "/#{name}", {}, options - else + + console.log "Would track analytics pageview: '/#{name}'", properties, options if debugAnalytics + return unless @isProduction and analytics? and not me.isAdmin() + + # Ok to pass empty properties, but maybe not options + # TODO: What happens when we pass empty options? + if _.isEmpty options + # console.log "trackPageView without options '/#{name}'", properties, options analytics.page "/#{name}" + else + # console.log "trackPageView with options '/#{name}'", properties, options + analytics.page "/#{name}", properties, options trackEvent: (action, properties, includeIntegrations=null) => # 'action' is a string