Move product information to the db

This commit is contained in:
Scott Erickson 2015-12-14 11:10:37 -08:00
parent ae1621ea09
commit 7c516c4d9f
19 changed files with 334 additions and 261 deletions

View file

@ -0,0 +1,8 @@
CocoCollection = require './CocoCollection'
Product = require 'models/Product'
module.exports = class Products extends CocoCollection
model: Product
url: '/db/products'
getByName: (name) -> @findWhere { name: name }

View file

@ -244,7 +244,7 @@ module.exports.getCoursePraise = getCoursePraise = ->
] ]
praise[_.random(0, praise.length - 1)] praise[_.random(0, praise.length - 1)]
module.exports.getPrepaidCodeAmount = getPrepaidCodeAmount = (price=999, users=0, months=0) -> module.exports.getPrepaidCodeAmount = getPrepaidCodeAmount = (price=0, users=0, months=0) ->
return 0 unless users > 0 and months > 0 return 0 unless users > 0 and months > 0
total = price * users * months total = price * users * months
total total

View file

@ -0,0 +1,6 @@
CocoModel = require './CocoModel'
module.exports = class ProductModel extends CocoModel
@className: 'Product'
@schema: require 'schemas/models/product.schema'
urlRoot: '/db/products'

View file

@ -0,0 +1,13 @@
c = require './../schemas'
module.exports = ProductSchema = {
type: 'object'
additionalProperties: false
properties: {
name: { type: 'string' }
amount: { type: 'integer', description: 'Cost in cents' }
gems: { type: 'integer', description: 'Number of gems awarded' }
}
}
c.extendBasicProperties ProductSchema, 'Product'

View file

@ -39,7 +39,7 @@ block content
label.control-label.col-md-2(data-i18n="account_prepaid.purchase_total") label.control-label.col-md-2(data-i18n="account_prepaid.purchase_total")
.col-md-10 .col-md-10
p.form-control-static $ p.form-control-static $
span#total #{purchase.total.toFixed(2)} span#total #{(purchase.total/100).toFixed(2)}
button#purchase-btn.btn.btn-success.pull-right(data-i18n="account_prepaid.purchase_button") button#purchase-btn.btn.btn-success.pull-right(data-i18n="account_prepaid.purchase_button")
.row .row
.col-md-12 .col-md-12
@ -66,7 +66,7 @@ block content
button#redeem-code-btn.btn.btn-success(data-i18n="account_prepaid.apply_account") button#redeem-code-btn.btn.btn-success(data-i18n="account_prepaid.apply_account")
.row .row
.col-md-12 .col-md-12
.panel.panel-default #codes-panel.panel.panel-default
.panel-heading .panel-heading
.panel-title .panel-title
a(data-toggle="collapse" href="#codeslist") a(data-toggle="collapse" href="#codeslist")

View file

@ -24,8 +24,11 @@
th.free-cell(data-i18n="subscribe.free") th.free-cell(data-i18n="subscribe.free")
th th
//- TODO: find a better way to localize '$9.99/month' //- TODO: find a better way to localize '$9.99/month'
span $#{(view.product.amount / 100)}/ if view.basicProduct
span(data-i18n="subscribe.month") span $#{(view.basicProduct.get('amount') / 100)}/
span(data-i18n="subscribe.month")
else
span '...'
tbody tbody
tr tr
td.feature-description td.feature-description

View file

@ -82,7 +82,7 @@ block content
span(data-i18n="account_prepaid.purchase_total") span(data-i18n="account_prepaid.purchase_total")
span.spr : #{view.numberOfStudents} span.spr : #{view.numberOfStudents}
span(data-i18n="courses.enrollments") span(data-i18n="courses.enrollments")
span.spl x $#{view.pricePerStudent.toFixed(2)} = #{view.getPriceString()} span.spl x $#{(view.pricePerStudent/100).toFixed(2)} = #{view.getPriceString()}
p.text-center p.text-center
button#purchase-btn.btn.btn-lg.btn-success.uppercase(data-i18n="courses.purchase_now") button#purchase-btn.btn.btn-lg.btn-success.uppercase(data-i18n="courses.purchase_now")

View file

@ -1,10 +1,10 @@
.modal-dialog .modal-dialog
.modal-content .modal-content
if state === 'purchasing' if view.state === 'purchasing'
.alert.alert-info(data-i18n="buy_gems.purchasing") .alert.alert-info(data-i18n="buy_gems.purchasing")
else if state === 'retrying' else if view.state === 'retrying'
#retrying-alert.alert.alert-danger(data-i18n="buy_gems.retrying") #retrying-alert.alert.alert-danger(data-i18n="buy_gems.retrying")
else else
@ -12,12 +12,12 @@
h1(data-i18n="play.buy_gems") h1(data-i18n="play.buy_gems")
#products #products
for product in products for product in view.products.models
.product .product
h4 x#{product.gems} h4 x#{product.get('gems')}
h3(data-i18n=product.i18n) h3(data-i18n=product.get('i18n'))
button.btn.btn-illustrated.btn-lg(value=product.id) button.btn.btn-illustrated.btn-lg(value=product.get('name'))
span= product.price span= product.get('priceString')
.product .product
h4(data-i18n="buy_gems.price") x3500 / mo h4(data-i18n="buy_gems.price") x3500 / mo
@ -29,20 +29,20 @@
else else
button.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success(data-i18n="subscribe.subscribe_title") Subscribe button.start-subscription-button.btn.btn-lg.btn-illustrated.btn-success(data-i18n="subscribe.subscribe_title") Subscribe
if state === 'declined' if view.state === 'declined'
#declined-alert.alert.alert-danger.alert-dismissible #declined-alert.alert.alert-danger.alert-dismissible
span(data-i18n="buy_gems.declined") span(data-i18n="buy_gems.declined")
button.close(type="button" data-dismiss="alert") button.close(type="button" data-dismiss="alert")
span(aria-hidden="true") × span(aria-hidden="true") ×
if state === 'unknown_error' if view.state === 'unknown_error'
#error-alert.alert.alert-danger.alert-dismissible #error-alert.alert.alert-danger.alert-dismissible
button.close(type="button" data-dismiss="alert") button.close(type="button" data-dismiss="alert")
span(aria-hidden="true") × span(aria-hidden="true") ×
p(data-i18n="loading_error.unknown") p(data-i18n="loading_error.unknown")
p= stateMessage p= view.stateMessage
if state === 'recovered_charge' if view.state === 'recovered_charge'
#recovered-alert.alert.alert-danger.alert-dismissible #recovered-alert.alert.alert-danger.alert-dismissible
span(data-i18n="buy_gems.recovered") span(data-i18n="buy_gems.recovered")
button.close(type="button" data-dismiss="alert") button.close(type="button" data-dismiss="alert")

