mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-23 15:48:11 -05:00
Add custom payments
Example: https://codecombat.com/account/invoices?a=21600&d=9%20monthly%20subscrip tions
This commit is contained in:
parent
942bab554a
commit
cd59b90e37
10 changed files with 332 additions and 16 deletions
|
@ -26,6 +26,7 @@ module.exports = class CocoRouter extends Backbone.Router
|
||||||
'account/profile': go('EmployersView') # Show the not-recruiting-now screen
|
'account/profile': go('EmployersView') # Show the not-recruiting-now screen
|
||||||
'account/payments': go('account/PaymentsView')
|
'account/payments': go('account/PaymentsView')
|
||||||
'account/subscription': go('account/SubscriptionView')
|
'account/subscription': go('account/SubscriptionView')
|
||||||
|
'account/invoices': go('account/InvoicesView')
|
||||||
|
|
||||||
'admin': go('admin/MainAdminView')
|
'admin': go('admin/MainAdminView')
|
||||||
'admin/candidates': go('admin/CandidatesView')
|
'admin/candidates': go('admin/CandidatesView')
|
||||||
|
|
|
@ -965,6 +965,7 @@
|
||||||
payments: "Payments"
|
payments: "Payments"
|
||||||
purchased: "Purchased"
|
purchased: "Purchased"
|
||||||
subscription: "Subscription"
|
subscription: "Subscription"
|
||||||
|
invoices: "Invoices"
|
||||||
service_apple: "Apple"
|
service_apple: "Apple"
|
||||||
service_web: "Web"
|
service_web: "Web"
|
||||||
paid_on: "Paid On"
|
paid_on: "Paid On"
|
||||||
|
@ -981,6 +982,16 @@
|
||||||
status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
|
status_unsubscribed_active: "You're not subscribed and won't be billed, but your account is still active for now."
|
||||||
status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"
|
status_unsubscribed: "Get access to new levels, heroes, items, and bonus gems with a CodeCombat subscription!"
|
||||||
|
|
||||||
|
account_invoices:
|
||||||
|
amount: "Amount in US dollars"
|
||||||
|
declined: "Your card was declined"
|
||||||
|
invalid_amount: "Please enter a US dollar amount."
|
||||||
|
not_logged_in: "Log in or create an account to access invoices."
|
||||||
|
pay: "Pay Invoice"
|
||||||
|
purchasing: "Purchasing..."
|
||||||
|
retrying: "Server error, retrying."
|
||||||
|
success: "Successfully paid. Thanks!"
|
||||||
|
|
||||||
loading_error:
|
loading_error:
|
||||||
could_not_load: "Error loading from server"
|
could_not_load: "Error loading from server"
|
||||||
connection_failure: "Connection failed."
|
connection_failure: "Connection failed."
|
||||||
|
|
|
@ -4,11 +4,12 @@ PaymentSchema = c.object({title: 'Payment', required: []}, {
|
||||||
purchaser: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ]) # in case of gifts
|
purchaser: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ]) # in case of gifts
|
||||||
recipient: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
|
recipient: c.objectId(links: [ {rel: 'extra', href: '/db/user/{($)}'} ])
|
||||||
|
|
||||||
service: { enum: ['stripe', 'ios', 'invoice']}
|
service: { enum: ['stripe', 'ios', 'external']}
|
||||||
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']}
|
productID: { enum: ['gems_5', 'gems_10', 'gems_20', 'custom']}
|
||||||
|
description: { type: 'string' }
|
||||||
|
|
||||||
ios: c.object({title: 'iOS IAP Data'}, {
|
ios: c.object({title: 'iOS IAP Data'}, {
|
||||||
transactionID: { type: 'string' }
|
transactionID: { type: 'string' }
|
||||||
|
|
9
app/styles/account/invoices-view.sass
Normal file
9
app/styles/account/invoices-view.sass
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#invoices-view
|
||||||
|
.form
|
||||||
|
#amount
|
||||||
|
width: 100px
|
||||||
|
#description
|
||||||
|
min-width: 400px
|
||||||
|
width: auto
|
||||||
|
#pay-button
|
||||||
|
width: auto
|
52
app/templates/account/invoices-view.jade
Normal file
52
app/templates/account/invoices-view.jade
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
extends /templates/base
|
||||||
|
|
||||||
|
block content
|
||||||
|
|
||||||
|
if me.get('anonymous')
|
||||||
|
p(data-i18n="account_invoices.not_logged_in")
|
||||||
|
else
|
||||||
|
ol.breadcrumb
|
||||||
|
li
|
||||||
|
a(href="/")
|
||||||
|
span.glyphicon.glyphicon-home
|
||||||
|
li
|
||||||
|
a(href="/account", data-i18n="nav.account")
|
||||||
|
li.active(data-i18n="account.invoices")
|
||||||
|
|
||||||
|
if state === 'purchasing'
|
||||||
|
.alert.alert-info(data-i18n="account_invoices.purchasing")
|
||||||
|
else if state === 'retrying'
|
||||||
|
#retrying-alert.alert.alert-danger(data-i18n="account_invoices.retrying")
|
||||||
|
else
|
||||||
|
.form
|
||||||
|
.form-group
|
||||||
|
label.control-label(for="amount", data-i18n="account_invoices.amount")
|
||||||
|
input#amount.form-control(name="amount", type="text", value="#{amount}")
|
||||||
|
.form-group
|
||||||
|
label.control-label(for="description", data-i18n="general.description")
|
||||||
|
input#description.form-control(name="description", type="text", value="#{description}")
|
||||||
|
button#pay-button.btn.form-control.btn-primary(data-i18n="account_invoices.pay")
|
||||||
|
|
||||||
|
br
|
||||||
|
|
||||||
|
if state === 'invoice_paid'
|
||||||
|
#declined-alert.alert.alert-success.alert-dismissible
|
||||||
|
button.close(type="button" data-dismiss="alert")
|
||||||
|
span(aria-hidden="true") ×
|
||||||
|
p= stateMessage
|
||||||
|
if state === 'validation_error'
|
||||||
|
#declined-alert.alert.alert-danger.alert-dismissible
|
||||||
|
button.close(type="button" data-dismiss="alert")
|
||||||
|
span(aria-hidden="true") ×
|
||||||
|
p= stateMessage
|
||||||
|
if state === 'declined'
|
||||||
|
#declined-alert.alert.alert-danger.alert-dismissible
|
||||||
|
span(data-i18n="account_invoices.declined")
|
||||||
|
button.close(type="button" data-dismiss="alert")
|
||||||
|
span(aria-hidden="true") ×
|
||||||
|
if state === 'unknown_error'
|
||||||
|
#error-alert.alert.alert-danger.alert-dismissible
|
||||||
|
button.close(type="button" data-dismiss="alert")
|
||||||
|
span(aria-hidden="true") ×
|
||||||
|
p(data-i18n="loading_error.unknown")
|
||||||
|
p= stateMessage
|
|
@ -23,3 +23,5 @@ block content
|
||||||
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
|
a.btn.btn-lg.btn-primary(href="/account/payments", data-i18n="account.payments")
|
||||||
li.list-group-item
|
li.list-group-item
|
||||||
a.btn.btn-lg.btn-primary(href="/account/subscription", data-i18n="account.subscription")
|
a.btn.btn-lg.btn-primary(href="/account/subscription", data-i18n="account.subscription")
|
||||||
|
li.list-group-item
|
||||||
|
a.btn.btn-lg.btn-primary(href="/account/invoices", data-i18n="account.invoices")
|
||||||
|
|
93
app/views/account/InvoicesView.coffee
Normal file
93
app/views/account/InvoicesView.coffee
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
RootView = require 'views/core/RootView'
|
||||||
|
template = require 'templates/account/invoices-view'
|
||||||
|
stripeHandler = require 'core/services/stripe'
|
||||||
|
utils = require 'core/utils'
|
||||||
|
|
||||||
|
# Internal amount and query params are in cents, display and web form input amount is in USD
|
||||||
|
|
||||||
|
# TODO: not supporting bitcoin currently because it pays without a confirmation step
|
||||||
|
|
||||||
|
module.exports = class InvoicesView extends RootView
|
||||||
|
id: "invoices-view"
|
||||||
|
template: template
|
||||||
|
|
||||||
|
events:
|
||||||
|
'click #pay-button': 'onPayButton'
|
||||||
|
|
||||||
|
subscriptions:
|
||||||
|
'stripe:received-token': 'onStripeReceivedToken'
|
||||||
|
|
||||||
|
constructor: (options) ->
|
||||||
|
super(options)
|
||||||
|
@amount = utils.getQueryVariable('a', 0)
|
||||||
|
@description = utils.getQueryVariable('d', '')
|
||||||
|
|
||||||
|
getRenderData: ->
|
||||||
|
c = super()
|
||||||
|
c.amount = (@amount / 100).toFixed(2)
|
||||||
|
c.description = @description
|
||||||
|
c.state = @state
|
||||||
|
c.stateMessage = @stateMessage
|
||||||
|
c
|
||||||
|
|
||||||
|
onPayButton: ->
|
||||||
|
@description = $('#description').val()
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
amount = parseFloat($('#amount').val())
|
||||||
|
if isNaN(amount) or amount <= 0
|
||||||
|
@state = 'validation_error'
|
||||||
|
@stateMessage = $.t('account_invoices.invalid_amount')
|
||||||
|
@amount = 0
|
||||||
|
@render()
|
||||||
|
return
|
||||||
|
|
||||||
|
@state = undefined
|
||||||
|
@stateMessage = undefined
|
||||||
|
@amount = parseInt(amount * 100)
|
||||||
|
|
||||||
|
# Show Stripe handler
|
||||||
|
application.tracker?.trackEvent 'Started invoice payment'
|
||||||
|
@timestampForPurchase = new Date().getTime()
|
||||||
|
stripeHandler.open
|
||||||
|
amount: @amount
|
||||||
|
description: @description
|
||||||
|
|
||||||
|
onStripeReceivedToken: (e) ->
|
||||||
|
data = {
|
||||||
|
amount: @amount
|
||||||
|
description: @description
|
||||||
|
stripe: {
|
||||||
|
token: e.token.id
|
||||||
|
timestamp: @timestampForPurchase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@state = 'purchasing'
|
||||||
|
@render()
|
||||||
|
jqxhr = $.post('/db/payment/custom', data)
|
||||||
|
|
||||||
|
jqxhr.done =>
|
||||||
|
application.tracker?.trackEvent 'Finished invoice payment',
|
||||||
|
amount: @amount
|
||||||
|
description: @description
|
||||||
|
|
||||||
|
# Show success UI
|
||||||
|
@state = 'invoice_paid'
|
||||||
|
@stateMessage = "$#{(@amount / 100).toFixed(2)} " + $.t('account_invoices.success')
|
||||||
|
@amount = 0
|
||||||
|
@description = ''
|
||||||
|
@render()
|
||||||
|
|
||||||
|
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'
|
||||||
|
@stateMessage = "#{jqxhr.status}: #{jqxhr.responseText}"
|
||||||
|
@render()
|
|
@ -13,7 +13,7 @@ var purchaserID = '54ed0ac0ca7f1c421c025b3d';
|
||||||
var endDate = '2015-06-01';
|
var endDate = '2015-06-01';
|
||||||
var gems = 10500;
|
var gems = 10500;
|
||||||
var amount = 1750;
|
var amount = 1750;
|
||||||
var service = 'invoice';
|
var service = 'external';
|
||||||
|
|
||||||
emails = emails.map(function(e) { return e.toLowerCase();});
|
emails = emails.map(function(e) { return e.toLowerCase();});
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,11 @@ products = {
|
||||||
gems: 25000
|
gems: 25000
|
||||||
id: 'gems_20'
|
id: 'gems_20'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
'custom': {
|
||||||
|
# amount expected in request body
|
||||||
|
id: 'custom'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PaymentHandler = class PaymentHandler extends Handler
|
PaymentHandler = class PaymentHandler extends Handler
|
||||||
|
@ -68,6 +73,9 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
stripeTimestamp = parseInt(req.body.stripe?.timestamp)
|
stripeTimestamp = parseInt(req.body.stripe?.timestamp)
|
||||||
productID = req.body.productID
|
productID = req.body.productID
|
||||||
|
|
||||||
|
if pathName is 'custom'
|
||||||
|
return @handleStripePaymentPost(req, res, stripeTimestamp, 'custom', stripeToken)
|
||||||
|
|
||||||
if not (appleReceipt or (stripeTimestamp and productID))
|
if not (appleReceipt or (stripeTimestamp and productID))
|
||||||
@logPaymentError(req, "Missing data. Apple? #{!!appleReceipt}. Stripe timestamp? #{!!stripeTimestamp}. Product id? #{!!productID}.")
|
@logPaymentError(req, "Missing data. Apple? #{!!appleReceipt}. Stripe timestamp? #{!!stripeTimestamp}. Product id? #{!!productID}.")
|
||||||
return @sendBadInputError(res, 'Need either apple.rawReceipt or stripe.timestamp and productID')
|
return @sendBadInputError(res, 'Need either apple.rawReceipt or stripe.timestamp and productID')
|
||||||
|
@ -85,13 +93,8 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
@logPaymentError(req, 'Missing apple transaction id')
|
@logPaymentError(req, 'Missing apple transaction id')
|
||||||
return @sendBadInputError(res, 'Apple purchase? Need to specify which transaction.')
|
return @sendBadInputError(res, 'Apple purchase? Need to specify which transaction.')
|
||||||
@handleApplePaymentPost(req, res, appleReceipt, appleTransactionID, appleLocalPrice)
|
@handleApplePaymentPost(req, res, appleReceipt, appleTransactionID, appleLocalPrice)
|
||||||
@onPostSuccess req
|
|
||||||
else
|
else
|
||||||
@handleStripePaymentPost(req, res, stripeTimestamp, productID, stripeToken)
|
@handleStripePaymentPost(req, res, stripeTimestamp, productID, stripeToken)
|
||||||
@onPostSuccess req
|
|
||||||
|
|
||||||
onPostSuccess: (req) ->
|
|
||||||
req.user?.saveActiveUser 'payment'
|
|
||||||
|
|
||||||
#- Apple payments
|
#- Apple payments
|
||||||
|
|
||||||
|
@ -162,7 +165,6 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#- Stripe payments
|
#- Stripe payments
|
||||||
|
|
||||||
handleStripePaymentPost: (req, res, timestamp, productID, token) ->
|
handleStripePaymentPost: (req, res, timestamp, productID, token) ->
|
||||||
|
@ -235,6 +237,8 @@ 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'
|
||||||
|
|
||||||
# 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) =>
|
||||||
if err
|
if err
|
||||||
|
@ -246,10 +250,12 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
chargeStripe: (req, res, product) ->
|
chargeStripe: (req, res, product) ->
|
||||||
|
amount = parseInt product.amount ? req.body.amount
|
||||||
|
return @sendError(res, 400, "Invalid amount.") if isNaN(amount)
|
||||||
|
|
||||||
stripe.charges.create({
|
stripe.charges.create({
|
||||||
amount: product.amount
|
amount: amount
|
||||||
currency: 'usd'
|
currency: 'usd'
|
||||||
customer: req.user.get('stripe')?.customerID
|
customer: req.user.get('stripe')?.customerID
|
||||||
metadata: {
|
metadata: {
|
||||||
|
@ -257,6 +263,7 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
userID: req.user._id + ''
|
userID: req.user._id + ''
|
||||||
gems: product.gems
|
gems: product.gems
|
||||||
timestamp: parseInt(req.body.stripe?.timestamp)
|
timestamp: parseInt(req.body.stripe?.timestamp)
|
||||||
|
description: req.body.description
|
||||||
}
|
}
|
||||||
receipt_email: req.user.get('email')
|
receipt_email: req.user.get('email')
|
||||||
}).then(
|
}).then(
|
||||||
|
@ -272,14 +279,14 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
@sendDatabaseError(res, 'Error charging card, please retry.'))
|
@sendDatabaseError(res, 'Error charging card, please retry.'))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
recordStripeCharge: (req, res, charge) ->
|
recordStripeCharge: (req, res, charge) ->
|
||||||
return @sendError(res, 500, 'Fake db error for testing.') if req.body.breakAfterCharging
|
return @sendError(res, 500, 'Fake db error for testing.') if req.body.breakAfterCharging
|
||||||
payment = @makeNewInstance(req)
|
payment = @makeNewInstance(req)
|
||||||
payment.set 'service', 'stripe'
|
payment.set 'service', 'stripe'
|
||||||
payment.set 'productID', charge.metadata.productID
|
payment.set 'productID', charge.metadata.productID
|
||||||
payment.set 'amount', parseInt(charge.amount)
|
payment.set 'amount', parseInt(charge.amount)
|
||||||
payment.set 'gems', parseInt(charge.metadata.gems)
|
payment.set 'gems', parseInt(charge.metadata.gems) if charge.metadata.gems
|
||||||
|
payment.set 'description', charge.metadata.description if charge.metadata.description
|
||||||
payment.set 'stripe', {
|
payment.set 'stripe', {
|
||||||
customerID: charge.customer
|
customerID: charge.customer
|
||||||
timestamp: parseInt(charge.metadata.timestamp)
|
timestamp: parseInt(charge.metadata.timestamp)
|
||||||
|
@ -291,9 +298,10 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
@logPaymentError(req, 'Invalid stripe payment object.')
|
@logPaymentError(req, 'Invalid stripe payment object.')
|
||||||
return @sendBadInputError(res, validation.errors)
|
return @sendBadInputError(res, validation.errors)
|
||||||
payment.save((err) =>
|
payment.save((err) =>
|
||||||
|
return @sendDatabaseError(res, err) if err
|
||||||
|
return @sendCreated(res, @formatEntity(req, payment)) if payment.productID is 'custom'
|
||||||
|
|
||||||
# Credit gems
|
# Credit gems
|
||||||
return @sendDatabaseError(res, err) if err
|
|
||||||
@incrementGemsFor(req.user, parseInt(charge.metadata.gems), (err) =>
|
@incrementGemsFor(req.user, parseInt(charge.metadata.gems), (err) =>
|
||||||
if err
|
if err
|
||||||
@logPaymentError(req, 'Stripe incr db error. '+err)
|
@logPaymentError(req, 'Stripe incr db error. '+err)
|
||||||
|
@ -302,7 +310,6 @@ PaymentHandler = class PaymentHandler extends Handler
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
#- Confirm all Stripe charges are recorded on our server
|
#- Confirm all Stripe charges are recorded on our server
|
||||||
|
|
||||||
checkStripeCharges: (req, res) ->
|
checkStripeCharges: (req, res) ->
|
||||||
|
|
|
@ -7,6 +7,7 @@ describe '/db/payment', ->
|
||||||
request = require 'request'
|
request = require 'request'
|
||||||
paymentURL = getURL('/db/payment')
|
paymentURL = getURL('/db/payment')
|
||||||
checkChargesURL = getURL('/db/payment/check-stripe-charges')
|
checkChargesURL = getURL('/db/payment/check-stripe-charges')
|
||||||
|
customPaymentURL = getURL('/db/payment/custom')
|
||||||
|
|
||||||
firstApplePayment = {
|
firstApplePayment = {
|
||||||
apple: {
|
apple: {
|
||||||
|
@ -76,7 +77,6 @@ describe '/db/payment', ->
|
||||||
)
|
)
|
||||||
|
|
||||||
describe 'posting Stripe purchases', ->
|
describe 'posting Stripe purchases', ->
|
||||||
|
|
||||||
stripe = require('stripe')(config.stripe.secretKey)
|
stripe = require('stripe')(config.stripe.secretKey)
|
||||||
|
|
||||||
charge = null
|
charge = null
|
||||||
|
@ -295,3 +295,143 @@ describe '/db/payment', ->
|
||||||
done()
|
done()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
describe '/db/payment/custom', ->
|
||||||
|
stripe = require('stripe')(config.stripe.secretKey)
|
||||||
|
|
||||||
|
it 'clears the db', (done) ->
|
||||||
|
clearModels [User, Payment], (err) ->
|
||||||
|
throw err if err
|
||||||
|
done()
|
||||||
|
|
||||||
|
it 'handles a custom purchase with description', (done) ->
|
||||||
|
timestamp = new Date().getTime()
|
||||||
|
amount = 5000
|
||||||
|
description = 'A sweet Coco t-shirt'
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
amount: amount
|
||||||
|
description: description
|
||||||
|
stripe: {
|
||||||
|
token: token.id
|
||||||
|
timestamp: timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.post {uri: customPaymentURL, 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.description).toEqual(description)
|
||||||
|
expect(body.amount).toEqual(amount)
|
||||||
|
expect(body.productID).toBe('custom')
|
||||||
|
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('stripe').customerID).toBe(body.stripe.customerID)
|
||||||
|
|
||||||
|
criteria =
|
||||||
|
recipient: user.get('_id')
|
||||||
|
purchaser: user.get('_id')
|
||||||
|
amount: amount
|
||||||
|
description: description
|
||||||
|
service: 'stripe'
|
||||||
|
"stripe.customerID": user.get('stripe').customerID
|
||||||
|
Payment.findOne criteria, (err, payment) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(payment).not.toBeNull()
|
||||||
|
done()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
it 'handles a custom purchase without description', (done) ->
|
||||||
|
timestamp = new Date().getTime()
|
||||||
|
amount = 73000
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
amount: amount
|
||||||
|
stripe: {
|
||||||
|
token: token.id
|
||||||
|
timestamp: timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.post {uri: customPaymentURL, 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.amount).toEqual(amount)
|
||||||
|
expect(body.productID).toBe('custom')
|
||||||
|
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('stripe').customerID).toBe(body.stripe.customerID)
|
||||||
|
|
||||||
|
criteria =
|
||||||
|
recipient: user.get('_id')
|
||||||
|
purchaser: user.get('_id')
|
||||||
|
amount: amount
|
||||||
|
service: 'stripe'
|
||||||
|
"stripe.customerID": user.get('stripe').customerID
|
||||||
|
Payment.findOne criteria, (err, payment) ->
|
||||||
|
expect(err).toBeNull()
|
||||||
|
expect(payment).not.toBeNull()
|
||||||
|
done()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
it 'handles a custom purchase with invalid amount', (done) ->
|
||||||
|
timestamp = new Date().getTime()
|
||||||
|
amount = 'free?'
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
amount: amount
|
||||||
|
stripe: {
|
||||||
|
token: token.id
|
||||||
|
timestamp: timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.post {uri: customPaymentURL, json: data }, (err, res, body) ->
|
||||||
|
expect(res.statusCode).toBe(400)
|
||||||
|
done()
|
||||||
|
)
|
||||||
|
|
||||||
|
it 'handles a custom purchase with no amount', (done) ->
|
||||||
|
timestamp = new Date().getTime()
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
stripe: {
|
||||||
|
token: token.id
|
||||||
|
timestamp: timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.post {uri: customPaymentURL, json: data }, (err, res, body) ->
|
||||||
|
expect(res.statusCode).toBe(400)
|
||||||
|
done()
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue