Set up stripe on the server and site to allow purchases through the website.
This commit is contained in:
parent
d3da5e330a
commit
95dca575d1
14 changed files with 404 additions and 39 deletions
app
lib/services
locale
schemas
styles/play/modal
templates/play
views/play/modal
server
server_config.coffeetest/server/functional
10
app/lib/services/stripe.coffee
Normal file
10
app/lib/services/stripe.coffee
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
publishableKey = if application.isProduction() then 'pk_live_27jQZozjDGN1HSUTnSuM578g' else 'pk_test_zG5UwVu6Ww8YhtE9ZYh0JO6a'
|
||||||
|
|
||||||
|
module.exports = handler = StripeCheckout.configure({
|
||||||
|
key: publishableKey
|
||||||
|
name: 'CodeCombat'
|
||||||
|
email: me.get('email')
|
||||||
|
image: '/images/pages/base/logo_square_250.png'
|
||||||
|
token: (token) ->
|
||||||
|
Backbone.Mediator.publish 'stripe:received-token', { token: token }
|
||||||
|
})
|
|
@ -319,6 +319,9 @@
|
||||||
few_gems: 'A few gems'
|
few_gems: 'A few gems'
|
||||||
pile_gems: 'Pile of gems'
|
pile_gems: 'Pile of gems'
|
||||||
chest_gems: 'Chest of gems'
|
chest_gems: 'Chest of gems'
|
||||||
|
purchasing: 'Purchasing...'
|
||||||
|
declined: 'Your card was declined'
|
||||||
|
retrying: 'Server error, retrying.'
|
||||||
|
|
||||||
choose_hero:
|
choose_hero:
|
||||||
choose_hero: "Choose Your Hero"
|
choose_hero: "Choose Your Hero"
|
||||||
|
|
|
@ -8,6 +8,7 @@ PaymentSchema = c.object({title: 'Payment', required: []}, {
|
||||||
amount: { type: 'integer', description: 'Payment in cents.' }
|
amount: { type: 'integer', description: 'Payment in cents.' }
|
||||||
created: c.date({title: 'Created', readOnly: true})
|
created: c.date({title: 'Created', readOnly: true})
|
||||||
gems: { type: 'integer', description: 'The number of gems acquired.' }
|
gems: { type: 'integer', description: 'The number of gems acquired.' }
|
||||||
|
productID: { enum: ['gems_5', 'gems_10', 'gems_20']}
|
||||||
|
|
||||||
ios: c.object({title: 'iOS IAP Data'}, {
|
ios: c.object({title: 'iOS IAP Data'}, {
|
||||||
transactionID: { type: 'string' }
|
transactionID: { type: 'string' }
|
||||||
|
@ -17,7 +18,7 @@ PaymentSchema = c.object({title: 'Payment', required: []}, {
|
||||||
|
|
||||||
stripe: c.object({title: 'Stripe Data'}, {
|
stripe: c.object({title: 'Stripe Data'}, {
|
||||||
timestamp: { type: 'integer', description: 'Unique identifier provided by the client, to guard against duplicate payments.' }
|
timestamp: { type: 'integer', description: 'Unique identifier provided by the client, to guard against duplicate payments.' }
|
||||||
transactionID: { type: 'string' }
|
chargeID: { type: 'string' }
|
||||||
customerID: { type: 'string' }
|
customerID: { type: 'string' }
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -270,6 +270,7 @@ _.extend UserSchema.properties,
|
||||||
earned: c.RewardSchema 'earned by achievements'
|
earned: c.RewardSchema 'earned by achievements'
|
||||||
purchased: c.RewardSchema 'purchased with gems or money'
|
purchased: c.RewardSchema 'purchased with gems or money'
|
||||||
spent: {type: 'number'}
|
spent: {type: 'number'}
|
||||||
|
stripeCustomerID: { type: 'string' }
|
||||||
|
|
||||||
c.extendBasicProperties UserSchema, 'user'
|
c.extendBasicProperties UserSchema, 'user'
|
||||||
|
|
||||||
|
|
|
@ -51,4 +51,9 @@ module.exports =
|
||||||
'buy-gems-modal:update-products': { }
|
'buy-gems-modal:update-products': { }
|
||||||
|
|
||||||
'buy-gems-modal:purchase-initiated': c.object {required: ['productID']},
|
'buy-gems-modal:purchase-initiated': c.object {required: ['productID']},
|
||||||
productID: { type: 'string' }
|
productID: { type: 'string' }
|
||||||
|
|
||||||
|
'stripe:received-token': c.object { required: ['token'] },
|
||||||
|
token: { type: 'object', properties: {
|
||||||
|
id: {type: 'string'}
|
||||||
|
}}
|
|
@ -42,3 +42,12 @@
|
||||||
|
|
||||||
button
|
button
|
||||||
width: 80%
|
width: 80%
|
||||||
|
|
||||||
|
|
||||||
|
//- Errors
|
||||||
|
.alert
|
||||||
|
position: absolute
|
||||||
|
left: 10%
|
||||||
|
width: 80%
|
||||||
|
top: 20px
|
||||||
|
border: 5px solid gray
|
||||||
|
|
|
@ -1,11 +1,30 @@
|
||||||
.modal-dialog
|
.modal-dialog
|
||||||
.modal-content
|
.modal-content
|
||||||
img(src="/images/pages/play/modal/buy-gems-background.png")#buy-gems-background
|
if state === 'purchasing'
|
||||||
|
.alert.alert-info(data-i18n="buy_gems.purchasing")
|
||||||
|
|
||||||
#products
|
else if state === 'retrying'
|
||||||
for product in products
|
#retrying-alert.alert.alert-danger(data-i18n="buy_gems.retrying")
|
||||||
.product
|
|
||||||
h4 x#{product.gems}
|
else
|
||||||
h3(data-i18n=product.i18n)
|
img(src="/images/pages/play/modal/buy-gems-background.png")#buy-gems-background
|
||||||
button.btn.btn-illustrated.btn-lg(value=product.id)
|
|
||||||
span= product.price
|
#products
|
||||||
|
for product in products
|
||||||
|
.product
|
||||||
|
h4 x#{product.gems}
|
||||||
|
h3(data-i18n=product.i18n)
|
||||||
|
button.btn.btn-illustrated.btn-lg(value=product.id)
|
||||||
|
span= product.price
|
||||||
|
|
||||||
|
if state === 'declined'
|
||||||
|
#declined-alert.alert.alert-danger.alert-dismissible
|
||||||
|
span(data-i18n="buy_gems.declined")
|
||||||
|
button.close(type="button" data-dismiss="alert")
|
||||||
|
span(aria-hidden="true") ×
|
||||||
|
|
||||||
|
if state === 'unknown_error'
|
||||||
|
#error-alert.alert.alert-danger.alert-dismissible
|
||||||
|
span(data-i18n="loading_error.unknown")
|
||||||
|
button.close(type="button" data-dismiss="alert")
|
||||||
|
span(aria-hidden="true") ×
|
|
@ -43,13 +43,13 @@
|
||||||
.game-controls.header-font
|
.game-controls.header-font
|
||||||
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
|
button.btn.items(data-toggle='coco-modal', data-target='play/modal/PlayItemsModal', data-i18n="[title]play.items")
|
||||||
button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes")
|
button.btn.heroes(data-toggle='coco-modal', data-target='play/modal/PlayHeroesModal', data-i18n="[title]play.heroes")
|
||||||
if me.isAdmin() || isIPadApp
|
if me.get('anonymous') === false
|
||||||
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
|
button.btn.gems(data-toggle='coco-modal', data-target='play/modal/BuyGemsModal', data-i18n="[title]play.buy_gems")
|
||||||
if me.isAdmin()
|
if me.isAdmin()
|
||||||
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
|
button.btn.achievements(data-toggle='coco-modal', data-target='play/modal/PlayAchievementsModal', data-i18n="[title]play.achievements")
|
||||||
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
|
button.btn.account(data-toggle='coco-modal', data-target='play/modal/PlayAccountModal', data-i18n="[title]play.account")
|
||||||
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
|
button.btn.settings(data-toggle='coco-modal', data-target='play/modal/PlaySettingsModal', data-i18n="[title]play.settings")
|
||||||
else if me.get('anonymous')
|
else if me.get('anonymous', true)
|
||||||
button.btn.settings(data-toggle='coco-modal', data-target='modal/AuthModal', data-i18n="[title]play.settings")
|
button.btn.settings(data-toggle='coco-modal', data-target='modal/AuthModal', data-i18n="[title]play.settings")
|
||||||
// Don't show these things, they are bad and take us out of the game. Just wait until the new ones work.
|
// Don't show these things, they are bad and take us out of the game. Just wait until the new ones work.
|
||||||
//else
|
//else
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
ModalView = require 'views/kinds/ModalView'
|
ModalView = require 'views/kinds/ModalView'
|
||||||
template = require 'templates/play/modal/buy-gems-modal'
|
template = require 'templates/play/modal/buy-gems-modal'
|
||||||
|
stripeHandler = require 'lib/services/stripe'
|
||||||
|
utils = require 'lib/utils'
|
||||||
|
|
||||||
module.exports = class BuyGemsModal extends ModalView
|
module.exports = class BuyGemsModal extends ModalView
|
||||||
id: 'buy-gems-modal'
|
id: 'buy-gems-modal'
|
||||||
|
@ -7,20 +9,22 @@ module.exports = class BuyGemsModal extends ModalView
|
||||||
plain: true
|
plain: true
|
||||||
|
|
||||||
originalProducts: [
|
originalProducts: [
|
||||||
{ price: '$4.99', gems: 5000, id: 'gems_5', i18n: 'buy_gems.few_gems' }
|
{ price: '$4.99', gems: 5000, amount: 499, id: 'gems_5', i18n: 'buy_gems.few_gems' }
|
||||||
{ price: '$9.99', gems: 11000, id: 'gems_10', i18n: 'buy_gems.pile_gems' }
|
{ price: '$9.99', gems: 11000, amount: 999, id: 'gems_10', i18n: 'buy_gems.pile_gems' }
|
||||||
{ price: '$19.99', gems: 25000, id: 'gems_20', i18n: 'buy_gems.chest_gems' }
|
{ price: '$19.99', gems: 25000, amount: 1999, id: 'gems_20', i18n: 'buy_gems.chest_gems' }
|
||||||
]
|
]
|
||||||
|
|
||||||
subscriptions:
|
subscriptions:
|
||||||
'ipad:products': 'onIPadProducts'
|
'ipad:products': 'onIPadProducts'
|
||||||
'ipad:iap-complete': 'onIAPComplete'
|
'ipad:iap-complete': 'onIAPComplete'
|
||||||
|
'stripe:received-token': 'onStripeReceivedToken'
|
||||||
|
|
||||||
events:
|
events:
|
||||||
'click .product button': 'onClickProductButton'
|
'click .product button': 'onClickProductButton'
|
||||||
|
|
||||||
constructor: (options) ->
|
constructor: (options) ->
|
||||||
super(options)
|
super(options)
|
||||||
|
@state = 'standby'
|
||||||
if application.isIPadApp
|
if application.isIPadApp
|
||||||
@products = []
|
@products = []
|
||||||
Backbone.Mediator.publish 'buy-gems-modal:update-products'
|
Backbone.Mediator.publish 'buy-gems-modal:update-products'
|
||||||
|
@ -30,6 +34,7 @@ module.exports = class BuyGemsModal extends ModalView
|
||||||
getRenderData: ->
|
getRenderData: ->
|
||||||
c = super()
|
c = super()
|
||||||
c.products = @products
|
c.products = @products
|
||||||
|
c.state = @state
|
||||||
return c
|
return c
|
||||||
|
|
||||||
onIPadProducts: (e) ->
|
onIPadProducts: (e) ->
|
||||||
|
@ -43,15 +48,48 @@ module.exports = class BuyGemsModal extends ModalView
|
||||||
@render()
|
@render()
|
||||||
|
|
||||||
onClickProductButton: (e) ->
|
onClickProductButton: (e) ->
|
||||||
productID = $(e.target).closest('button.product').val()
|
productID = $(e.target).closest('button').val()
|
||||||
console.log 'purchasing', _.find @products, { id: productID }
|
product = _.find @products, { id: 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 }
|
||||||
|
|
||||||
else
|
else
|
||||||
application.tracker?.trackEvent 'Started purchase', {productID:productID}, ['Google Analytics']
|
application.tracker?.trackEvent 'Started purchase', {productID:productID}, ['Google Analytics']
|
||||||
alert('Not yet implemented, but thanks for trying!')
|
stripeHandler.open({
|
||||||
|
description: $.t(product.i18n)
|
||||||
|
amount: product.amount
|
||||||
|
})
|
||||||
|
|
||||||
|
@productBeingPurchased = product
|
||||||
|
|
||||||
|
onStripeReceivedToken: (e) ->
|
||||||
|
@timestampForPurchase = new Date().getTime()
|
||||||
|
data = {
|
||||||
|
productID: @productBeingPurchased.id
|
||||||
|
stripe: {
|
||||||
|
token: e.token.id
|
||||||
|
timestamp: @timestampForPurchase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@state = 'purchasing'
|
||||||
|
@render()
|
||||||
|
jqxhr = $.post('/db/payment', data)
|
||||||
|
jqxhr.done(=>
|
||||||
|
document.location.reload()
|
||||||
|
)
|
||||||
|
jqxhr.fail(=>
|
||||||
|
if jqxhr.status is 402
|
||||||
|
@state = 'declined'
|
||||||
|
@stateMessage = arguments[2]
|
||||||
|
else if jqxhr.status is 500
|
||||||
|
@state = 'retrying'
|
||||||
|
f = _.bind @onStripeReceivedToken, @, e
|
||||||
|
_.delay f, 2000
|
||||||
|
else
|
||||||
|
@state = 'unknown_error'
|
||||||
|
@render()
|
||||||
|
)
|
||||||
|
|
||||||
onIAPComplete: (e) ->
|
onIAPComplete: (e) ->
|
||||||
product = _.find @products, { id: e.productID }
|
product = _.find @products, { id: e.productID }
|
||||||
|
|
|
@ -69,7 +69,8 @@
|
||||||
"aether": "~0.2.39",
|
"aether": "~0.2.39",
|
||||||
"JASON": "~0.1.3",
|
"JASON": "~0.1.3",
|
||||||
"JQDeferred": "~2.1.0",
|
"JQDeferred": "~2.1.0",
|
||||||
"jsondiffpatch": "0.1.17"
|
"jsondiffpatch": "0.1.17",
|
||||||
|
"stripe": "~2.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"jade": "0.33.x",
|
"jade": "0.33.x",
|
||||||
|
|
|
@ -8,21 +8,26 @@ sendwithus = require '../sendwithus'
|
||||||
hipchat = require '../hipchat'
|
hipchat = require '../hipchat'
|
||||||
config = require '../../server_config'
|
config = require '../../server_config'
|
||||||
request = require 'request'
|
request = require 'request'
|
||||||
|
stripe = require('stripe')(config.stripe.secretKey)
|
||||||
|
async = require 'async'
|
||||||
|
|
||||||
products = {
|
products = {
|
||||||
'gems_5': {
|
'gems_5': {
|
||||||
amount: 500
|
amount: 499
|
||||||
gems: 5000
|
gems: 5000
|
||||||
|
id: 'gems_5'
|
||||||
}
|
}
|
||||||
|
|
||||||
'gems_10': {
|
'gems_10': {
|
||||||
amount: 1000
|
amount: 999
|
||||||
gems: 11000
|
gems: 11000
|
||||||
|
id: 'gems_10'
|
||||||
}
|
}
|
||||||
|
|
||||||
'gems_20': {
|
'gems_20': {
|
||||||
amount: 2000
|
amount: 1999
|
||||||
gems: 25000
|
gems: 25000
|
||||||
|
id: 'gems_20'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,9 +50,16 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
appleLocalPrice = req.body.apple?.localPrice
|
appleLocalPrice = req.body.apple?.localPrice
|
||||||
stripeToken = req.body.stripe?.token
|
stripeToken = req.body.stripe?.token
|
||||||
stripeTimestamp = parseInt(req.body.stripe?.timestamp)
|
stripeTimestamp = parseInt(req.body.stripe?.timestamp)
|
||||||
|
productID = req.body.productID
|
||||||
|
|
||||||
if not (appleReceipt or stripeTimestamp)
|
if not (appleReceipt or (stripeTimestamp and productID))
|
||||||
return @sendBadInputError(res, 'Need either apple.rawReceipt or stripe.timestamp')
|
return @sendBadInputError(res, 'Need either apple.rawReceipt or stripe.timestamp and productID')
|
||||||
|
|
||||||
|
if stripeTimestamp and not productID
|
||||||
|
return @sendBadInputError(res, 'Need productID if paying with Stripe.')
|
||||||
|
|
||||||
|
if stripeTimestamp and (not stripeToken) and (not user.get('stripeCustomerID'))
|
||||||
|
return @sendBadInputError(res, 'Need stripe.token if new customer.')
|
||||||
|
|
||||||
if appleReceipt
|
if appleReceipt
|
||||||
if not appleTransactionID
|
if not appleTransactionID
|
||||||
|
@ -55,7 +67,10 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
@handleApplePaymentPost(req, res, appleReceipt, appleTransactionID, appleLocalPrice)
|
@handleApplePaymentPost(req, res, appleReceipt, appleTransactionID, appleLocalPrice)
|
||||||
|
|
||||||
else
|
else
|
||||||
@handleStripePaymentPost(req, res, stripeTimestamp, stripeToken)
|
@handleStripePaymentPost(req, res, stripeTimestamp, productID, stripeToken)
|
||||||
|
|
||||||
|
|
||||||
|
#- Apple payments
|
||||||
|
|
||||||
handleApplePaymentPost: (req, res, receipt, transactionID, localPrice) ->
|
handleApplePaymentPost: (req, res, receipt, transactionID, localPrice) ->
|
||||||
formFields = { 'receipt-data': receipt }
|
formFields = { 'receipt-data': receipt }
|
||||||
|
@ -87,8 +102,6 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
payment.set 'service', 'ios'
|
payment.set 'service', 'ios'
|
||||||
product = products[transaction.product_id]
|
product = products[transaction.product_id]
|
||||||
|
|
||||||
product ?= _.values(products)[0] # TEST
|
|
||||||
|
|
||||||
payment.set 'amount', product.amount
|
payment.set 'amount', product.amount
|
||||||
payment.set 'gems', product.gems
|
payment.set 'gems', product.gems
|
||||||
payment.set 'ios', {
|
payment.set 'ios', {
|
||||||
|
@ -110,10 +123,123 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
handleStripePaymentPost: (req, res, timestamp, token) ->
|
#- Stripe payments
|
||||||
console.log 'lol not implemented yet'
|
|
||||||
@sendNotFoundError(res)
|
|
||||||
|
|
||||||
|
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')
|
||||||
|
stripe.customers.create({
|
||||||
|
card: token
|
||||||
|
description: req.user._id + ''
|
||||||
|
}).then(((customer) =>
|
||||||
|
req.user.set('stripeCustomerID', customer.id)
|
||||||
|
req.user.save((err) =>
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@beginStripePayment(req, res, timestamp, productID)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
(err) =>
|
||||||
|
return @sendDatabaseError(res, err)
|
||||||
|
)
|
||||||
|
|
||||||
|
else
|
||||||
|
@beginStripePayment(req, res, timestamp, productID)
|
||||||
|
|
||||||
|
|
||||||
|
beginStripePayment: (req, res, timestamp, productID) ->
|
||||||
|
product = products[productID]
|
||||||
|
|
||||||
|
async.parallel([
|
||||||
|
((callback) ->
|
||||||
|
criteria = { recipient: req.user._id, 'stripe.timestamp': timestamp }
|
||||||
|
Payment.findOne(criteria).exec((err, payment) =>
|
||||||
|
callback(err, payment)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
((callback) ->
|
||||||
|
stripe.charges.list({customer: req.user.get('stripeCustomerID')}, (err, recentCharges) =>
|
||||||
|
return callback(err) if err
|
||||||
|
charge = _.find recentCharges.data, (c) -> c.metadata.timestamp is timestamp
|
||||||
|
callback(null, charge)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
|
||||||
|
((err, results) =>
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
|
[payment, charge] = results
|
||||||
|
|
||||||
|
if not (payment or charge)
|
||||||
|
# Proceed normally from the beginning
|
||||||
|
@chargeStripe(req, res, payment, product)
|
||||||
|
|
||||||
|
else if charge and not payment
|
||||||
|
# Initialized Payment. Start from charging.
|
||||||
|
@recordStripeCharge(req, res, payment, product, charge)
|
||||||
|
|
||||||
|
else
|
||||||
|
# Charged Stripe and recorded it. Recalculate gems to make sure credited the purchase.
|
||||||
|
@recalculateGemsFor(req.user, (err) =>
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@sendSuccess(res, @formatEntity(req, payment))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
chargeStripe: (req, res, payment, product) ->
|
||||||
|
stripe.charges.create({
|
||||||
|
amount: product.amount
|
||||||
|
currency: 'usd'
|
||||||
|
customer: req.user.get('stripeCustomerID')
|
||||||
|
metadata: {
|
||||||
|
productID: product.id
|
||||||
|
userID: req.user._id + ''
|
||||||
|
gems: product.gems
|
||||||
|
timestamp: parseInt(req.body.stripe?.timestamp)
|
||||||
|
}
|
||||||
|
receipt_email: req.user.get('email')
|
||||||
|
}).then(
|
||||||
|
# success case
|
||||||
|
((charge) => @recordStripeCharge(req, res, payment, product, charge)),
|
||||||
|
|
||||||
|
# error case
|
||||||
|
((err) =>
|
||||||
|
if err.type in ['StripeCardError', 'StripeInvalidRequestError']
|
||||||
|
@sendError(res, 402, err.message)
|
||||||
|
else
|
||||||
|
@sendDatabaseError(res, 'Error charging card, please retry.'))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
recordStripeCharge: (req, res, payment, product, charge) ->
|
||||||
|
return @sendError(res, 500, 'Fake db error for testing.') if req.body.breakAfterCharging
|
||||||
|
payment = @makeNewInstance(req)
|
||||||
|
payment.set 'service', 'stripe'
|
||||||
|
payment.set 'productID', req.body.productID
|
||||||
|
payment.set 'amount', product.amount
|
||||||
|
payment.set 'gems', product.gems
|
||||||
|
payment.set 'stripe', {
|
||||||
|
customerID: req.user.get('stripeCustomerID')
|
||||||
|
timestamp: parseInt(req.body.stripe.timestamp)
|
||||||
|
chargeID: charge.id
|
||||||
|
}
|
||||||
|
|
||||||
|
validation = @validateDocumentInput(payment.toObject())
|
||||||
|
return @sendBadInputError(res, validation.errors) if validation.valid is false
|
||||||
|
payment.save((err) =>
|
||||||
|
|
||||||
|
# Credit gems
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@incrementGemsFor(req.user, product.gems, (err) =>
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
|
@sendCreated(res, @formatEntity(req, payment))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#- Incrementing/recalculating gems
|
||||||
|
|
||||||
incrementGemsFor: (user, gems, done) ->
|
incrementGemsFor: (user, gems, done) ->
|
||||||
purchased = _.clone(user.get('purchased'))
|
purchased = _.clone(user.get('purchased'))
|
||||||
|
|
|
@ -192,7 +192,7 @@ UserSchema.statics.hashPassword = (password) ->
|
||||||
UserSchema.statics.privateProperties = [
|
UserSchema.statics.privateProperties = [
|
||||||
'permissions', 'email', 'mailChimp', 'firstName', 'lastName', 'gender', 'facebookID',
|
'permissions', 'email', 'mailChimp', 'firstName', 'lastName', 'gender', 'facebookID',
|
||||||
'gplusID', 'music', 'volume', 'aceConfig', 'employerAt', 'signedEmployerAgreement',
|
'gplusID', 'music', 'volume', 'aceConfig', 'employerAt', 'signedEmployerAgreement',
|
||||||
'emailSubscriptions', 'emails', 'activity'
|
'emailSubscriptions', 'emails', 'activity', 'stripeCustomerID'
|
||||||
]
|
]
|
||||||
UserSchema.statics.jsonSchema = jsonschema
|
UserSchema.statics.jsonSchema = jsonschema
|
||||||
UserSchema.statics.editableProperties = [
|
UserSchema.statics.editableProperties = [
|
||||||
|
|
|
@ -18,8 +18,10 @@ config.mongo =
|
||||||
mongoose_replica_string: process.env.COCO_MONGO_MONGOOSE_REPLICA_STRING or ''
|
mongoose_replica_string: process.env.COCO_MONGO_MONGOOSE_REPLICA_STRING or ''
|
||||||
|
|
||||||
config.apple =
|
config.apple =
|
||||||
#verifyURL: process.env.COCO_APPLE_VERIFY_URL or 'https://sandbox.itunes.apple.com/verifyReceipt'
|
verifyURL: process.env.COCO_APPLE_VERIFY_URL or 'https://sandbox.itunes.apple.com/verifyReceipt'
|
||||||
verifyURL: process.env.COCO_APPLE_VERIFY_URL or 'https://buy.itunes.apple.com/verifyReceipt'
|
|
||||||
|
config.stripe =
|
||||||
|
secretKey: process.env.COCO_STRIPE_SECRET_KEY or 'sk_test_MFnZHYD0ixBbiBuvTlLjl2da'
|
||||||
|
|
||||||
config.redis =
|
config.redis =
|
||||||
port: process.env.COCO_REDIS_PORT or 6379
|
port: process.env.COCO_REDIS_PORT or 6379
|
||||||
|
|
File diff suppressed because one or more lines are too long
Reference in a new issue