View file

@ -7,6 +7,7 @@ Prepaid = require '../../models/Prepaid'
utils = require 'core/utils' utils = require 'core/utils'
RedeemModal = require 'views/account/PrepaidRedeemModal' RedeemModal = require 'views/account/PrepaidRedeemModal'
forms = require 'core/forms' forms = require 'core/forms'
Products = require 'collections/Products'
# TODO: remove redeem code modal # TODO: remove redeem code modal
@ -26,12 +27,10 @@ module.exports = class PrepaidView extends RootView
subscriptions: subscriptions:
'stripe:received-token': 'onStripeReceivedToken' 'stripe:received-token': 'onStripeReceivedToken'
baseAmount: 9.99
constructor: (options) -> constructor: (options) ->
super(options) super(options)
@purchase = @purchase =
total: @baseAmount total: 0
users: 3 users: 3
months: 3 months: 3
@updateTotal() @updateTotal()
@ -45,6 +44,14 @@ module.exports = class PrepaidView extends RootView
@ppcQuery = true @ppcQuery = true
@loadPrepaid(@ppc) @loadPrepaid(@ppc)
@products = new Products()
@supermodel.loadCollection(@products, 'products')
onLoaded: ->
@prepaidProduct = @products.findWhere { name: 'prepaid_subscription' }
@updateTotal()
super()
getRenderData: -> getRenderData: ->
c = super() c = super()
c.purchase = @purchase c.purchase = @purchase
@ -62,7 +69,8 @@ module.exports = class PrepaidView extends RootView
noty text: message, layout: 'topCenter', type: type, killer: false, timeout: 5000, dismissQueue: true, maxVisible: 3 noty text: message, layout: 'topCenter', type: type, killer: false, timeout: 5000, dismissQueue: true, maxVisible: 3
updateTotal: -> updateTotal: ->
@purchase.total = getPrepaidCodeAmount(@baseAmount, @purchase.users, @purchase.months) return unless @prepaidProduct
@purchase.total = getPrepaidCodeAmount(@prepaidProduct.get('amount'), @purchase.users, @purchase.months)
@renderSelectors("#total", "#users-input", "#months-input") @renderSelectors("#total", "#users-input", "#months-input")
# Form Input Callbacks # Form Input Callbacks
@ -99,7 +107,7 @@ module.exports = class PrepaidView extends RootView
onClickPurchaseButton: (e) -> onClickPurchaseButton: (e) ->
return unless $("#users-input").val() >= 3 or $("#months-input").val() >= 3 return unless $("#users-input").val() >= 3 or $("#months-input").val() >= 3
@purchaseTimestamp = new Date().getTime() @purchaseTimestamp = new Date().getTime()
@stripeAmount = @purchase.total * 100 @stripeAmount = @purchase.total
@description = "Prepaid Code for " + @purchase.users + " users / " + @purchase.months + " months" @description = "Prepaid Code for " + @purchase.users + " users / " + @purchase.months + " months"
stripeHandler.open stripeHandler.open
@ -179,6 +187,7 @@ module.exports = class PrepaidView extends RootView
# console.log 'SUCCESS: Prepaid purchase', model.code # console.log 'SUCCESS: Prepaid purchase', model.code
@statusMessage "Successfully purchased Prepaid Code!", "success" @statusMessage "Successfully purchased Prepaid Code!", "success"
@codes.add(model) @codes.add(model)
@renderSelectors('#codes-panel')
@statusMessage "Finalizing purchase...", "information" @statusMessage "Finalizing purchase...", "information"
@supermodel.addRequestResource('purchase_prepaid', options, 0).load() @supermodel.addRequestResource('purchase_prepaid', options, 0).load()

View file

@ -3,16 +3,14 @@ template = require 'templates/core/subscribe-modal'
stripeHandler = require 'core/services/stripe' stripeHandler = require 'core/services/stripe'
utils = require 'core/utils' utils = require 'core/utils'
AuthModal = require 'views/core/AuthModal' AuthModal = require 'views/core/AuthModal'
Products = require 'collections/Products'
module.exports = class SubscribeModal extends ModalView module.exports = class SubscribeModal extends ModalView
id: 'subscribe-modal' id: 'subscribe-modal'
template: template template: template
plain: true plain: true
closesOnClickOutside: false closesOnClickOutside: false
product: planID: 'basic'
amount: 999
planID: 'basic'
yearAmount: 9900
subscriptions: subscriptions:
'stripe:received-token': 'onStripeReceivedToken' 'stripe:received-token': 'onStripeReceivedToken'
@ -27,6 +25,13 @@ module.exports = class SubscribeModal extends ModalView
constructor: (options) -> constructor: (options) ->
super(options) super(options)
@state = 'standby' @state = 'standby'
@products = new Products()
@supermodel.loadCollection(@products, 'products')
onLoaded: ->
@basicProduct = @products.findWhere { name: 'basic_subscription' }
@yearProduct = @products.findWhere { name: 'year_subscription' }
super()
afterRender: -> afterRender: ->
super() super()
@ -109,12 +114,13 @@ module.exports = class SubscribeModal extends ModalView
@$el.find('.parent-button').popover('hide') @$el.find('.parent-button').popover('hide')
onClickPurchaseButton: (e) -> onClickPurchaseButton: (e) ->
return unless @basicProduct and @yearProduct
@playSound 'menu-button-click' @playSound 'menu-button-click'
return @openModalView new AuthModal() if me.get('anonymous') return @openModalView new AuthModal() if me.get('anonymous')
application.tracker?.trackEvent 'Started subscription purchase' application.tracker?.trackEvent 'Started subscription purchase'
options = { options = {
description: $.i18n.t('subscribe.stripe_description') description: $.i18n.t('subscribe.stripe_description')
amount: @product.amount amount: @basicProduct.get('amount')
alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto' alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
alipayReusable: true alipayReusable: true
} }
@ -138,7 +144,7 @@ module.exports = class SubscribeModal extends ModalView
application.tracker?.trackEvent 'Started 1 year subscription purchase' application.tracker?.trackEvent 'Started 1 year subscription purchase'
options = options =
description: $.i18n.t('subscribe.stripe_description_year_sale') description: $.i18n.t('subscribe.stripe_description_year_sale')
amount: @product.yearAmount amount: @yearProduct.get('amount')
alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto' alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
alipayReusable: true alipayReusable: true
@purchasedAmount = options.amount @purchasedAmount = options.amount
@ -148,15 +154,15 @@ module.exports = class SubscribeModal extends ModalView
@state = 'purchasing' @state = 'purchasing'
@render() @render()
if @purchasedAmount is @product.amount if @purchasedAmount is @basicProduct.get('amount')
stripe = _.clone(me.get('stripe') ? {}) stripe = _.clone(me.get('stripe') ? {})
stripe.planID = @product.planID stripe.planID = @basicProduct.get('planID')
stripe.token = e.token.id stripe.token = e.token.id
me.set 'stripe', stripe me.set 'stripe', stripe
@listenToOnce me, 'sync', @onSubscriptionSuccess @listenToOnce me, 'sync', @onSubscriptionSuccess
@listenToOnce me, 'error', @onSubscriptionError @listenToOnce me, 'error', @onSubscriptionError
me.patch({headers: {'X-Change-Plan': 'true'}}) me.patch({headers: {'X-Change-Plan': 'true'}})
else if @purchasedAmount is @product.yearAmount else if @purchasedAmount is @yearProduct.get('amount')
# Purchasing a year # Purchasing a year
data = data =
stripe: stripe:

View file

@ -9,12 +9,13 @@ stripeHandler = require 'core/services/stripe'
template = require 'templates/courses/purchase-courses-view' template = require 'templates/courses/purchase-courses-view'
User = require 'models/User' User = require 'models/User'
utils = require 'core/utils' utils = require 'core/utils'
Products = require 'collections/Products'
module.exports = class PurchaseCoursesView extends RootView module.exports = class PurchaseCoursesView extends RootView
id: 'purchase-courses-view' id: 'purchase-courses-view'
template: template template: template
numberOfStudents: 30 numberOfStudents: 30
pricePerStudent: 4 pricePerStudent: 0
initialize: (options) -> initialize: (options) ->
@listenTo stripeHandler, 'received-token', @onStripeReceivedToken @listenTo stripeHandler, 'received-token', @onStripeReceivedToken
@ -29,13 +30,19 @@ module.exports = class PurchaseCoursesView extends RootView
@prepaids.comparator = '_id' @prepaids.comparator = '_id'
@prepaids.fetchByCreator(me.id) @prepaids.fetchByCreator(me.id)
@supermodel.loadCollection(@prepaids, 'prepaids') @supermodel.loadCollection(@prepaids, 'prepaids')
@products = new Products()
@supermodel.loadCollection(@products, 'products')
super(options) super(options)
events: events:
'input #students-input': 'onInputStudentsInput' 'input #students-input': 'onInputStudentsInput'
'click #purchase-btn': 'onClickPurchaseButton' 'click #purchase-btn': 'onClickPurchaseButton'
getPriceString: -> '$' + (@getPrice()).toFixed(2) onLoaded: ->
@pricePerStudent = @products.findWhere({name: 'course'}).get('amount')
super()
getPriceString: -> '$' + (@getPrice()/100).toFixed(2)
getPrice: -> @pricePerStudent * @numberOfStudents getPrice: -> @pricePerStudent * @numberOfStudents
onceClassroomsSync: -> onceClassroomsSync: ->
@ -80,7 +87,7 @@ module.exports = class PurchaseCoursesView extends RootView
application.tracker?.trackEvent 'Started course prepaid purchase', { application.tracker?.trackEvent 'Started course prepaid purchase', {
price: @pricePerStudent, students: @numberOfStudents} price: @pricePerStudent, students: @numberOfStudents}
stripeHandler.open stripeHandler.open
amount: @numberOfStudents * @pricePerStudent * 100 amount: @numberOfStudents * @pricePerStudent
description: "Full course access for #{@numberOfStudents} students" description: "Full course access for #{@numberOfStudents} students"
bitcoin: true bitcoin: true
alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto' alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'

View file

@ -3,6 +3,7 @@ template = require 'templates/play/modal/buy-gems-modal'
stripeHandler = require 'core/services/stripe' stripeHandler = require 'core/services/stripe'
utils = require 'core/utils' utils = require 'core/utils'
SubscribeModal = require 'views/core/SubscribeModal' SubscribeModal = require 'views/core/SubscribeModal'
Products = require 'collections/Products'
module.exports = class BuyGemsModal extends ModalView module.exports = class BuyGemsModal extends ModalView
id: 'buy-gems-modal' id: 'buy-gems-modal'
@ -29,22 +30,21 @@ module.exports = class BuyGemsModal extends ModalView
super(options) super(options)
@timestampForPurchase = new Date().getTime() @timestampForPurchase = new Date().getTime()
@state = 'standby' @state = 'standby'
@products = new Products()
@products.comparator = 'amount'
if application.isIPadApp if application.isIPadApp
@products = [] @products = []
Backbone.Mediator.publish 'buy-gems-modal:update-products' Backbone.Mediator.publish 'buy-gems-modal:update-products'
else else
@products = @originalProducts @supermodel.loadCollection(@products, 'products')
$.post '/db/payment/check-stripe-charges', (something, somethingElse, jqxhr) => $.post '/db/payment/check-stripe-charges', (something, somethingElse, jqxhr) =>
if jqxhr.status is 201 if jqxhr.status is 201
@state = 'recovered_charge' @state = 'recovered_charge'
@render() @render()
getRenderData: -> onLoaded: ->
c = super() @products.reset @products.filter (product) -> _.string.startsWith(product.get('name'), 'gems_')
c.products = @products super()
c.state = @state
c.stateMessage = @stateMessage
return c
afterRender: -> afterRender: ->
super() super()
@ -56,19 +56,20 @@ module.exports = class BuyGemsModal extends ModalView
@playSound 'game-menu-close' @playSound 'game-menu-close'
onIPadProducts: (e) -> onIPadProducts: (e) ->
newProducts = [] # TODO: Update to handle new products collection
for iapProduct in e.products # newProducts = []
localProduct = _.find @originalProducts, { id: iapProduct.id } # for iapProduct in e.products
continue unless localProduct # localProduct = _.find @originalProducts, { id: iapProduct.id }
localProduct.price = iapProduct.price # continue unless localProduct
newProducts.push localProduct # localProduct.price = iapProduct.price
@products = _.sortBy newProducts, 'gems' # newProducts.push localProduct
@render() # @products = _.sortBy newProducts, 'gems'
# @render()
onClickProductButton: (e) -> onClickProductButton: (e) ->
@playSound 'menu-button-click' @playSound 'menu-button-click'
productID = $(e.target).closest('button').val() productID = $(e.target).closest('button').val()
product = _.find @products, { id: productID } product = @products.findWhere { name: productID }
if application.isIPadApp if application.isIPadApp
Backbone.Mediator.publish 'buy-gems-modal:purchase-initiated', { productID: productID } Backbone.Mediator.publish 'buy-gems-modal:purchase-initiated', { productID: productID }
@ -76,8 +77,8 @@ module.exports = class BuyGemsModal extends ModalView
else else
application.tracker?.trackEvent 'Started gem purchase', { productID: productID } application.tracker?.trackEvent 'Started gem purchase', { productID: productID }
stripeHandler.open({ stripeHandler.open({
description: $.t(product.i18n) description: $.t(product.get('i18n'))
amount: product.amount amount: product.get('amount')
bitcoin: true bitcoin: true
alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto' alipay: if me.get('country') is 'china' or (me.get('preferredLanguage') or 'en-US')[...2] is 'zh' then true else 'auto'
}) })
@ -86,7 +87,7 @@ module.exports = class BuyGemsModal extends ModalView
onStripeReceivedToken: (e) -> onStripeReceivedToken: (e) ->
data = { data = {
productID: @productBeingPurchased.id productID: @productBeingPurchased.get('name')
stripe: { stripe: {
token: e.token.id token: e.token.id
timestamp: @timestampForPurchase timestamp: @timestampForPurchase
@ -97,8 +98,8 @@ module.exports = class BuyGemsModal extends ModalView
jqxhr = $.post('/db/payment', data) jqxhr = $.post('/db/payment', data)
jqxhr.done(=> jqxhr.done(=>
application.tracker?.trackEvent 'Finished gem purchase', application.tracker?.trackEvent 'Finished gem purchase',
productID: @productBeingPurchased.id productID: @productBeingPurchased.get('name')
value: @productBeingPurchased.amount value: @productBeingPurchased.get('amount')
document.location.reload() document.location.reload()
) )
jqxhr.fail(=> jqxhr.fail(=>
@ -116,7 +117,7 @@ module.exports = class BuyGemsModal extends ModalView
) )
onIAPComplete: (e) -> onIAPComplete: (e) ->
product = _.find @products, { id: e.productID } product = @products.findWhere { name: e.productID }
purchased = me.get('purchased') ? {} purchased = me.get('purchased') ? {}
purchased = _.clone purchased purchased = _.clone purchased
purchased.gems ?= 0 purchased.gems ?= 0

View file

@ -0,0 +1,5 @@
mongoose = require('mongoose')
config = require '../../server_config'
ProductSchema = new mongoose.Schema({}, {strict: false,read:config.mongo.readpref})
module.exports = mongoose.model('product', ProductSchema)

View file

@ -1,4 +1,5 @@
Payment = require './Payment' Payment = require './Payment'
Product = require '../models/Product'
User = require '../users/User' User = require '../users/User'
Handler = require '../commons/Handler' Handler = require '../commons/Handler'
{handlers} = require '../commons/mapping' {handlers} = require '../commons/mapping'
@ -11,30 +12,6 @@ request = require 'request'
async = require 'async' async = require 'async'
apple_utils = require '../lib/apple_utils' apple_utils = require '../lib/apple_utils'
products = {
'gems_5': {
amount: 499
gems: 5000
id: 'gems_5'
}
'gems_10': {
amount: 999
gems: 11000
id: 'gems_10'
}
'gems_20': {
amount: 1999
gems: 25000
id: 'gems_20'
}
'custom': {
# amount expected in request body
id: 'custom'
}
}
PaymentHandler = class PaymentHandler extends Handler PaymentHandler = class PaymentHandler extends Handler
modelClass: Payment modelClass: Payment
@ -134,33 +111,34 @@ PaymentHandler = class PaymentHandler extends Handler
payment = @makeNewInstance(req) payment = @makeNewInstance(req)
payment.set 'service', 'ios' payment.set 'service', 'ios'
product = products[transaction.product_id] Product.findOne({name: transaction.product_id}).exec (err, product) =>
return @sendDatabaseError(res, err) if err
return @sendNotFoundError(res) if not product
payment.set 'amount', product.get('amount')
payment.set 'gems', product.get('gems')
payment.set 'ios', {
transactionID: transactionID
rawReceipt: receipt
localPrice: localPrice
}
payment.set 'amount', product.amount validation = @validateDocumentInput(payment.toObject())
payment.set 'gems', product.gems if validation.valid is false
payment.set 'ios', { @logPaymentError(req, 'Invalid apple payment object.')
transactionID: transactionID return @sendBadInputError(res, validation.errors)
rawReceipt: receipt
localPrice: localPrice
}
validation = @validateDocumentInput(payment.toObject()) payment.save((err) =>
if validation.valid is false
@logPaymentError(req, 'Invalid apple payment object.')
return @sendBadInputError(res, validation.errors)
payment.save((err) =>
if err
@logPaymentError(req, 'Apple payment save error.'+err)
return @sendDatabaseError(res, err)
@incrementGemsFor(req.user, product.gems, (err) =>
if err if err
@logPaymentError(req, 'Apple incr db error.'+err) @logPaymentError(req, 'Apple payment save error.'+err)
return @sendDatabaseError(res, err) return @sendDatabaseError(res, err)
@sendPaymentHipChatMessage user: req.user, payment: payment @incrementGemsFor(req.user, product.get('gems'), (err) =>
@sendCreated(res, @formatEntity(req, payment)) if err
@logPaymentError(req, 'Apple incr db error.'+err)
return @sendDatabaseError(res, err)
@sendPaymentHipChatMessage user: req.user, payment: payment
@sendCreated(res, @formatEntity(req, payment))
)
) )
)
) )
) )
@ -203,7 +181,6 @@ PaymentHandler = class PaymentHandler extends Handler
beginStripePayment: (req, res, timestamp, productID) -> beginStripePayment: (req, res, timestamp, productID) ->
product = products[productID]
async.parallel([ async.parallel([
((callback) -> ((callback) ->
@ -218,6 +195,10 @@ PaymentHandler = class PaymentHandler extends Handler
charge = _.find recentCharges.data, (c) -> c.metadata.timestamp is timestamp charge = _.find recentCharges.data, (c) -> c.metadata.timestamp is timestamp
callback(null, charge) callback(null, charge)
) )
),
((callback) ->
Product.findOne({name: productID}).exec (err, product) =>
callback(err, product)
) )
], ],
@ -225,7 +206,10 @@ PaymentHandler = class PaymentHandler extends Handler
if err if err
@logPaymentError(req, 'Stripe async load db error. '+err) @logPaymentError(req, 'Stripe async load db error. '+err)
return @sendDatabaseError(res, err) return @sendDatabaseError(res, err)
[payment, charge] = results [payment, charge, product] = results
if not product
return @sendNotFoundError(res, 'could not find product with id '+productID)
if not (payment or charge) if not (payment or charge)
# Proceed normally from the beginning # Proceed normally from the beginning
@ -236,7 +220,7 @@ PaymentHandler = class PaymentHandler extends Handler
@recordStripeCharge(req, res, charge) @recordStripeCharge(req, res, charge)
else else
return @sendSuccess(res, @formatEntity(req, payment)) if product.id is 'custom' return @sendSuccess(res, @formatEntity(req, payment)) if product.get('name') is 'custom'
# Charged Stripe and recorded it. Recalculate gems to make sure credited the purchase. # Charged Stripe and recorded it. Recalculate gems to make sure credited the purchase.
@recalculateGemsFor(req.user, (err) => @recalculateGemsFor(req.user, (err) =>
@ -250,7 +234,7 @@ PaymentHandler = class PaymentHandler extends Handler
) )
chargeStripe: (req, res, product) -> chargeStripe: (req, res, product) ->
amount = parseInt product.amount ? req.body.amount amount = parseInt product.get('amount') ? req.body.amount
return @sendError(res, 400, "Invalid amount.") if isNaN(amount) return @sendError(res, 400, "Invalid amount.") if isNaN(amount)
stripe.charges.create({ stripe.charges.create({
@ -258,9 +242,9 @@ PaymentHandler = class PaymentHandler extends Handler
currency: 'usd' currency: 'usd'
customer: req.user.get('stripe')?.customerID customer: req.user.get('stripe')?.customerID
metadata: { metadata: {
productID: product.id productID: product.get('name')
userID: req.user._id + '' userID: req.user._id + ''
gems: product.gems gems: product.get('gems')
timestamp: parseInt(req.body.stripe?.timestamp) timestamp: parseInt(req.body.stripe?.timestamp)
description: req.body.description description: req.body.description
} }

View file

@ -15,20 +15,10 @@ User = require '../users/User'
{getSponsoredSubsAmount} = require '../../app/core/utils' {getSponsoredSubsAmount} = require '../../app/core/utils'
StripeUtils = require '../lib/stripe_utils' StripeUtils = require '../lib/stripe_utils'
moment = require 'moment' moment = require 'moment'
Product = require '../models/Product'
recipientCouponID = 'free' recipientCouponID = 'free'
# TODO: rename this to avoid collisions with 'subscriptions' variables
subscriptions = {
basic: {
gems: 3500
amount: 999 # For calculating incremental quantity before sub creation
}
year_sale: {
amount: 9900
}
}
class SubscriptionHandler extends Handler class SubscriptionHandler extends Handler
logSubscriptionError: (user, msg) -> logSubscriptionError: (user, msg) ->
console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'" console.warn "Subscription Error: #{user.get('slug')} (#{user._id}): '#{msg}'"
@ -149,51 +139,56 @@ class SubscriptionHandler extends Handler
if err if err
@logSubscriptionError(user, "Purchase year sale Stripe cancel subscription error: #{JSON.stringify(err)}") @logSubscriptionError(user, "Purchase year sale Stripe cancel subscription error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err) return @sendDatabaseError(res, err)
metadata =
type: req.body.type
userID: req.user._id + ''
gems: subscriptions.basic.gems * 12
timestamp: parseInt(req.body.stripe?.timestamp)
description: req.body.description
StripeUtils.createCharge req.user, subscriptions.year_sale.amount, metadata, (err, charge) => Product.findOne({name: 'year_subscription'}).exec (err, product) =>
if err return @sendDatabaseError(res, err) if err
@logSubscriptionError(req.user, "Purchase year sale create charge: #{JSON.stringify(err)}") return @sendNotFoundError(res, 'year_subscription product not found') if not product
return @sendDatabaseError(res, err)
StripeUtils.createPayment req.user, charge, (err, payment) => metadata =
type: req.body.type
userID: req.user._id + ''
gems: product.get('gems')
timestamp: parseInt(req.body.stripe?.timestamp)
description: req.body.description
StripeUtils.createCharge req.user, product.get('amount'), metadata, (err, charge) =>
if err if err
@logSubscriptionError(req.user, "Purchase year sale create payment: #{JSON.stringify(err)}") @logSubscriptionError(req.user, "Purchase year sale create charge: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err) return @sendDatabaseError(res, err)
# Add terminal subscription to User with extensions for existing subscriptions StripeUtils.createPayment req.user, charge, (err, payment) =>
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
endDate = new Date()
if stripeSubscriptionPeriodEndDate
endDate = stripeSubscriptionPeriodEndDate
else if _.isString(stripeInfo.free) and new Date() < new Date(stripeInfo.free)
endDate = new Date(stripeInfo.free)
endDate.setUTCFullYear(endDate.getUTCFullYear() + 1)
stripeInfo.free = endDate.toISOString().substring(0, 10)
req.user.set('stripe', stripeInfo)
# Add year's worth of gems to User
purchased = _.clone(req.user.get('purchased'))
purchased ?= {}
purchased.gems ?= 0
purchased.gems += parseInt(charge.metadata.gems)
req.user.set('purchased', purchased)
req.user.save (err, user) =>
if err if err
@logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}") @logSubscriptionError(req.user, "Purchase year sale create payment: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err) return @sendDatabaseError(res, err)
try
msg = "Year subscription purchased by #{req.user.get('email')} #{req.user.id}" # Add terminal subscription to User with extensions for existing subscriptions
hipchat.sendHipChatMessage msg, ['tower'] stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
catch error endDate = new Date()
@logSubscriptionError(req.user, "Year sub sale HipChat tower msg error: #{JSON.stringify(error)}") if stripeSubscriptionPeriodEndDate
@sendSuccess(res, user) endDate = stripeSubscriptionPeriodEndDate
else if _.isString(stripeInfo.free) and new Date() < new Date(stripeInfo.free)
endDate = new Date(stripeInfo.free)
endDate.setUTCFullYear(endDate.getUTCFullYear() + 1)
stripeInfo.free = endDate.toISOString().substring(0, 10)
req.user.set('stripe', stripeInfo)
# Add year's worth of gems to User
purchased = _.clone(req.user.get('purchased'))
purchased ?= {}
purchased.gems ?= 0
purchased.gems += parseInt(charge.metadata.gems)
req.user.set('purchased', purchased)
req.user.save (err, user) =>
if err
@logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
try
msg = "Year subscription purchased by #{req.user.get('email')} #{req.user.id}"
hipchat.sendHipChatMessage msg, ['tower']
catch error
@logSubscriptionError(req.user, "Year sub sale HipChat tower msg error: #{JSON.stringify(error)}")
@sendSuccess(res, user)
subscribeWithPrepaidCode: (req, res) -> subscribeWithPrepaidCode: (req, res) ->
return @sendUnauthorizedError(res) unless req.user? return @sendUnauthorizedError(res) unless req.user?
@ -241,31 +236,35 @@ class SubscriptionHandler extends Handler
@logSubscriptionError(user, "Redeem Prepaid Code Stripe cancel subscription error: #{JSON.stringify(err)}") @logSubscriptionError(user, "Redeem Prepaid Code Stripe cancel subscription error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err) return @sendDatabaseError(res, err)
# Add terminal subscription to User, extending existing subscriptions Product.findOne({name: 'basic_subscription'}).exec (err, product) =>
# TODO: refactor this into some form useable by both this and purchaseYearSale return @sendDatabaseError(res, err) if err
stripeInfo = _.cloneDeep(req.user.get('stripe') ? {}) return @sendNotFoundError(res, 'basic_subscription product not found') if not product
endDate = new moment()
if stripeSubscriptionPeriodEndDate
endDate = new moment(stripeSubscriptionPeriodEndDate)
else if _.isString(stripeInfo.free) and new moment().isBefore(new moment(stripeInfo.free))
endDate = new moment(stripeInfo.free)
endDate = endDate.add(months, 'months') # Add terminal subscription to User, extending existing subscriptions
stripeInfo.free = endDate.toISOString().substring(0, 10) # TODO: refactor this into some form useable by both this and purchaseYearSale
req.user.set('stripe', stripeInfo) stripeInfo = _.cloneDeep(req.user.get('stripe') ? {})
endDate = new moment()
if stripeSubscriptionPeriodEndDate
endDate = new moment(stripeSubscriptionPeriodEndDate)
else if _.isString(stripeInfo.free) and new moment().isBefore(new moment(stripeInfo.free))
endDate = new moment(stripeInfo.free)
# Add gems to User endDate = endDate.add(months, 'months')
purchased = _.clone(req.user.get('purchased')) stripeInfo.free = endDate.toISOString().substring(0, 10)
purchased ?= {} req.user.set('stripe', stripeInfo)
purchased.gems ?= 0
purchased.gems += subscriptions.basic.gems * months
req.user.set('purchased', purchased)
req.user.save (err, user) => # Add gems to User
if err purchased = _.clone(req.user.get('purchased'))
@logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}") purchased ?= {}
return @sendDatabaseError(res, err) purchased.gems ?= 0
@sendSuccess(res, user) purchased.gems += product.get('gems') * months
req.user.set('purchased', purchased)
req.user.save (err, user) =>
if err
@logSubscriptionError(req.user, "User save error: #{JSON.stringify(err)}")
return @sendDatabaseError(res, err)
@sendSuccess(res, user)
subscribeUser: (req, user, done) -> subscribeUser: (req, user, done) ->
if (not req.user) or req.user.isAnonymous() or user.isAnonymous() if (not req.user) or req.user.isAnonymous() or user.isAnonymous()
@ -427,18 +426,22 @@ class SubscriptionHandler extends Handler
req.body.stripe = stripeInfo req.body.stripe = stripeInfo
user.set('stripe', stripeInfo) user.set('stripe', stripeInfo)
if increment Product.findOne({name: 'basic_subscription'}).exec (err, product) =>
purchased = _.clone(user.get('purchased')) return @sendDatabaseError(res, err) if err
purchased ?= {} return @sendNotFoundError(res, 'basic_subscription product not found') if not product
purchased.gems ?= 0
purchased.gems += subscriptions.basic.gems # TODO: Put actual subscription amount here
user.set('purchased', purchased)
user.save (err) => if increment
if err purchased = _.clone(user.get('purchased'))
@logSubscriptionError(user, 'Stripe user plan saving error. ' + err) purchased ?= {}
return done({res: 'Database error.', code: 500}) purchased.gems ?= 0
done() purchased.gems += product.get('gems') # TODO: Put actual subscription amount here
user.set('purchased', purchased)
user.save (err) =>
if err
@logSubscriptionError(user, 'Stripe user plan saving error. ' + err)
return done({res: 'Database error.', code: 500})
done()
updateStripeRecipientSubscriptions: (req, user, customer, done) -> updateStripeRecipientSubscriptions: (req, user, customer, done) ->
return done({res: 'Database error.', code: 500}) unless req.body.stripe?.subscribeEmails? return done({res: 'Database error.', code: 500}) unless req.body.stripe?.subscribeEmails?
@ -527,36 +530,40 @@ class SubscriptionHandler extends Handler
@logSubscriptionError(user, 'User saving stripe error. ' + err) @logSubscriptionError(user, 'User saving stripe error. ' + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
createUpdateFn = (recipient, increment) => Product.findOne({name: 'basic_subscription'}).exec (err, product) =>
(done) => return @sendDatabaseError(res, err) if err
# Update recipient return @sendNotFoundError(res, 'basic_subscription product not found') if not product
stripeInfo = _.cloneDeep(recipient.get('stripe') ? {})
stripeInfo.sponsorID = user.id
recipient.set 'stripe', stripeInfo
if increment
purchased = _.clone(recipient.get('purchased'))
purchased ?= {}
purchased.gems ?= 0
purchased.gems += subscriptions.basic.gems
recipient.set('purchased', purchased)
recipient.save (err) =>
if err
@logSubscriptionError(user, 'Stripe user saving stripe error. ' + err)
return done({res: 'Database error.', code: 500})
done()
tasks = [] createUpdateFn = (recipient, increment) =>
for sub in stripeRecipients (done) =>
tasks.push createUpdateFn(sub.recipient, sub.increment) # Update recipient
stripeInfo = _.cloneDeep(recipient.get('stripe') ? {})
stripeInfo.sponsorID = user.id
recipient.set 'stripe', stripeInfo
if increment
purchased = _.clone(recipient.get('purchased'))
purchased ?= {}
purchased.gems ?= 0
purchased.gems += product.get('gems')
recipient.set('purchased', purchased)
recipient.save (err) =>
if err
@logSubscriptionError(user, 'Stripe user saving stripe error. ' + err)
return done({res: 'Database error.', code: 500})
done()
async.parallel tasks, (err, results) => tasks = []
return done(err) if err for sub in stripeRecipients
@updateStripeSponsorSubscription(req, user, customer, done) tasks.push createUpdateFn(sub.recipient, sub.increment)
updateStripeSponsorSubscription: (req, user, customer, done) -> async.parallel tasks, (err, results) =>
return done(err) if err
@updateStripeSponsorSubscription(req, user, customer, product, done)
updateStripeSponsorSubscription: (req, user, customer, product, done) ->
stripeInfo = user.get('stripe') ? {} stripeInfo = user.get('stripe') ? {}
numSponsored = stripeInfo.recipients.length numSponsored = stripeInfo.recipients.length
quantity = getSponsoredSubsAmount(subscriptions.basic.amount, numSponsored, stripeInfo.subscriptionID?) quantity = getSponsoredSubsAmount(product.get('amount'), numSponsored, stripeInfo.subscriptionID?)
findStripeSubscription customer.id, subscriptionID: stripeInfo.sponsorSubscriptionID, (subscription) => findStripeSubscription customer.id, subscriptionID: stripeInfo.sponsorSubscriptionID, (subscription) =>
if stripeInfo.sponsorSubscriptionID? and not subscription? if stripeInfo.sponsorSubscriptionID? and not subscription?
@ -656,38 +663,42 @@ class SubscriptionHandler extends Handler
@logSubscriptionError(user, 'Unable to find recipient subscription. ') @logSubscriptionError(user, 'Unable to find recipient subscription. ')
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
# Update recipient user Product.findOne({name: 'basic_subscription'}).exec (err, product) =>
deleteUserStripeProp(recipient, 'sponsorID') return @sendDatabaseError(res, err) if err
recipient.save (err) => return @sendNotFoundError(res, 'basic_subscription product not found') if not product
if err
@logSubscriptionError(user, 'Recipient user save unsubscribe error. ' + err)
return done({res: 'Database error.', code: 500})
# Cancel Stripe subscription # Update recipient user
stripe.customers.cancelSubscription stripeInfo.customerID, sponsoredEntry.subscriptionID, (err) => deleteUserStripeProp(recipient, 'sponsorID')
recipient.save (err) =>
if err if err
@logSubscriptionError(user, "Stripe cancel sponsored subscription failed. " + err) @logSubscriptionError(user, 'Recipient user save unsubscribe error. ' + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
# Update sponsor user # Cancel Stripe subscription
_.remove(stripeInfo.recipients, (s) -> s.userID is recipient.id) stripe.customers.cancelSubscription stripeInfo.customerID, sponsoredEntry.subscriptionID, (err) =>
delete stripeInfo.unsubscribeEmail
user.set('stripe', stripeInfo)
req.body.stripe = stripeInfo
user.save (err) =>
if err if err
@logSubscriptionError(user, 'Sponsor user save unsubscribe error. ' + err) @logSubscriptionError(user, "Stripe cancel sponsored subscription failed. " + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
return done() unless stripeInfo.sponsorSubscriptionID? # Update sponsor user
_.remove(stripeInfo.recipients, (s) -> s.userID is recipient.id)
# Update sponsored subscription quantity delete stripeInfo.unsubscribeEmail
options = user.set('stripe', stripeInfo)
quantity: getSponsoredSubsAmount(subscriptions.basic.amount, stripeInfo.recipients.length, stripeInfo.subscriptionID?) req.body.stripe = stripeInfo
stripe.customers.updateSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, options, (err, subscription) => user.save (err) =>
if err if err
@logSubscriptionError(user, 'Sponsored subscription quantity update error. ' + JSON.stringify(err)) @logSubscriptionError(user, 'Sponsor user save unsubscribe error. ' + err)
return done({res: 'Database error.', code: 500}) return done({res: 'Database error.', code: 500})
done()
return done() unless stripeInfo.sponsorSubscriptionID?
# Update sponsored subscription quantity
options =
quantity: getSponsoredSubsAmount(product.get('amount'), stripeInfo.recipients.length, stripeInfo.subscriptionID?)
stripe.customers.updateSubscription stripeInfo.customerID, stripeInfo.sponsorSubscriptionID, options, (err, subscription) =>
if err
@logSubscriptionError(user, 'Sponsored subscription quantity update error. ' + JSON.stringify(err))
return done({res: 'Database error.', code: 500})
done()
module.exports = new SubscriptionHandler() module.exports = new SubscriptionHandler()

View file

@ -6,6 +6,7 @@ User = require '../users/User'
StripeUtils = require '../lib/stripe_utils' StripeUtils = require '../lib/stripe_utils'
utils = require '../../app/core/utils' utils = require '../../app/core/utils'
mongoose = require 'mongoose' mongoose = require 'mongoose'
Product = require '../models/Product'
# TODO: Should this happen on a save() call instead of a prepaid/-/create post? # TODO: Should this happen on a save() call instead of a prepaid/-/create post?
# TODO: Probably a better way to create a unique 8 charactor string property using db voodoo # TODO: Probably a better way to create a unique 8 charactor string property using db voodoo
@ -17,8 +18,6 @@ PrepaidHandler = class PrepaidHandler extends Handler
jsonSchema: require '../../app/schemas/models/prepaid.schema' jsonSchema: require '../../app/schemas/models/prepaid.schema'
allowedMethods: ['GET','POST'] allowedMethods: ['GET','POST']
baseAmount: 999
logError: (user, msg) -> logError: (user, msg) ->
console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'" console.warn "Prepaid Error: [#{user.get('slug')} (#{user._id})] '#{msg}'"
@ -134,9 +133,13 @@ PrepaidHandler = class PrepaidHandler extends Handler
return @sendBadInputError(res) unless isNaN(months) is false and months > 0 return @sendBadInputError(res) unless isNaN(months) is false and months > 0
return @sendError(res, 403, "Users or Months must be greater than 3") if maxRedeemers < 3 and months < 3 return @sendError(res, 403, "Users or Months must be greater than 3") if maxRedeemers < 3 and months < 3
@purchasePrepaidTerminalSubscription req.user, description, maxRedeemers, months, timestamp, token, (err, prepaid) => Product.findOne({name: 'prepaid_subscription'}).exec (err, product) =>
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, prepaid.toObject()) return @sendNotFoundError(res, 'prepaid_subscription product not found') if not product
@purchasePrepaidTerminalSubscription req.user, description, maxRedeemers, months, timestamp, token, product, (err, prepaid) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, prepaid.toObject())
else if req.body.type is 'course' else if req.body.type is 'course'
maxRedeemers = parseInt(req.body.maxRedeemers) maxRedeemers = parseInt(req.body.maxRedeemers)
@ -145,18 +148,22 @@ PrepaidHandler = class PrepaidHandler extends Handler
return @sendBadInputError(res) unless isNaN(maxRedeemers) is false and maxRedeemers > 0 return @sendBadInputError(res) unless isNaN(maxRedeemers) is false and maxRedeemers > 0
@purchasePrepaidCourse req.user, maxRedeemers, timestamp, token, (err, prepaid) => Product.findOne({name: 'course'}).exec (err, product) =>
# TODO: this badinput detection is fragile, in course instance handler as well
return @sendBadInputError(res, err) if err is 'Missing required Stripe token'
return @sendDatabaseError(res, err) if err return @sendDatabaseError(res, err) if err
@sendSuccess(res, prepaid.toObject()) return @sendNotFoundError(res, 'course product not found') if not product
@purchasePrepaidCourse req.user, maxRedeemers, timestamp, token, product, (err, prepaid) =>
# TODO: this badinput detection is fragile, in course instance handler as well
return @sendBadInputError(res, err) if err is 'Missing required Stripe token'
return @sendDatabaseError(res, err) if err
@sendSuccess(res, prepaid.toObject())
else else
@sendForbiddenError(res) @sendForbiddenError(res)
purchasePrepaidCourse: (user, maxRedeemers, timestamp, token, done) -> purchasePrepaidCourse: (user, maxRedeemers, timestamp, token, product, done) ->
type = 'course' type = 'course'
amount = maxRedeemers * 400 amount = maxRedeemers * product.get('amount')
if amount > 0 and not (token or user.isAdmin()) if amount > 0 and not (token or user.isAdmin())
@logError(user, "Purchase prepaid courses missing required Stripe token #{amount}") @logError(user, "Purchase prepaid courses missing required Stripe token #{amount}")
return done('Missing required Stripe token') return done('Missing required Stripe token')
@ -190,7 +197,7 @@ PrepaidHandler = class PrepaidHandler extends Handler
hipchat.sendHipChatMessage msg, ['tower'] hipchat.sendHipChatMessage msg, ['tower']
@createPrepaid(user, type, maxRedeemers, {}, done) @createPrepaid(user, type, maxRedeemers, {}, done)
purchasePrepaidTerminalSubscription: (user, description, maxRedeemers, months, timestamp, token, done) -> purchasePrepaidTerminalSubscription: (user, description, maxRedeemers, months, timestamp, token, product, done) ->
type = 'terminal_subscription' type = 'terminal_subscription'
StripeUtils.getCustomer user, token, (err, customer) => StripeUtils.getCustomer user, token, (err, customer) =>
@ -207,7 +214,7 @@ PrepaidHandler = class PrepaidHandler extends Handler
months: months months: months
productID: "prepaid #{type}" productID: "prepaid #{type}"
amount = utils.getPrepaidCodeAmount(@baseAmount, maxRedeemers, months) amount = utils.getPrepaidCodeAmount(product.get('amount'), maxRedeemers, months)
StripeUtils.createCharge user, amount, metadata, (err, charge) => StripeUtils.createCharge user, amount, metadata, (err, charge) =>
if err if err

View file

@ -0,0 +1,2 @@
module.exports.setup = (app) ->
app.get('/db/products', require('./db/products').get)

View file

@ -14,6 +14,7 @@ user = require './server/users/user_handler'
logging = require './server/commons/logging' logging = require './server/commons/logging'
config = require './server_config' config = require './server_config'
auth = require './server/routes/auth' auth = require './server/routes/auth'
routes = require './server/routes'
UserHandler = require './server/users/user_handler' UserHandler = require './server/users/user_handler'
hipchat = require './server/hipchat' hipchat = require './server/hipchat'
global.tv4 = require 'tv4' # required for TreemaUtils to work global.tv4 = require 'tv4' # required for TreemaUtils to work
@ -166,6 +167,7 @@ setupFacebookCrossDomainCommunicationRoute = (app) ->
res.sendfile path.join(__dirname, 'public', 'channel.html') res.sendfile path.join(__dirname, 'public', 'channel.html')
exports.setupRoutes = (app) -> exports.setupRoutes = (app) ->
routes.setup(app)
app.use app.router app.use app.router
baseRoute.setup app baseRoute.setup app

View file

@ -67,5 +67,14 @@ describe('Server Test Helper', function() {
if (err) { console.log(err); } if (err) { console.log(err); }
done(); done();
}); });
});
it('initializes products', function(done) {
var request = require('request');
request.get(getURL('/db/products'), function(err, res, body) {
expect(err).toBe(null);
expect(res.statusCode).toBe(200);
done();
});
}) })
}); });