codecombat/test/server/functional/payment.spec.coffee

264 lines
17 KiB
CoffeeScript
Raw Normal View History

testReceipt = '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
config = require '../../../server_config'
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', ->
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()
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()
)
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()
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()
)
describe 'posting Stripe purchases', ->
stripe = require('stripe')(config.stripe.secretKey)
charge = null
joeID = null
timestamp = new Date().getTime()
stripeTokenID = null
joeData = null
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)
expect(user.get('stripe').customerID).toBe(body.stripe.customerID)
done()
)
)
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()
)
)
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()
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()
# 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()
)