2016-04-06 13:56:06 -04:00
Payment = require ' ./../models/Payment '
Prepaid = require ' ../models/Prepaid '
2015-12-14 14:10:37 -05:00
Product = require ' ../models/Product '
2016-04-06 13:56:06 -04:00
User = require ' ../models/User '
2014-11-11 20:40:03 -05:00
Handler = require ' ../commons/Handler '
{ handlers } = require ' ../commons/mapping '
mongoose = require ' mongoose '
log = require ' winston '
sendwithus = require ' ../sendwithus '
2016-03-18 20:05:21 -04:00
slack = require ' ../slack '
2014-11-11 20:40:03 -05:00
config = require ' ../../server_config '
request = require ' request '
2014-11-17 18:15:02 -05:00
async = require ' async '
2015-12-09 17:27:10 -05:00
apple_utils = require ' ../lib/apple_utils '
2014-11-11 20:40:03 -05:00
PaymentHandler = class PaymentHandler extends Handler
modelClass: Payment
editableProperties: [ ]
postEditableProperties: [ ' purchased ' ]
jsonSchema: require ' ../../app/schemas/models/payment.schema '
2015-02-04 19:17:53 -05:00
2014-11-29 13:44:58 -05:00
get: (req, res) ->
return res . send ( [ ] ) unless req . user
q = Payment . find ( { recipient : req . user . _id } )
q . exec ( (err, payments) ->
return @ sendDatabaseError ( res , err ) if err
res . send ( payments )
)
2015-02-04 19:17:53 -05:00
2016-02-23 17:04:35 -05:00
getByRelationship: (req, res, args...) ->
relationship = args [ 1 ]
return @ getSchoolSalesAPI ( req , res ) if relationship is ' school_sales '
super arguments . . .
2014-11-29 13:44:58 -05:00
logPaymentError: (req, msg) ->
console . warn " Payment Error: #{ req . user . get ( ' slug ' ) } ( #{ req . user . _id } ): ' #{ msg } ' "
2014-11-11 20:40:03 -05:00
makeNewInstance: (req) ->
payment = super ( req )
payment . set ' purchaser ' , req . user . _id
payment . set ' recipient ' , req . user . _id
payment . set ' created ' , new Date ( ) . toISOString ( )
payment
2014-11-25 16:20:09 -05:00
2016-02-23 17:04:35 -05:00
getSchoolSalesAPI: (req, res, code) ->
2016-02-24 09:24:58 -05:00
return @ sendUnauthorizedError ( res ) unless req . user ? . isAdmin ( )
2016-02-23 17:04:35 -05:00
userIDs = [ ] ;
Payment . find ( { } , { amount: 1 , created: 1 , description: 1 , prepaidID: 1 , productID: 1 , purchaser: 1 , service: 1 } ) . exec (err, payments) =>
return @ sendDatabaseError ( res , err ) if err
schoolSales = [ ]
prepaidIDs = [ ]
prepaidPaymentMap = { }
for payment in payments
continue unless payment . get ( ' amount ' ) ? and payment . get ( ' amount ' ) > 0
unless created = payment . get ( ' created ' )
created = payment . get ( ' _id ' ) . getTimestamp ( )
description = payment . get ( ' description ' ) ? ' '
if prepaidID = payment . get ( ' prepaidID ' )
unless prepaidPaymentMap [ prepaidID . valueOf ( ) ]
prepaidPaymentMap [ prepaidID . valueOf ( ) ] = { _id: payment . get ( ' _id ' ) . valueOf ( ) , amount: payment . get ( ' amount ' ) , created: created , description: description , userID: payment . get ( ' purchaser ' ) . valueOf ( ) , prepaidID: prepaidID . valueOf ( ) }
prepaidIDs . push ( prepaidID )
userIDs . push ( payment . get ( ' purchaser ' ) )
else if payment . get ( ' productID ' ) is ' custom ' or payment . get ( ' service ' ) is ' external ' or payment . get ( ' service ' ) is ' invoice '
schoolSales . push ( { _id: payment . get ( ' _id ' ) . valueOf ( ) , amount: payment . get ( ' amount ' ) , created: created , description: description , userID: payment . get ( ' purchaser ' ) . valueOf ( ) } )
userIDs . push ( payment . get ( ' purchaser ' ) )
Prepaid . find ( { $and: [ { _id: { $in: prepaidIDs } } , { type: ' course ' } ] } , { _id: 1 } ) . exec (err, prepaids) =>
return @ sendDatabaseError ( res , err ) if err
for prepaid in prepaids
schoolSales . push ( prepaidPaymentMap [ prepaid . get ( ' _id ' ) . valueOf ( ) ] )
User . find ( { _id: { $in: userIDs } } ) . exec (err, users) =>
return @ sendDatabaseError ( res , err ) if err
userMap = { }
for user in users
userMap [ user . get ( ' _id ' ) . valueOf ( ) ] = user
for schoolSale in schoolSales
schoolSale.user = userMap [ schoolSale . userID ] ? . toObject ( )
@ sendSuccess ( res , schoolSales )
2014-12-12 18:27:58 -05:00
post: (req, res, pathName) ->
if pathName is ' check-stripe-charges '
return @ checkStripeCharges ( req , res )
2015-02-04 19:17:53 -05:00
2014-12-04 16:07:00 -05:00
if ( not req . user ) or req . user . isAnonymous ( )
return @ sendForbiddenError ( res )
2015-02-04 19:17:53 -05:00
2014-11-11 20:40:03 -05:00
appleReceipt = req . body . apple ? . rawReceipt
appleTransactionID = req . body . apple ? . transactionID
appleLocalPrice = req . body . apple ? . localPrice
stripeToken = req . body . stripe ? . token
stripeTimestamp = parseInt ( req . body . stripe ? . timestamp )
2014-11-17 18:15:02 -05:00
productID = req . body . productID
2014-11-25 16:20:09 -05:00
2015-03-04 18:40:32 -05:00
if pathName is ' custom '
return @ handleStripePaymentPost ( req , res , stripeTimestamp , ' custom ' , stripeToken )
2014-11-17 18:15:02 -05:00
if not ( appleReceipt or ( stripeTimestamp and productID ) )
2014-11-29 13:44:58 -05:00
@ logPaymentError ( req , " Missing data. Apple? #{ ! ! appleReceipt } . Stripe timestamp? #{ ! ! stripeTimestamp } . Product id? #{ ! ! productID } . " )
2014-11-17 18:15:02 -05:00
return @ sendBadInputError ( res , ' Need either apple.rawReceipt or stripe.timestamp and productID ' )
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
if stripeTimestamp and not productID
2014-11-29 13:44:58 -05:00
@ logPaymentError ( req , ' Missing stripe productID ' )
2014-11-17 18:15:02 -05:00
return @ sendBadInputError ( res , ' Need productID if paying with Stripe. ' )
2014-12-02 14:47:15 -05:00
if stripeTimestamp and ( not stripeToken ) and ( not req . user . get ( ' stripe ' ) ? . customerID )
2014-11-29 13:44:58 -05:00
@ logPaymentError ( req , ' Missing stripe token ' )
2014-11-17 18:15:02 -05:00
return @ sendBadInputError ( res , ' Need stripe.token if new customer. ' )
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
if appleReceipt
if not appleTransactionID
2014-11-29 13:44:58 -05:00
@ logPaymentError ( req , ' Missing apple transaction id ' )
2014-11-11 20:40:03 -05:00
return @ sendBadInputError ( res , ' Apple purchase? Need to specify which transaction. ' )
@ handleApplePaymentPost ( req , res , appleReceipt , appleTransactionID , appleLocalPrice )
else
2014-11-17 18:15:02 -05:00
@ handleStripePaymentPost ( req , res , stripeTimestamp , productID , stripeToken )
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
#- Apple payments
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
handleApplePaymentPost: (req, res, receipt, transactionID, localPrice) ->
2014-11-25 16:20:09 -05:00
#- verify receipt with Apple
2015-12-09 17:27:10 -05:00
apple_utils . verifyReceipt ( receipt , (err, body) =>
2014-11-11 20:40:03 -05:00
if err or not body ? . receipt ? . in_app or ( not body ? . bundle_id is ' com.codecombat.CodeCombat ' )
console . warn ' apple receipt error? ' , err , body
2014-11-29 13:44:58 -05:00
@ logPaymentError ( req , ' Unable to verify apple receipt ' )
2014-11-11 20:40:03 -05:00
@ sendBadInputError ( res , ' Unable to verify Apple receipt. ' )
return
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
transaction = _ . find body . receipt . in_app , { transaction_id: transactionID }
2014-11-29 13:44:58 -05:00
unless transaction
@ logPaymentError ( req , ' Missing transaction given id. ' )
return @ sendBadInputError ( res , ' Invalid transactionID. ' )
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
#- Check existence
transactionID = transaction . transaction_id
2014-11-21 13:34:30 -05:00
criteria = { ' ios.transactionID ' : transactionID }
2014-11-11 20:40:03 -05:00
Payment . findOne ( criteria ) . exec ( (err, payment) =>
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
if payment
2014-11-21 13:34:30 -05:00
unless payment . get ( ' recipient ' ) . equals ( req . user . _id )
2014-11-29 13:44:58 -05:00
@ logPaymentError ( req , ' Cross user apple payment. ' )
2014-11-21 13:34:30 -05:00
return @ sendForbiddenError ( res )
2014-11-11 20:40:03 -05:00
@ recalculateGemsFor ( req . user , (err) =>
2014-11-29 13:44:58 -05:00
if err
@ logPaymentError ( req , ' Apple recalc db error. ' + err )
return @ sendDatabaseError ( res , err )
2014-11-11 20:40:03 -05:00
@ sendSuccess ( res , @ formatEntity ( req , payment ) )
)
return
payment = @ makeNewInstance ( req )
payment . set ' service ' , ' ios '
2015-12-14 14:10:37 -05:00
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
}
2016-04-08 12:24:38 -04:00
2015-12-14 14:10:37 -05:00
validation = @ validateDocumentInput ( payment . toObject ( ) )
if validation . valid is false
@ logPaymentError ( req , ' Invalid apple payment object. ' )
return @ sendBadInputError ( res , validation . errors )
2016-04-08 12:24:38 -04:00
2015-12-14 14:10:37 -05:00
payment . save ( (err) =>
2014-11-29 13:44:58 -05:00
if err
2015-12-14 14:10:37 -05:00
@ logPaymentError ( req , ' Apple payment save error. ' + err )
2014-11-29 13:44:58 -05:00
return @ sendDatabaseError ( res , err )
2015-12-14 14:10:37 -05:00
@ incrementGemsFor ( req . user , product . get ( ' gems ' ) , (err) =>
if err
@ logPaymentError ( req , ' Apple incr db error. ' + err )
return @ sendDatabaseError ( res , err )
2016-03-18 20:05:21 -04:00
@ sendPaymentSlackMessage user: req . user , payment: payment
2015-12-14 14:10:37 -05:00
@ sendCreated ( res , @ formatEntity ( req , payment ) )
)
2014-11-11 20:40:03 -05:00
)
)
)
2014-11-17 18:15:02 -05:00
#- Stripe payments
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
handleStripePaymentPost: (req, res, timestamp, productID, token) ->
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
# First, make sure we save the payment info as a Customer object, if we haven't already.
2014-12-04 20:41:17 -05:00
if token
customerID = req . user . get ( ' stripe ' ) ? . customerID
2015-02-04 19:17:53 -05:00
2014-12-04 20:41:17 -05:00
if customerID
# old customer, new token. Save it.
stripe . customers . update customerID , { card: token } , (err, customer) =>
@ beginStripePayment ( req , res , timestamp , productID )
2015-02-04 19:17:53 -05:00
2014-12-04 20:41:17 -05:00
else
newCustomer = {
card: token
email: req . user . get ( ' email ' )
metadata: { id: req . user . _id + ' ' , slug: req . user . get ( ' slug ' ) }
2014-12-03 19:35:53 -05:00
}
2015-02-04 19:17:53 -05:00
2014-12-04 20:41:17 -05:00
stripe . customers . create newCustomer , (err, customer) =>
2014-11-29 13:44:58 -05:00
if err
2014-12-04 20:41:17 -05:00
@ logPaymentError ( req , ' Stripe customer creation error. ' + err )
2014-11-29 13:44:58 -05:00
return @ sendDatabaseError ( res , err )
2015-02-04 19:17:53 -05:00
2014-12-04 20:41:17 -05:00
stripeInfo = _ . cloneDeep ( req . user . get ( ' stripe ' ) ? { } )
stripeInfo.customerID = customer . id
req . user . set ( ' stripe ' , stripeInfo )
req . user . save (err) =>
if err
@ logPaymentError ( req , ' Stripe customer id save db error. ' + err )
return @ sendDatabaseError ( res , err )
@ beginStripePayment ( req , res , timestamp , productID )
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
else
@ beginStripePayment ( req , res , timestamp , productID )
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
beginStripePayment: (req, res, timestamp, productID) ->
async . parallel ( [
( (callback) ->
criteria = { recipient: req . user . _id , ' stripe.timestamp ' : timestamp }
Payment . findOne ( criteria ) . exec ( (err, payment) =>
callback ( err , payment )
)
) ,
( (callback) ->
2014-12-02 14:47:15 -05:00
stripe . charges . list ( { customer: req . user . get ( ' stripe ' ) ? . customerID } , (err, recentCharges) =>
2014-11-17 18:15:02 -05:00
return callback ( err ) if err
charge = _ . find recentCharges . data , (c) -> c . metadata . timestamp is timestamp
callback ( null , charge )
)
2015-12-14 14:10:37 -05:00
) ,
( (callback) ->
Product . findOne ( { name: productID } ) . exec (err, product) =>
callback ( err , product )
2014-11-17 18:15:02 -05:00
)
] ,
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
( (err, results) =>
2014-11-29 13:44:58 -05:00
if err
@ logPaymentError ( req , ' Stripe async load db error. ' + err )
2015-02-04 19:17:53 -05:00
return @ sendDatabaseError ( res , err )
2015-12-14 14:10:37 -05:00
[ payment , charge , product ] = results
2016-04-08 12:24:38 -04:00
2015-12-14 14:10:37 -05:00
if not product
return @ sendNotFoundError ( res , ' could not find product with id ' + productID )
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
if not ( payment or charge )
# Proceed normally from the beginning
2014-12-12 18:27:58 -05:00
@ chargeStripe ( req , res , product )
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
else if charge and not payment
# Initialized Payment. Start from charging.
2014-12-12 18:27:58 -05:00
@ recordStripeCharge ( req , res , charge )
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
else
2015-12-14 14:10:37 -05:00
return @ sendSuccess ( res , @ formatEntity ( req , payment ) ) if product . get ( ' name ' ) is ' custom '
2015-03-04 18:40:32 -05:00
2014-11-17 18:15:02 -05:00
# Charged Stripe and recorded it. Recalculate gems to make sure credited the purchase.
@ recalculateGemsFor ( req . user , (err) =>
2014-11-29 13:44:58 -05:00
if err
@ logPaymentError ( req , ' Stripe recalc db error. ' + err )
return @ sendDatabaseError ( res , err )
2016-03-18 20:05:21 -04:00
@ sendPaymentSlackMessage user: req . user , payment: payment
2014-11-17 18:15:02 -05:00
@ sendSuccess ( res , @ formatEntity ( req , payment ) )
)
)
)
2014-12-12 18:27:58 -05:00
chargeStripe: (req, res, product) ->
2015-12-14 14:10:37 -05:00
amount = parseInt product . get ( ' amount ' ) ? req . body . amount
2015-03-04 18:40:32 -05:00
return @ sendError ( res , 400 , " Invalid amount. " ) if isNaN ( amount )
2014-11-17 18:15:02 -05:00
stripe . charges . create ( {
2015-03-04 18:40:32 -05:00
amount: amount
2014-11-17 18:15:02 -05:00
currency: ' usd '
2014-12-02 14:47:15 -05:00
customer: req . user . get ( ' stripe ' ) ? . customerID
2014-11-17 18:15:02 -05:00
metadata: {
2015-12-14 14:10:37 -05:00
productID: product . get ( ' name ' )
2014-11-17 18:15:02 -05:00
userID: req . user . _id + ' '
2015-12-14 14:10:37 -05:00
gems: product . get ( ' gems ' )
2014-11-17 18:15:02 -05:00
timestamp: parseInt ( req . body . stripe ? . timestamp )
2015-03-04 18:40:32 -05:00
description: req . body . description
2014-11-17 18:15:02 -05:00
}
receipt_email: req . user . get ( ' email ' )
2015-06-05 17:58:35 -04:00
statement_descriptor: ' CODECOMBAT.COM '
2014-11-17 18:15:02 -05:00
} ) . then (
# success case
2014-12-12 18:27:58 -05:00
( (charge) => @ recordStripeCharge ( req , res , charge ) ) ,
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
# error case
( (err) =>
if err . type in [ ' StripeCardError ' , ' StripeInvalidRequestError ' ]
@ sendError ( res , 402 , err . message )
else
2014-11-29 13:44:58 -05:00
@ logPaymentError ( req , ' Stripe charge error. ' + err )
2014-11-17 18:15:02 -05:00
@ sendDatabaseError ( res , ' Error charging card, please retry. ' ) )
)
2014-11-25 16:20:09 -05:00
2014-12-12 18:27:58 -05:00
recordStripeCharge: (req, res, charge) ->
2014-11-17 18:15:02 -05:00
return @ sendError ( res , 500 , ' Fake db error for testing. ' ) if req . body . breakAfterCharging
payment = @ makeNewInstance ( req )
payment . set ' service ' , ' stripe '
2014-12-12 18:27:58 -05:00
payment . set ' productID ' , charge . metadata . productID
payment . set ' amount ' , parseInt ( charge . amount )
2015-03-04 18:40:32 -05:00
payment . set ' gems ' , parseInt ( charge . metadata . gems ) if charge . metadata . gems
payment . set ' description ' , charge . metadata . description if charge . metadata . description
2014-11-17 18:15:02 -05:00
payment . set ' stripe ' , {
2014-12-12 18:27:58 -05:00
customerID: charge . customer
timestamp: parseInt ( charge . metadata . timestamp )
2014-11-17 18:15:02 -05:00
chargeID: charge . id
}
2015-02-04 19:17:53 -05:00
2014-11-17 18:15:02 -05:00
validation = @ validateDocumentInput ( payment . toObject ( ) )
2014-11-29 13:44:58 -05:00
if validation . valid is false
@ logPaymentError ( req , ' Invalid stripe payment object. ' )
return @ sendBadInputError ( res , validation . errors )
2014-11-17 18:15:02 -05:00
payment . save ( (err) =>
2015-03-04 18:40:32 -05:00
return @ sendDatabaseError ( res , err ) if err
return @ sendCreated ( res , @ formatEntity ( req , payment ) ) if payment . productID is ' custom '
2014-11-17 18:15:02 -05:00
# Credit gems
2014-12-12 18:27:58 -05:00
@ incrementGemsFor ( req . user , parseInt ( charge . metadata . gems ) , (err) =>
2014-11-29 13:44:58 -05:00
if err
@ logPaymentError ( req , ' Stripe incr db error. ' + err )
return @ sendDatabaseError ( res , err )
2016-03-18 20:05:21 -04:00
@ sendPaymentSlackMessage user: req . user , payment: payment
2014-11-17 18:15:02 -05:00
@ sendCreated ( res , @ formatEntity ( req , payment ) )
)
)
2014-12-12 18:27:58 -05:00
#- Confirm all Stripe charges are recorded on our server
2015-02-04 19:17:53 -05:00
2014-12-12 18:27:58 -05:00
checkStripeCharges: (req, res) ->
return @ sendSuccess ( res ) unless customerID = req . user . get ( ' stripe ' ) ? . customerID
async . parallel ( [
( (callback) ->
2015-02-09 11:31:44 -05:00
criteria = { recipient: req . user . _id , ' stripe.invoiceID ' : { $exists: false } , ' ios.transactionID ' : { $exists: false } }
2014-12-12 18:27:58 -05:00
Payment . find ( criteria ) . limit ( 100 ) . sort ( { _id : - 1 } ) . exec ( (err, payments) =>
callback ( err , payments )
)
) ,
( (callback) ->
stripe . charges . list ( { customer: customerID , limit: 100 } , (err, recentCharges) =>
return callback ( err ) if err
callback ( null , recentCharges . data )
)
)
] ,
( (err, results) =>
if err
@ logPaymentError ( req , ' Stripe async load db error. ' + err )
return @ sendDatabaseError ( res , err )
[ payments , charges ] = results
recordedChargeIDs = ( p . get ( ' stripe ' ) . chargeID for p in payments )
for charge in charges
2015-05-19 14:41:11 -04:00
continue unless charge . paid
2014-12-12 18:27:58 -05:00
continue if charge . invoice # filter out subscription charges
if charge . id not in recordedChargeIDs
return @ recordStripeCharge ( req , res , charge )
@ sendSuccess ( res )
)
)
2014-11-25 16:20:09 -05:00
2014-11-17 18:15:02 -05:00
#- Incrementing/recalculating gems
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
incrementGemsFor: (user, gems, done) ->
purchased = _ . clone ( user . get ( ' purchased ' ) )
if not purchased ? . gems
purchased ? = { }
purchased.gems = gems
user . set ( ' purchased ' , purchased )
user . save ( (err) -> done ( err ) )
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
else
user . update ( { $inc: { ' purchased.gems ' : gems } } , { } , (err) -> done ( err ) )
2014-11-25 16:20:09 -05:00
2015-02-20 14:11:12 -05:00
recalculateGemsFor: (user, done, saveIfUnchanged=true) ->
2014-11-11 20:40:03 -05:00
Payment . find ( { recipient: user . _id } ) . select ( ' gems ' ) . exec ( (err, payments) ->
gems = _ . reduce payments , ( (sum, p) -> sum + p . get ( ' gems ' ) ) , 0
purchased = _ . clone ( user . get ( ' purchased ' ) )
purchased ? = { }
2015-02-20 14:11:12 -05:00
if ( purchased . gems or 0 ) isnt gems
log . debug " Updating #{ user . get ( ' _id ' ) } gems from #{ purchased . gems } to #{ gems } from #{ payments . length } payments; #{ user . get ( ' email ' ) } #{ user . get ( ' name ' ) } "
else unless saveIfUnchanged
log . debug " #{ user . get ( ' _id ' ) } already had #{ purchased . gems } #{ gems } from #{ payments . length } payments; #{ user . get ( ' email ' ) } #{ user . get ( ' name ' ) } "
return done ( )
2014-11-11 20:40:03 -05:00
purchased.gems = gems
user . set ( ' purchased ' , purchased )
user . save ( (err) -> done ( err ) )
)
2014-11-25 16:20:09 -05:00
2016-03-18 20:05:21 -04:00
sendPaymentSlackMessage: (options) ->
2014-11-25 16:20:09 -05:00
try
2016-04-08 12:24:38 -04:00
message = " #{ options . user ? . get ( ' emailLower ' ) } paid #{ options . payment ? . get ( ' amount ' ) } for #{ options . payment . get ( ' description ' ) or ' ???, no payment description! ' } "
2016-03-18 20:05:21 -04:00
slack . sendSlackMessage message , [ ' tower ' ]
2014-11-25 16:20:09 -05:00
catch e
2016-03-18 20:05:21 -04:00
log . error " Couldn ' t send Slack message on payment because of error: #{ e } "
2014-11-25 16:20:09 -05:00
2014-11-11 20:40:03 -05:00
module.exports = new PaymentHandler ( )