2014-11-11 20:40:03 -05:00
|
|
|
testReceipt = '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
|
|
|
|
|
2014-11-17 18:15:02 -05:00
|
|
|
config = require '../../../server_config'
|
2014-11-11 20:40:03 -05:00
|
|
|
require '../common'
|
|
|
|
|
|
|
|
describe '/db/payment', ->
|
|
|
|
request = require 'request'
|
|
|
|
paymentURL = getURL('/db/payment')
|
|
|
|
|
|
|
|
firstApplePayment = {
|
|
|
|
apple: {
|
|
|
|
rawReceipt: testReceipt
|
|
|
|
transactionID: '1000000131274439'
|
|
|
|
localPrice: '$5.00'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
secondApplePayment = {
|
|
|
|
apple: {
|
|
|
|
rawReceipt: testReceipt
|
|
|
|
transactionID: '1000000131283608'
|
|
|
|
localPrice: '$10.00'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
paymentCreated = null
|
|
|
|
|
|
|
|
it 'clears the db first', (done) ->
|
|
|
|
clearModels [User, Payment], (err) ->
|
|
|
|
throw err if err
|
|
|
|
done()
|
|
|
|
|
|
|
|
describe 'posting Apple IAPs', ->
|
2014-12-04 16:07:00 -05:00
|
|
|
|
|
|
|
it 'denies anonymous users trying to pay', (done) ->
|
|
|
|
request.get getURL('/auth/whoami'), ->
|
|
|
|
request.post {uri: paymentURL, json: firstApplePayment}, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe 403
|
|
|
|
done()
|
2014-11-11 20:40:03 -05:00
|
|
|
|
|
|
|
it 'creates a payment object and credits gems to the user', (done) ->
|
|
|
|
loginJoe ->
|
|
|
|
request.post {uri: paymentURL, json: firstApplePayment}, (err, res, body) ->
|
|
|
|
paymentCreated = body?._id
|
|
|
|
expect(res.statusCode).toBe 201
|
|
|
|
User.findOne({name:'Joe'}).exec(err, (err, user) ->
|
|
|
|
expect(user.get('purchased').gems).toBe(5000)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
|
|
|
|
it 'is idempotent', (done) ->
|
|
|
|
loginJoe ->
|
|
|
|
request.post {uri: paymentURL, json: firstApplePayment}, (err, res, body) ->
|
|
|
|
expect(body._id is paymentCreated).toBe(true)
|
|
|
|
expect(res.statusCode).toBe 200
|
|
|
|
User.findOne({name:'Joe'}).exec(err, (err, user) ->
|
|
|
|
expect(user.get('purchased').gems).toBe(5000)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
|
2014-11-21 13:34:30 -05:00
|
|
|
it 'prevents other users from reusing payment receipts', (done) ->
|
|
|
|
loginSam ->
|
|
|
|
request.post {uri: paymentURL, json: firstApplePayment}, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe 403
|
|
|
|
done()
|
|
|
|
|
2014-11-11 20:40:03 -05:00
|
|
|
it 'processes only the transactionID that is given', (done) ->
|
|
|
|
loginJoe ->
|
|
|
|
request.post {uri: paymentURL, json: secondApplePayment}, (err, res, body) ->
|
|
|
|
expect(body._id is paymentCreated).toBe(false)
|
|
|
|
expect(res.statusCode).toBe 201
|
|
|
|
User.findOne({name:'Joe'}).exec(err, (err, user) ->
|
|
|
|
expect(user.get('purchased').gems).toBe(16000)
|
|
|
|
done()
|
|
|
|
)
|
2014-11-17 18:15:02 -05:00
|
|
|
|
|
|
|
describe 'posting Stripe purchases', ->
|
2014-11-11 20:40:03 -05:00
|
|
|
|
2014-11-17 18:15:02 -05:00
|
|
|
stripe = require('stripe')(config.stripe.secretKey)
|
2014-11-11 20:40:03 -05:00
|
|
|
|
2014-11-17 18:15:02 -05:00
|
|
|
charge = null
|
|
|
|
joeID = null
|
|
|
|
timestamp = new Date().getTime()
|
|
|
|
stripeTokenID = null
|
2014-12-04 20:41:17 -05:00
|
|
|
joeData = null
|
2014-11-11 20:40:03 -05:00
|
|
|
|
2014-11-17 18:15:02 -05:00
|
|
|
it 'clears the db first', (done) ->
|
|
|
|
clearModels [User, Payment], (err) ->
|
|
|
|
throw err if err
|
|
|
|
done()
|
|
|
|
|
|
|
|
it 'handles a purchase', (done) ->
|
|
|
|
stripe.tokens.create({
|
|
|
|
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
stripeTokenID = token.id
|
|
|
|
loginJoe (joe) ->
|
|
|
|
joeID = joe.get('_id')+''
|
|
|
|
data = {
|
|
|
|
productID: 'gems_5'
|
|
|
|
stripe: {
|
|
|
|
token: token.id
|
|
|
|
timestamp: timestamp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe 201
|
|
|
|
expect(body.stripe.chargeID).toBeDefined()
|
|
|
|
expect(body.stripe.timestamp).toBe(timestamp)
|
|
|
|
expect(body.stripe.customerID).toBeDefined()
|
|
|
|
expect(body.gems).toBe(5000)
|
|
|
|
expect(body.amount).toBe(499)
|
|
|
|
expect(body.productID).toBe('gems_5')
|
|
|
|
expect(body.service).toBe('stripe')
|
|
|
|
expect(body.recipient).toBe(joeID)
|
|
|
|
expect(body.purchaser).toBe(joeID)
|
|
|
|
User.findById(joe.get('_id'), (err, user) ->
|
|
|
|
expect(user.get('purchased').gems).toBe(5000)
|
2014-12-02 14:47:15 -05:00
|
|
|
expect(user.get('stripe').customerID).toBe(body.stripe.customerID)
|
2014-11-17 18:15:02 -05:00
|
|
|
done()
|
|
|
|
)
|
|
|
|
)
|
2014-11-11 20:40:03 -05:00
|
|
|
|
2014-11-17 18:15:02 -05:00
|
|
|
it 'ignores repeated purchases', (done) ->
|
|
|
|
data = { productID: 'gems_5', stripe: { token: stripeTokenID, timestamp: timestamp } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe 200
|
|
|
|
Payment.count({}, (err, count) ->
|
|
|
|
expect(count).toBe(1)
|
|
|
|
User.findById(joeID, (err, user) ->
|
|
|
|
expect(user.get('purchased').gems).toBe(5000)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
)
|
2014-12-04 20:41:17 -05:00
|
|
|
|
|
|
|
it 'allows a new charge on the existing customer', (done) ->
|
|
|
|
data = { productID: 'gems_5', stripe: { timestamp: new Date().getTime() } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe 201
|
|
|
|
Payment.count {}, (err, count) ->
|
|
|
|
expect(count).toBe(2)
|
|
|
|
User.findById joeID, (err, user) ->
|
|
|
|
joeData = user.toObject()
|
|
|
|
expect(user.get('purchased').gems).toBe(10000)
|
|
|
|
done()
|
|
|
|
|
|
|
|
it "updates the customer's card when you submit a new token", (done) ->
|
|
|
|
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
|
|
|
originalCustomerID = customer.id
|
|
|
|
originalCardID = customer.cards.data[0].id
|
|
|
|
stripe.tokens.create {
|
|
|
|
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
data = { productID: 'gems_5', stripe: { timestamp: new Date().getTime(), token: token.id } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe(201)
|
|
|
|
User.findById joeID, (err, user) ->
|
|
|
|
joeData = user.toObject()
|
|
|
|
expect(joeData.stripe.customerID).toBe(originalCustomerID)
|
|
|
|
stripe.customers.retrieve joeData.stripe.customerID, (err, customer) ->
|
|
|
|
expect(customer.cards.data[0].id).not.toBe(originalCardID)
|
|
|
|
done()
|
2014-11-17 18:15:02 -05:00
|
|
|
|
|
|
|
it 'clears the db', (done) ->
|
|
|
|
clearModels [User, Payment], (err) ->
|
|
|
|
throw err if err
|
|
|
|
done()
|
|
|
|
|
|
|
|
it 'recovers from breaking between charge and document creation', (done) ->
|
|
|
|
stripe.tokens.create({
|
|
|
|
card: { number: '4242424242424242', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
|
|
|
|
data = {
|
|
|
|
productID: 'gems_5'
|
|
|
|
stripe: { token: token.id, timestamp: timestamp }
|
|
|
|
breakAfterCharging: true
|
|
|
|
}
|
|
|
|
|
|
|
|
loginJoe (joe) ->
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe 500
|
|
|
|
|
|
|
|
data = _.omit data, 'breakAfterCharging'
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe 201
|
|
|
|
|
|
|
|
Payment.count({}, (err, count) ->
|
|
|
|
expect(count).toBe(1)
|
|
|
|
User.findById(joe.get('_id'), (err, user) ->
|
|
|
|
expect(user.get('purchased').gems).toBe(5000)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
it 'clears the db', (done) ->
|
|
|
|
clearModels [User, Payment], (err) ->
|
|
|
|
throw err if err
|
|
|
|
done()
|
2014-11-11 20:40:03 -05:00
|
|
|
|
2014-11-17 18:15:02 -05:00
|
|
|
# Testing card numbers are here: https://stripe.com/docs/testing
|
|
|
|
|
|
|
|
it 'handles card that attaches to customer but fails to be charged', (done) ->
|
|
|
|
stripe.tokens.create({
|
|
|
|
card: { number: '4000000000000341', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
loginJoe (joe) ->
|
|
|
|
timestamp = new Date().getTime()
|
|
|
|
data = { productID: 'gems_5', stripe: { token: token.id, timestamp: timestamp } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe(402)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
|
|
|
|
it 'handles card that always is declined with card_declined code', (done) ->
|
|
|
|
stripe.tokens.create({
|
|
|
|
card: { number: '4000000000000002', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
loginJoe (joe) ->
|
|
|
|
timestamp = new Date().getTime()
|
|
|
|
data = { productID: 'gems_5', stripe: { token: token.id, timestamp: timestamp } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe(402)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
|
|
|
|
it 'handles card that always is declined with incorrect_cvc code', (done) ->
|
|
|
|
stripe.tokens.create({
|
|
|
|
card: { number: '4000000000000127', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
loginJoe (joe) ->
|
|
|
|
timestamp = new Date().getTime()
|
|
|
|
data = { productID: 'gems_5', stripe: { token: token.id, timestamp: timestamp } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe(402)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
|
|
|
|
it 'handles card that always is declined with expired_card code', (done) ->
|
|
|
|
stripe.tokens.create({
|
|
|
|
card: { number: '4000000000000069', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
loginJoe (joe) ->
|
|
|
|
timestamp = new Date().getTime()
|
|
|
|
data = { productID: 'gems_5', stripe: { token: token.id, timestamp: timestamp } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe(402)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
|
|
|
|
it 'handles card that always is declined with processing_error code', (done) ->
|
|
|
|
stripe.tokens.create({
|
|
|
|
card: { number: '4000000000000119', exp_month: 12, exp_year: 2020, cvc: '123' }
|
|
|
|
}, (err, token) ->
|
|
|
|
loginJoe (joe) ->
|
|
|
|
timestamp = new Date().getTime()
|
|
|
|
data = { productID: 'gems_5', stripe: { token: token.id, timestamp: timestamp } }
|
|
|
|
request.post {uri: paymentURL, json: data }, (err, res, body) ->
|
|
|
|
expect(res.statusCode).toBe(402)
|
|
|
|
done()
|
|
|
|
)
|
|
|
|
|
|
|
|
|