Skip to content

Handle Incoming Transactions

Indospace.io edited this page Jan 26, 2018 · 11 revisions

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

bitcoin.conf

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.js]

app.db.transactions.updateAll(() => {
  setInterval(() => { 
    app.db.transactions.updateAll(() => {
    })
  }, 60000) // 60.000 = 1 min

[models/transactions.js] - updateAll()

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?

[models/transactions.js] - add()

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)
}

Clone this wiki locally