-
Notifications
You must be signed in to change notification settings - Fork 16
Handle Incoming Transactions
This is a scenario that we are all faced with. How do you handle/receive incoming payments to your full node?
First of all, we want everything in real time! So edit your bitcoin.conf file and add this
walletnotify=curl http://www.yoursite.io/transactions/add/bch/%s
The %s passed the transaction ID of the incoming transaction to the URL so we can immediately add the transaction to the database and start updating confirmations.
Always have a listener running and check it every x number of minutes. I personally set mine to one minute. All we are doing is calling the listtransactions RPC command and
looking for the "receive" value in the category attribute that is returned in the JSON block.
I've also included a Litecoin RPC library as well to illustrate how to add any number of cryptocurrencies to the mix.
Notice that they are both callback functions, keep your code asynchronous, so don't wait for one listTransaction iteration to complete and then start the next. Start them all at once! When a transaction is found as in if(trans.category == 'receive'), then transaction.add(trans.txid, '[crypto]', () => { }) is called.
app.db.transactions.updateAll(() => {
setInterval(() => {
app.db.transactions.updateAll(() => {
})
}, 60000) // 60.000 = 1 min
transaction.updateAll = (cb) => {
app.crypto.litecoin.cmd([{
method: 'listtransactions',
params: ['*']
}], (err, tx_recs) => {
tx_recs.forEach(trans => {
if(trans.category == 'receive')
transaction.add(trans.txid, 'ltc', () => {
})
})
})
Promise.resolve(app.crypto.bitcoin_cash.listTransactions()).then(tx_recs => {
tx_recs.forEach(trans => {
if(trans.category == 'receive')
transaction.add(trans.txid, 'bch', () => {
})
})
})
return cb()
}
Now it's time to pass our transaction ID from our received transaction into our database. How about send an email notification to our customer and a real time socket notification to them as well?
transaction.add = (tx_id, crypto, cb) => {
require('async').waterfall([
(done) => {
db.models.transactions.findTxID(tx_id, crypto, crypto_tx_rec => {
return !crypto_tx_rec ? cb(false) : done(null, crypto_tx_rec)
})
},
(crypto_tx_rec, done) => {
db.models.deposit_addresses.findOne({
where: {
'address': crypto_tx_rec.details[0].address
}
}).then((deposit_address_rec) => {
if(deposit_address_rec) {
var trans_ids = Array.isArray(deposit_address_rec.txids) ? deposit_address_rec.txids : []
trans_ids.forEach((txid, index) => {
if(txid == 0 || txid == null)
delete trans_ids[index]
})
if(!trans_ids.includes(crypto_tx_rec.txid)) {
trans_ids.push(crypto_tx_rec.txid)
deposit_address_rec.update({
'used': parseInt(deposit_address_rec.used) + 1,
'txids': trans_ids
})
}
db.models.users.findOne({
where: {
'id': deposit_address_rec.user_id
}
}).then((user_rec) => {
if(user_rec) {
user_rec.deposit_address_rec = deposit_address_rec
done(null, user_rec, crypto, crypto_tx_rec)
}
else
return cb(false)
})
}
})
},
(user_rec, crypto, crypto_tx_rec, done) => {
// console.log('crypto: ' + crypto)
if(crypto_tx_rec.details[0].label == 'indospace_users')
db.models.sitevars.getLtcUsdPrice((ltc_usd_amount) => {
transaction.findOne({
where: {
'txid': crypto_tx_rec.txid
}
}).then(tx_rec => {
if(tx_rec)
if(parseInt(crypto_tx_rec.confirmations) < 10)
tx_rec.update({
'confirmations': parseInt(crypto_tx_rec.confirmations),
'date_updated': db.Sequelize.fn('now')
}).then((tx_rec) => {
done(null, user_rec, crypto, tx_rec, crypto_tx_rec.confirmations, ltc_usd_amount)
})
else
return cb(false)
else {
var crypto_amount = parseFloat(crypto_tx_rec.details[0].amount)
transaction.create({
'user_id': user_rec.id,
'type': 'pending_deposit',
'crypto': crypto,
'crypto_amount': crypto_amount.toFixed(8),
'usd_amount': crypto_amount.toFixed(2),
'confirmations': 0,
'txid': crypto_tx_rec.txid,
'blob': {}
}).then(tx_rec => {
done(null, user_rec, crypto, tx_rec, 0, ltc_usd_amount)
})
}
})
})
},
(user_rec, crypto, tx_rec, confirmations, ltc_usd_amount, done) => {
if(confirmations == 0)
done(null, user_rec)
else
tx_rec.update({
'type': 'deposit'
}).then(tx_rec => {
transaction.calcUserBalance(user_rec.id, crypto, (user_balance) => {
user_rec.deposit_address_rec.updateAttributes({
'balance': parseFloat(user_balance)
}).then((deposit_address_rec) => {
})
var balance = user_rec.balance
balance.usd = parseFloat(user_balance * ltc_usd_amount).toFixed(2)
balance[crypto].total = parseFloat(user_balance).toFixed(8)
balance[crypto].usd = parseFloat(user_balance * ltc_usd_amount).toFixed(2)
user_rec.updateAttributes({
'balance': balance
}).then((user_rec) => {
if(!tx_rec.blob.customer_notified) {
app.sio.sockets.in('room_' + user_rec.id).emit('update-balance', {
})
db.models.notifications.sendMessage({
'to': user_rec.email,
'from': app.nconf.get('mail_user'),
'label': 'customer_deposit_confirmation',
'template_vars': {
'deposit': {
'id': tx_rec.id,
'amount': tx_rec.crypto_amount,
'amount_usd': parseFloat(ltc_usd_amount * tx_rec.crypto_amount).toFixed(2),
'date_received': moment(tx_rec.date_created).format('MM/DD/YY @ hh:mm a')
}
}
}, (status) => {
if(status) {
var blob = tx_rec.blob
blob.customer_notified = 1
tx_rec.update({
'blob': blob
})
}
})
}
done(null, user_rec)
})
})
})
}],
(err, user_rec) => {
if(err)
throw err
app.sio.sockets.in('room_' + user_rec.id).emit('update-transactions', {
})
app.sio.sockets.in('room_' + user_rec.id).emit('update-balance', {
})
return cb(user_rec.id)
}, 1)
}