Skip to content
This repository has been archived by the owner on Feb 26, 2020. It is now read-only.

Commit

Permalink
V2 (#5)
Browse files Browse the repository at this point in the history
REL
  • Loading branch information
Overtorment authored Nov 9, 2017
1 parent a8e62ba commit 43df158
Show file tree
Hide file tree
Showing 26 changed files with 3,195 additions and 1,541 deletions.
56 changes: 56 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
version: 2
jobs:
build:
docker:
- image: circleci/node:8.5.0

working_directory: ~/Cashier-BTC

steps:
- checkout

- run:
command: |
echo Setting up
npm install
cp config.js.dev config.js
sudo apt-get install --yes build-essential curl git
sudo apt-get install --yes python-software-properties python g++ make
sudo apt-get install -y erlang-dev erlang-manpages erlang-base-hipe erlang-eunit erlang-nox erlang-xmerl erlang-inets
sudo apt-get install -y libmozjs185-dev libicu-dev libcurl4-gnutls-dev libtool
cd ~/
wget http://ftp.fau.de/apache/couchdb/source/1.6.1/apache-couchdb-1.6.1.tar.gz
tar xvzf apache-couchdb-*
cd apache-couchdb-*
./configure && make
sudo make install
sudo useradd -d /var/lib/couchdb couchdb || true
sudo chown -R couchdb: /var/lib/couchdb /var/log/couchdb || true
sudo chown -R couchdb: /usr/local/var/{lib,log,run}/couchdb /usr/local/etc/couchdb || true
sudo chmod 0770 /usr/local/var/{lib,log,run}/couchdb/ || true
sudo chmod 664 /usr/local/etc/couchdb/*.ini || true
sudo chmod 775 /usr/local/etc/couchdb/*.d || true
cd /etc/init.d
sudo ln -s /usr/local/etc/init.d/couchdb couchdb || true
sudo /etc/init.d/couchdb start || true
sudo update-rc.d couchdb defaults || true
sudo service couchdb restart || true
sleep 5
curl http://127.0.0.1:5984/ || true
curl -s -X PUT http://localhost:5984/_config/admins/user -d '"pass"' || true
curl -s -X PUT http://user:pass@localhost:5984/cashier-btc || true
cd ~/Cashier-BTC
wget https://bitcoin.org/bin/bitcoin-core-0.15.0.1/bitcoin-0.15.0.1-x86_64-linux-gnu.tar.gz
tar -xvf bitcoin-0.15.0.1-x86_64-linux-gnu.tar.gz
- run:
name: Running bitcoind in background...
command: ~/Cashier-BTC/bitcoin-0.15.0/bin/bitcoind -rpcuser=user -rpcpassword=pass -rpcbind=0.0.0.0 -rpcallowip=127.0.0.1 -datadir=/tmp/
background: true

- run: sleep 30

- run: cd ~/Cashier-BTC; npm test


23 changes: 23 additions & 0 deletions BITCOIN-CORE-INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Bitcoin Core quick install guide
================================

Install and run:

```bash
wget https://bitcoin.org/bin/bitcoin-core-0.15.0.1/bitcoin-0.15.0.1-x86_64-linux-gnu.tar.gz
tar -xvf bitcoin-0.15.0.1-x86_64-linux-gnu.tar.gz
cd bitcoin-0.15.0/
mkdir datadir
./bin/bitcoind -port=8444 -rpcport=8442 -datadir=./datadir -rpcuser=user -rpcpassword=pass -rpcbind=0.0.0.0 -rpcallowip=44.33.22.11
# dont forget to put your own allowip, username, pass
```

Wait till it syncs (might take a while).

Run

```bash
./bin/bitcoin-cli -rpcclienttimeout=30 -rpcport=8442 -rpcuser=user -rpcpassword=pass getblockchaininfo
```

to check it. If it hangs - blockchain is busy syncing, check out `debug.log` in datadir for details.
66 changes: 32 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
Cashier-BTC
===================

[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
v2 refactored and improved, battle-tested
-----------------------------------------

[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) Tests: [![CircleCI](https://circleci.com/gh/Overtorment/Cashier-BTC/tree/v2.svg?style=svg)](https://circleci.com/gh/Overtorment/Cashier-BTC/tree/v2)

Self-hosted Node.js Bitcoin payment gateway. Provides REST API for anyone who wants to accept bitcoin.
Request payments (invoicing), check payments (whether invoice is paid), receive callbacks if payment is made.
Aggregate funds on final (aggregational) address.
Depends on Bitcore, Couchdb for storage.
Depends on Nodejs v8+, Bitcoin Core, Couchdb for storage.

* Simple
* Autonomous (works though self-hosted Bitcoin Core node)
* Transactions are signed locally. No private keys leak
* Battle-tested in production: 50+ BTC turnover already

Expand All @@ -22,13 +26,13 @@ $ npm install
$ cp config.js.dev config.js
```

* Install [Bitcore full node and Bitcore Insight API](https://github.com/bitpay/insight-api)
* Install Couchdb (install one if needed, or use https://cloudant.com)
* Install [Bitcoin Core](BITCOIN-CORE-INSTALL.md)
* Install Couchdb (or use [https://cloudant.com](https://cloudant.com))

Edit config.js:
Edit `config.js`:

* Point it to a new Couchdb database
* Point it to a Bitcore server
* Point it to a Bitcoin Core RPC server

Tests
-----
Expand All @@ -46,9 +50,9 @@ $ nodejs worker.js
$ nodejs worker2.js
```

Open http://localhost:2222 in browser, you should see 'Cashier-BTC reporting for duty'.
Open [http://localhost:2222](http://localhost:2222) in browser, you should see 'Cashier-BTC reporting for duty'.
That's it, ready to use.
Use tools like supervisord or foreverjs to keep it running.
Use tools like `supervisord` or `foreverjs` to keep it running.

License
-------
Expand All @@ -64,29 +68,29 @@ Igor Korsakov
TODO
----

* ~~[V] Get rid of Chain and leave Bitcore only~~
* [ ] Add options to work through bitcoind and other bitcoin network endpoints
* ~~[V] Add tests~~
* ~~[V] Better abstractioning (add more abstraction layers)~~
* [x] ~~Get rid of Chain and leave Bitcore only~~
* [x] ~~Add options to work through bitcoind and other bitcoin network endpoints~~
* [x] ~~Add tests~~
* [x] ~~Better abstractioning (add more abstraction layers)~~
* [ ] Better logging & error handling
* [ ] Stats

* [ ] Better tests
* [x] ~~CI~~
* [ ] SegWit


API
===

GET /request_payment/:expect/:currency/:message/:seller/:customer/:callback_url
--------------------------------------------------------------------------------------------------------
### GET /request_payment/:expect/:currency/:message/:seller/:customer/:callback_url


Create a request to pay, supported currencies BTC, USD, EUR. Non-btc currency is converted to btc using current rate from btc-e.com.
Create a request to pay, supported currencies: BTC, USD, EUR. Non-btc currency is converted to btc using current rate from bitstamp.com.
Returns a json document with QR code to be displayed to the payer, and a unique address for that particular payment (you can use it as invoice id).
Message will be displayed to the client (for example, you can write "Payment for goods"). Seller and customer - system field, here you can
write the application that created the request and the payer id.
Message will be displayed to the client (for example, you can write "Payment for goods"). Seller and customer - system field, here you can
write the application that created the request and the payer id. Keep Seller field private, it is also used for payouts.
Callback_url will be requested once the invoice is paid.



Example

http://localhost:2222/request_payment/0.005/BTC/wheres%20the%20money%20lebowski/treehorn/lebowski/http%3A%2F%2Fgoogle.com%2F
Expand All @@ -103,12 +107,9 @@ Callback_url will be requested once the invoice is paid.
Link can be opened by the payer, there is a chance it will be handled by his bitcoin wallet.
QR whoud be shown to payer as well. Duplicate it with text, like, dear user, please pay the %expect% amount to %address%.

### GET /check_payment/:address



GET /check_payment/:address
---------------------------------------

Check payment by a unique address received in the "request_payment" call.


Expand All @@ -127,29 +128,26 @@ Check payment by a unique address received in the "request_payment" call.
Using difference between "btc_expected" and "btc_actual" you can judge whether payment request (invoice) was paid.


GET /payout/:seller/:amount/:currency/:address
-------------------------------------------------------------
### GET /payout/:seller/:amount/:currency/:address


Transfer funds from aggregated seller's address to some other address.
Supported currencies BTC, USD, EUR.
Supported currencies: BTC.
There's no additional sequrity here, it is presumed that the %seller% identifier is kept secret.
You might want to disable this call for security reasons.
You might want to disable this call for security reasons (or manually replace seller's address in
database with the one you control).

Example

http://localhost:2222/payout/new_test_seller/0.01/BTC/1MahZCousgNv6EAofCfi7Wpp2RKUfHH8uD

Response

If successfull, json document with transaction details (txid, txhex, etc)



If successfull, json document with transaction details (txid etc)


### GET /get_seller_balance/:seller

GET /get_seller_balance/:seller
---------------------------------------

Check the total balance of seller's aggregated address.

Expand Down
12 changes: 3 additions & 9 deletions _design_docs/address.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,16 @@
"unprocessed_by_timestamp": {
"map": "function(doc) {\n if (doc.doctype=='address' && doc.timestamp && (!doc.processed || doc.processed=='unprocessed')) {\n emit(doc.timestamp, null);\n }\n}"
},
"unpaid_by_timestamp": {
"map": "function(doc) {\n if (doc.doctype=='address' && doc.timestamp && (doc.processed=='unpaid')) {\n emit(doc.timestamp, null);\n }\n}"
},
"paid_by_timestamp": {
"map": "function(doc) {\n if (doc.doctype=='address' && doc.timestamp && (doc.processed=='paid')) {\n emit(doc.timestamp, new Date( doc.timestamp * 1000).toISOString().replace('T', ' ') );\n }\n}"
"map": "function(doc) {\n if (doc.doctype=='address' && doc.timestamp && (doc.processed=='paid')) {\n emit(doc.timestamp, new Date( doc.timestamp ).toISOString().replace('T', ' ') );\n }\n}"
},
"paid_and_sweeped_by_timestamp": {
"map": "function(doc) {\n if (doc.doctype=='address' && doc.timestamp && (doc.processed=='paid_and_sweeped')) {\n emit(doc.timestamp, new Date( doc.timestamp * 1000).toISOString().replace('T', ' '));\n }\n}"
},
"processing_by_timestamp": {
"map": "function(doc) {\n if (doc.doctype=='address' && doc.timestamp && (doc.processed=='processing')) {\n emit(doc.timestamp, null);\n }\n}"
"map": "function(doc) {\n if (doc.doctype=='address' && doc.timestamp && (doc.processed=='paid_and_sweeped')) {\n emit(doc.timestamp, new Date( doc.timestamp ).toISOString().replace('T', ' '));\n }\n}"
},
"total_by_seller": {
"map": "function(doc) {\n if (doc.doctype=='address' && doc.processed=='paid_and_sweeped' && !doc.is_test) {\n emit(doc.seller, doc.btc_to_ask-0.0001);\n }\n}",
"reduce": "function(key, values, rereduce) {\n return sum(values); \n}"
}
},
"language": "javascript"
}
}
90 changes: 27 additions & 63 deletions cashier-btc.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
* -----------
* Self-hosted bitcoin payment gateway
*
* License: WTFPL
* Author: Igor Korsakov
* */
* https://github.com/Overtorment/Cashier-BTC
*
**/

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
var express = require('express')
var morgan = require('morgan')
var uuid = require('node-uuid')
let express = require('express')
let morgan = require('morgan')
let uuid = require('node-uuid')

morgan.token('id', function getId (req) {
return req.id
})

var app = express()
let app = express()

app.use(function (req, res, next) {
req.id = uuid.v4()
Expand All @@ -26,85 +26,49 @@ app.use(morgan(':id :remote-addr - :remote-user [:date[clf]] ":method :url HTTP/

app.set('trust proxy', 'loopback')

var bodyParser = require('body-parser')
var config = require('./config')
var fs = require('fs')
var storage = require('./models/storage')
var https = require('https')
let bodyParser = require('body-parser')
let config = require('./config')
let https = require('https')

app.use(bodyParser.urlencoded({ extended: false })) // parse application/x-www-form-urlencoded
app.use(bodyParser.json(null)) // parse application/json

global.btcUsd = 650 // initial
global.btcEur = 700
global.sellers = {} // cache of existing sellers' wallets
global.btcUsd = 7000 // initial
global.btcEur = 6000

app.use('/qr', express.static('qr'))

app.use(require('./controllers/api'))
app.use(require('./controllers/website'))

var updateExchangeRate = function (pair) {
https.get('https://btc-e.com/api/3/ticker/' + pair, function (ret) {
var json = ''
let updateExchangeRate = function (pair) {
https.get('https://www.bitstamp.net/api/v2/ticker/' + pair, function (ret) {
let json = ''
ret.on('data', function (d) { json += d })
ret.on('end', function () {
json = JSON.parse(json)
var rate
if (json[pair].buy) {
rate = json[pair].buy
let rate
if (json.ask) {
rate = json.ask
} else {
console.log(json)
}
switch (pair) {
case 'btc_eur': global.btcEur = rate; break
case 'btc_usd': global.btcUsd = rate; break
case 'btceur': global.btcEur = rate; break
case 'btcusd': global.btcUsd = rate; break
}
})
})
}

updateExchangeRate('btc_usd')
updateExchangeRate('btc_eur')
setInterval(function () { updateExchangeRate('btc_usd') }, 60 * 1000)
setInterval(function () { updateExchangeRate('btc_eur') }, 60 * 1000)

// checking design docs in Couchdb
fs.readdir('./_design_docs', function (err, designDocs) {
if (err) {
console.log('Cant read design documents list')
process.exit()
}

var readFileCallback = function (err, data) {
var json = JSON.parse(data)
if (err) {
return console.log(err)
}
storage.getDocument(json._id, function (doc) {
if (!doc || doc.error === 'not_found') {
console.log(json._id + ' design doc needs to be created')
storage.saveDocument(json, function (response, err) {
console.log('Creating design document resulted in:', JSON.stringify(response || err))
})
}
})
}
updateExchangeRate('btcusd')
updateExchangeRate('btceur')
setInterval(function () { updateExchangeRate('btcusd') }, 5 * 60 * 1000)
setInterval(function () { updateExchangeRate('btceur') }, 5 * 60 * 1000)

for (var i = 0; i < designDocs.length; i++) {
fs.readFile('./_design_docs' + '/' + designDocs[i], 'utf8', readFileCallback)
}
}) // done with design docs

// street magic
process.on('uncaughtException', function (err) {
console.log('Exception: ', err)
console.log('\nStacktrace:')
console.log('====================')
console.log(err.stack)
})
require('./smoke-test')
require('./deploy-design-docs') // checking design docs in Couchdb

var server = app.listen(config.port, function () {
let server = app.listen(config.port, function () {
console.log('Listening on port %d', config.port)
})

Expand Down
Loading

0 comments on commit 43df158

Please sign in to comment.