const express = require('express'); const app = express(); const bodyparser = require('body-parser'); const cors = require('cors'); const crypto = require('crypto'); const postmodel = require('./models/post'); const usermodel = require('./models/user'); const ownermodel = require('./models/owner'); const itemmodel = require('./models/item'); const ordermodel = require('./models/order'); const pricemodel = require('./models/price'); const txmodel = require('./models/tx'); const paymentmodel = require('./models/payment'); const zecTxModel = require('./models/zectxs.js'); const countryModel = require('./models/country.js'); const mongoose = require('mongoose'); const stdrpc = require('stdrpc'); const CoinGecko = require('coingecko-api'); var URLSafeBase64 = require('urlsafe-base64'); var Buffer = require('buffer/').Buffer; var db = require('./config/db'); mongoose.connect('mongodb://'+db.user+':'+db.password+'@'+db.server+'/'+db.database).then(() => { console.log("connecting-- ", db.database); }).catch(() => { console.log("connection failed!"); }); var fullnode = require('./config/fullnode'); const rpc = stdrpc({ url: fullnode.url, username: fullnode.username, password: fullnode.password }); var async = require('async'); const CoinGeckoClient = new CoinGecko(); var intervalObject = setInterval( function() { CoinGeckoClient.simple.price({ ids: ['zcash'], vs_currencies: ['usd', 'gbp', 'eur', 'cad', 'aud'] }).then((data) => { pricemodel.findOneAndUpdate({currency: 'usd'}, { price: data.data.zcash.usd, timestamp: Date.now()}, {new:true, upsert:true}, function(err,docs) { if(err) { console.log(err); } }); pricemodel.findOneAndUpdate({currency: 'gbp'}, { price: data.data.zcash.gbp, timestamp: Date.now()}, {new:true, upsert:true}, function(err,docs) { if(err) { console.log(err); } }); pricemodel.findOneAndUpdate({currency: 'eur'}, { price: data.data.zcash.eur, timestamp: Date.now()}, {new:true, upsert:true}, function(err,docs) { if(err) { console.log(err); } }); pricemodel.findOneAndUpdate({currency: 'cad'}, { price: data.data.zcash.cad, timestamp: Date.now()}, {new:true, upsert:true}, function(err,docs) { if(err) { console.log(err); } }); pricemodel.findOneAndUpdate({currency: 'aud'}, { price: data.data.zcash.aud, timestamp: Date.now()}, {new:true, upsert:true}, function(err,docs) { if(err) { console.log(err); } }); }).catch((err) => { console.log(err); }); }, 90000); function hexToString(hexString) { var str = ''; for (var n=0; n < hexString.length; n +=2) { str += String.fromCharCode(parseInt(hexString.substr(n, 2), 16)); } return str; } function sendPin(pin, address) { //var memo = URLSafeBase64.encode(Buffer.from('ZGO pin: '.concat(pin))); var memo = Buffer.from('ZGo PIN: '.concat(pin)).toString('hex'); //console.log(typeof(memo)); var amounts = [ { address: address, amount: 0.00000001, memo: memo } ]; rpc.z_sendmany(fullnode.addr, amounts).catch((err) => { console.log('Sendmany', err); }); } var blockInterval = setInterval( function() { console.log('Node periodic Zcash scan'); rpc.z_listreceivedbyaddress(fullnode.addr, 1).then(txs => { var re = /.*ZGO::(.*)\sReply-To:\s(z\w+)/; var pay = /.*ZGOp::(.*)/; async.each (txs, function(txData, callback) { var memo = hexToString(txData.memo).replace(/\0/g, ''); if (!txData.change) { zecTxModel.updateOne({txid: txData.txid}, { txid: txData.txid, confirmations: txData.confirmations, amount:txData.amount, memo: memo}, {new:true, upsert:true}, function(err,docs) { if (err) { console.log(err); } }); } if (re.test(memo) && txData.confirmations < 100) { //console.log('Processing tx:', memo); var match = re.exec(memo); if (match != null) { var address = match[2]; var session = match[1]; var blocktime = txData.blocktime; var amount = txData.amount; var expiration = blocktime; //console.log(' ', session, blocktime); txmodel.updateOne({txid: txData.txid}, { txid: txData.txid, address: address, session: session, confirmations: txData.confirmations, amount:txData.amount, memo: memo}, {new:true, upsert:true}, function(err,docs) { if (err) { console.log(err); } }); if (txData.confirmations >= 2 ) { usermodel.findOne({address: address, session: session, blocktime: blocktime}).then(function(doc){ if (doc == null){ console.log('User not found', session, blocktime); const n = crypto.randomInt(0, 10000000); const pin = n.toString().padStart(6, '0'); sendPin(pin, address); var user = new usermodel({ address: address, session: session, blocktime: blocktime, pin: pin, validated: false }); user.save(function(error) { if (error) { console.log(error); } console.log('User saved'); }); } }); } } } var exptime = 0; if (pay.test(memo) && txData.confirmations < 100) { var match2 = pay.exec(memo); if (match2 != null) { var session = match2[1]; } if (txData.amount >= 0.001 && txData.amount < 0.005){ exptime = txData.blocktime + 3600; } else if (txData.amount >= 0.005 && txData.amount < 0.025){ exptime = txData.blocktime + 24*3600; } else if (txData.amount >= 0.025 && txData.amount < 0.1) { exptime = txData.blocktime + 7*24*3600; } else if (txData.amount >= 0.1) { exptime = txData.blocktime + 4*7*24*3600; } usermodel.findOne({session: session}).then(function(doc){ if(doc != null) { paymentmodel.findOne({address: doc.address, blocktime: txData.blocktime, amount: txData.amount}).then(function(payments){ if(payments == null){ var payment = new paymentmodel({ address: doc.address, blocktime: txData.blocktime, expiration: new Date(exptime * 1000), amount: txData.amount, session: doc.session }); payment.save(function(error) { if (error) { console.log(error); } else { console.log('Payment saved'); } }); } }); } }); } }, function (err) { if (err) { console.log(err); } console.log('Txs synced'); }); }); }, 75000); var payCheck = setInterval( function() { ownermodel.find({}).then((documents) => { if(documents.length > 0){ //console.log(documents); async.each (documents, function(document, callback) { paymentmodel.findOne({ address: document.address, expiration: {$gt: Date.now()}}).then(payment => { if (payment != null){ document.paid = true; document.save(); } else { document.paid = false; document.save(); } }); }, function (err) { if (err) { console.log(err); } else { console.log("Owners checked for payment"); } }); } }); }, 60000); app.use(cors()); app.options('*', cors()); app.use(bodyparser.json()); app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS"); next(); }); app.use((req, res, next) => { if (req.headers.authorization !== 'Le2adeic8Thah4Aeng4daem6i' ) { return res.status(401).send('Authorization required.'); } else { next(); } }); app.get('/api/countries', (req, res, next) => { console.log('Get: /api/countries'); countryModel.find({}).then((documents) => { if (documents != null) { res.status(200).json({ message: 'Country data found', countries: documents }); } else { res.status(204).json({ message: 'No country data available' }); } }); }); app.get('/api/users', (req, res, next) => { console.log('Get: /api/users'); usermodel.find({'address': req.query.address, 'session': req.query.session, 'expired': { $ne: true}}). then((documents) => { if (documents != null) { res.status(200).json({ message: 'Users found successfully', users: documents }); } else { res.status(204).json({ message: 'User not found', users: null }); } }); }); app.get('/api/pending', (req, res, next) => { console.log('Get: /api/pending', req.query.session); txmodel.find({'session': req.query.session, 'confirmations': {$lt: 10}}). then((documents) => { if (documents.length > 0) { //console.log('pending', documents); res.status(200).json({ message: 'Found pending txs', txs: documents }); } else { //console.log('pending not found', documents); res.status(204).json({ message: 'No txs found', txs: null }); } }); }); app.get('/api/getuser', (req, res, next) => { console.log('Get: /api/getuser/', req.query.session); usermodel.find({'session': req.query.session, 'expired': { $ne: true }}). then((documents) => { if(documents.length > 0){ //console.log(documents); console.log(' found user'); res.status(200).json({ message: 'User found!', user: documents }); } else { console.log(' did not find user'); res.status(204).json({ message: 'User not found!', user: null }); } }); }); app.get('/api/blockheight', (req, res, next) => { console.log('Get: /api/blockheight'); rpc.getblock("-1", 1).then(block => { res.status(200).json({ message: 'Found block', height: block.height }); }); }); app.get('/api/txs', (req, res, next) => { console.log('Get: /api/txs'); rpc.z_listreceivedbyaddress(fullnode.addr, 10).then(txs => { res.status(200).json({ message: 'Transactions found', txs: txs }); }); }); app.get('/api/getaddr', (req, res, next) => { console.log('Get: /api/getaddr'); res.status(200).json({ message: 'Sending address', addr: fullnode.addr }); }); app.get('/api/getowner', (req, res, next) => { console.log('Get: /api/getowner'); ownermodel.find({'address': req.query.address}).then((documents) => { if(documents.length > 0){ //console.log(documents); res.status(200).json({ message: 'Owner found!', owner: documents }); } else { res.status(204).json({ message: 'Owner not found!', owner: null }); } }); }); app.post('/api/addowner', (req, res, next) => { console.log('Post: /api/addowner'); const owner = new ownermodel(req.body.owner); owner.save(); res.status(201).json({ message: 'Owner added successfully' }); }); app.post('/api/validateuser', (req, res, next) => { console.log('Post: /api/validateuser'); usermodel.findByIdAndUpdate(req.body.user._id, req.body.user, function(err, docs) { if (err) { console.log(err); } else { res.status(201).json({ message: 'User Validated', user: docs }); } }); }); app.post('/api/updateowner', (req, res, next) => { console.log('Post: /api/updateowner'); ownermodel.findByIdAndUpdate(req.body.owner._id, req.body.owner, function(err, docs) { if (err) { console.log(err); } else { console.log(docs); res.status(201).json({ message: 'Owner updated', owner: docs }); } }); }); app.get('/api/getitems', (req, res, next) => { console.log('Get: /api/getitems'); //console.log('getitems', req.query.address); if (req.query.address.length > 0 ) { const items = itemmodel.find({user: req.query.address}).then((documents) => { if(documents.length > 0){ //console.log(documents); res.status(200).json({ message: 'items found!', items: documents }); } else { res.status(204).json({ message: 'items not found!', items: [] }); } }); } else { res.status(204).json({ message: 'no address', items: [] }); } }); app.post('/api/item', (req, res, next) => { console.log('Post: /api/item', req.body.item); if ( req.body.item._id == null ) { const item = new itemmodel(req.body.item); item.save(); res.status(201).json({ message: 'Item added' }); } else { console.log('Editing', req.body.item._id); itemmodel.findByIdAndUpdate(req.body.item._id, {'name': req.body.item.name, 'description': req.body.item.description, 'cost': req.body.item.cost}, function(err, docs) { if (err) { console.log(err); } else { res.status(201).json({ message: 'Item updated' }); } }); } }); app.delete('/api/item/:id', (req, res, next) => { console.log('delete endpoint', req.params.id); itemmodel.findByIdAndDelete(req.params.id, function (err, docs) { if (err) { console.log(err); } else { res.status(200).json({ message: 'Item deleted' }); } }); }); app.delete('/api/user/:id', (req, res, next) => { console.log("Delete user", req.params.id); usermodel.findByIdAndUpdate(req.params.id, {expired: true}, function(err, docs) { if (err) { console.log(err); } else { res.status(200).json({ message: 'User session deleted' }); } }); }); app.get('/api/price', (req, res, next) => { console.log('Get /api/price'); const price = pricemodel.findOne({currency: req.query.currency}).then((document) => { if (document != null) { res.status(200).json({ message: 'price found!', price: document }); } else { res.status(204).json({ message: 'no price found!', order: null }); } }); }); app.get('/api/allorders', (req, res, next) => { console.log('Get /api/allorders'); if (req.query.address.length > 0) { const orders = ordermodel.find({address: req.query.address, closed: true}).then((documents) => { if (documents != null) { res.status(200).json({ message: 'orders found!', orders: documents }); } else { res.status(204).json({ message: 'no orders found', orders: null }); } }); } }); app.get('/api/receipt', (req, res, next) => { console.log('Get /api/receipt'); if (req.query.id.length > 0) { const order = ordermodel.findOne({_id: req.query.id}).then((documents) => { if (documents != null) { console.log(documents); res.status(200).json({ message: 'order found!', order: documents }); } else { res.status(204).json({ message: 'no order found!', order: null }); } }); } else { res.status(204).json({ message: 'no valid ID received', order: null }); } }); app.get('/api/order', (req, res, next) => { console.log('Get /api/order'); if (req.query.session.length > 0) { const order = ordermodel.findOne({session: req.query.session, closed: false}).then((documents) => { if (documents != null) { console.log(documents); res.status(200).json({ message: 'order found!', order: documents }); } else { res.status(204).json({ message: 'no order found!', order: null }); } }); } else { res.status(204).json({ message: 'no session received', order: null }); } }); app.post('/api/order', (req, res, next) => { console.log('Post /api/order', req.body); if(req.body.order._id == null) { const order = new ordermodel(req.body.order); order.save(); res.status(200).json({ message: 'Order added', order: order }); } else { ordermodel.findByIdAndUpdate(req.body.order._id, { address: req.body.order.address, session: req.body.order.session, price: req.body.order.price, total: req.body.order.total, currency: req.body.order.currency, totalZec: req.body.order.totalZec, closed: req.body.order.closed }, function(err, docs) { if(err) { console.log(err); } else { res.status(200).json({ message: 'Order updated' }); } }); } }); app.post('/api/lineitem', (req, res, next) => { console.log('Post /api/lineitem'); ordermodel.findByIdAndUpdate(req.body.order_id, { $push: {lines: req.body.line}}, function(err,docs) { if (err) { console.log(err); } else { res.status(200).json({ message: 'Item added to order' }); } }); }); app.delete('/api/order/:id', (req, res, next) => { console.log('delete order endpoint', req.params.id); ordermodel.findByIdAndDelete(req.params.id, function (err, docs) { if (err) { console.log(err); } else { console.log(docs); res.status(200).json({ message: 'Order deleted' }); } }); }); module.exports = app;