Ryan X. Charles
11 years ago
7 changed files with 972 additions and 1 deletions
@ -0,0 +1 @@ |
|||
../../browser/bundle.js |
@ -0,0 +1,495 @@ |
|||
/** |
|||
* Payment-Customer - A Payment Protocol demonstration. |
|||
* This file will run in node or the browser. |
|||
* Copyright (c) 2014, BitPay |
|||
* https://github.com/bitpay/bitcore
|
|||
*/ |
|||
|
|||
;(function() { |
|||
|
|||
/** |
|||
* Global |
|||
*/ |
|||
|
|||
var window = this; |
|||
var global = this; |
|||
|
|||
/** |
|||
* Platform |
|||
*/ |
|||
|
|||
var isNode = !!(typeof process === 'object' && process && process.versions.node); |
|||
|
|||
// Disable strictSSL
|
|||
if (isNode) { |
|||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; |
|||
} |
|||
|
|||
/** |
|||
* Dependencies |
|||
*/ |
|||
|
|||
var bitcore = isNode |
|||
? require('../../') |
|||
: require('bitcore'); |
|||
var PayPro = bitcore.PayPro; |
|||
var Transaction = bitcore.Transaction; |
|||
var TransactionBuilder = bitcore.TransactionBuilder; |
|||
|
|||
/** |
|||
* Variables |
|||
*/ |
|||
|
|||
var port = 8080; |
|||
|
|||
if (isNode) { |
|||
var argv = require('optimist').argv; |
|||
if (argv.p || argv.port) { |
|||
port = +argv.p || +argv.port; |
|||
} |
|||
} else { |
|||
port = +window.location.port || 443; |
|||
} |
|||
|
|||
var merchant = isNode |
|||
? parseMerchantURI(argv.m || argv.u || argv._[0]) |
|||
: parseMerchantURI(window.merchantURI); |
|||
|
|||
/** |
|||
* Send Payment |
|||
*/ |
|||
|
|||
if (isNode) { |
|||
var Buffer = global.Buffer; |
|||
} else { |
|||
var Buffer = function Buffer(data) { |
|||
var ab = new ArrayBuffer(data.length); |
|||
var view = new Uint8Array(ab); |
|||
data._size = data.length; |
|||
for (var i = 0; i < data._size; i++) { |
|||
view[i] = data[i]; |
|||
} |
|||
if (!view.slice) { |
|||
// view.slice = ab.slice.bind(ab);
|
|||
view.slice = function(start, end) { |
|||
if (end < 0) { |
|||
end = data._size + end; |
|||
} |
|||
data._size = end - start; |
|||
var ab = new ArrayBuffer(data._size); |
|||
var view = new Uint8Array(ab); |
|||
for (var i = 0, j = start; j < end; i++, j++) { |
|||
view[i] = data[j]; |
|||
} |
|||
return view; |
|||
}; |
|||
} |
|||
return view; |
|||
}; |
|||
Buffer.byteLength = function(buf) { |
|||
var bytes = 0 |
|||
, ch; |
|||
|
|||
for (var i = 0; i < buf.length; i++) { |
|||
ch = buf.charCodeAt(i); |
|||
if (ch > 0xff) { |
|||
bytes += 2; |
|||
} else { |
|||
bytes++; |
|||
} |
|||
} |
|||
|
|||
return bytes; |
|||
}; |
|||
} |
|||
|
|||
function request(options, callback) { |
|||
if (typeof options === 'string') { |
|||
options = { uri: options }; |
|||
} |
|||
|
|||
options.method = options.method || 'GET'; |
|||
options.headers = options.headers || {}; |
|||
|
|||
if (!isNode) { |
|||
var xhr = new XMLHttpRequest(); |
|||
xhr.open(options.method, options.uri, true); |
|||
|
|||
Object.keys(options.headers).forEach(function(key) { |
|||
var val = options.headers[key]; |
|||
if (key === 'Content-Length') return; |
|||
if (key === 'Content-Transfer-Encoding') return; |
|||
xhr.setRequestHeader(key, val); |
|||
}); |
|||
|
|||
// For older browsers:
|
|||
// xhr.overrideMimeType('text/plain; charset=x-user-defined');
|
|||
|
|||
// Newer browsers:
|
|||
xhr.responseType = 'arraybuffer'; |
|||
|
|||
xhr.onload = function(event) { |
|||
var response = xhr.response; |
|||
var buf = new Uint8Array(response); |
|||
return callback(null, xhr, buf); |
|||
}; |
|||
|
|||
if (options.body) { |
|||
xhr.send(options.body); |
|||
} else { |
|||
xhr.send(null); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
return require('request')(options, callback); |
|||
} |
|||
|
|||
function sendPayment(msg, callback) { |
|||
if (arguments.length === 1) { |
|||
callback = msg; |
|||
msg = null; |
|||
} |
|||
|
|||
return request({ |
|||
method: 'POST', |
|||
uri: 'https://localhost:' + port + '/-/request', |
|||
headers: { |
|||
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE |
|||
+ ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, |
|||
'Content-Type': 'application/octet-stream', |
|||
'Content-Length': 0 |
|||
}, |
|||
encoding: null |
|||
}, function(err, res, body) { |
|||
if (err) return callback(err); |
|||
|
|||
body = PayPro.PaymentRequest.decode(body); |
|||
|
|||
var pr = new PayPro(); |
|||
pr = pr.makePaymentRequest(body); |
|||
|
|||
var ver = pr.get('payment_details_version'); |
|||
var pki_type = pr.get('pki_type'); |
|||
var pki_data = pr.get('pki_data'); |
|||
var details = pr.get('serialized_payment_details'); |
|||
var sig = pr.get('signature'); |
|||
|
|||
// Verify Signature
|
|||
var verified = pr.verify(); |
|||
|
|||
if (!verified) { |
|||
return callback(new Error('Server sent a bad signature.')); |
|||
} |
|||
|
|||
details = PayPro.PaymentDetails.decode(details); |
|||
var pd = new PayPro(); |
|||
pd = pd.makePaymentDetails(details); |
|||
var network = pd.get('network'); |
|||
var outputs = pd.get('outputs'); |
|||
var time = pd.get('time'); |
|||
var expires = pd.get('expires'); |
|||
var memo = pd.get('memo'); |
|||
var payment_url = pd.get('payment_url'); |
|||
var merchant_data = pd.get('merchant_data'); |
|||
|
|||
print('You are currently on this BTC network:'); |
|||
print(network); |
|||
print('The server sent you a message:'); |
|||
print(memo); |
|||
|
|||
var refund_outputs = []; |
|||
|
|||
var rpo = new PayPro(); |
|||
rpo = rpo.makeOutput(); |
|||
rpo.set('amount', 0); |
|||
rpo.set('script', new Buffer([ |
|||
118, // OP_DUP
|
|||
169, // OP_HASH160
|
|||
76, // OP_PUSHDATA1
|
|||
20, // number of bytes
|
|||
0xcf, |
|||
0xbe, |
|||
0x41, |
|||
0xf4, |
|||
0xa5, |
|||
0x18, |
|||
0xed, |
|||
0xc2, |
|||
0x5a, |
|||
0xf7, |
|||
0x1b, |
|||
0xaf, |
|||
0xc7, |
|||
0x2f, |
|||
0xb6, |
|||
0x1b, |
|||
0xfc, |
|||
0xfc, |
|||
0x4f, |
|||
0xcd, |
|||
136, // OP_EQUALVERIFY
|
|||
172 // OP_CHECKSIG
|
|||
])); |
|||
|
|||
refund_outputs.push(rpo.message); |
|||
|
|||
// We send this to the serve after receiving a PaymentRequest
|
|||
var pay = new PayPro(); |
|||
pay = pay.makePayment(); |
|||
pay.set('merchant_data', merchant_data); |
|||
pay.set('transactions', [createTX()]); |
|||
pay.set('refund_to', refund_outputs); |
|||
|
|||
msg = msg || 'Hi server, I would like to give you some money.'; |
|||
|
|||
if (isNode && argv.memo) { |
|||
msg = argv.memo; |
|||
} |
|||
|
|||
pay.set('memo', msg); |
|||
pay = pay.serialize(); |
|||
|
|||
return request({ |
|||
method: 'POST', |
|||
uri: payment_url, |
|||
headers: { |
|||
// BIP-71
|
|||
'Accept': PayPro.PAYMENT_REQUEST_CONTENT_TYPE |
|||
+ ', ' + PayPro.PAYMENT_ACK_CONTENT_TYPE, |
|||
'Content-Type': 'application/bitcoin-payment', |
|||
'Content-Length': pay.length + '', |
|||
'Content-Transfer-Encoding': 'binary' |
|||
}, |
|||
body: pay, |
|||
encoding: null |
|||
}, function(err, res, body) { |
|||
if (err) return callback(err); |
|||
body = PayPro.PaymentACK.decode(body); |
|||
var ack = new PayPro(); |
|||
ack = ack.makePaymentACK(body); |
|||
var payment = ack.get('payment'); |
|||
var memo = ack.get('memo'); |
|||
print('Our payment was acknowledged!'); |
|||
print('Message from Merchant: %s', memo); |
|||
return callback(); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* Helpers |
|||
*/ |
|||
|
|||
// URI Spec
|
|||
// A backwards-compatible request:
|
|||
// bitcoin:mq7se9wy2egettFxPbmn99cK8v5AFq55Lx?amount=0.11&r=https://merchant.com/pay.php?h%3D2a8628fc2fbe
|
|||
// Non-backwards-compatible equivalent:
|
|||
// bitcoin:?r=https://merchant.com/pay.php?h%3D2a8628fc2fbe
|
|||
function parseMerchantURI(uri) { |
|||
uri = uri || 'bitcoin:?r=https://localhost:' + port + '/-/request'; |
|||
var query, id; |
|||
if (uri.indexOf('bitcoin:') !== 0) { |
|||
throw new Error('Not a Bitcoin URI.'); |
|||
} |
|||
if (~uri.indexOf(':?')) { |
|||
query = uri.split(':?')[1]; |
|||
} else { |
|||
// Legacy URI
|
|||
uri = uri.substring('bitcoin:'.length); |
|||
uri = uri.split('?'); |
|||
id = uri[0]; |
|||
query = uri[1]; |
|||
} |
|||
query = parseQS(query); |
|||
if (!query.r) { |
|||
throw new Error('No uri.'); |
|||
} |
|||
if (id) { |
|||
query.id = id; |
|||
} |
|||
return query; |
|||
} |
|||
|
|||
function parseQS(query) { |
|||
var out = {}; |
|||
var parts = query.split('&'); |
|||
parts.forEach(function(part) { |
|||
var parts = part.split('='); |
|||
var key = parts[0]; |
|||
var value = parts[1]; |
|||
out[key] = value; |
|||
}); |
|||
return out; |
|||
} |
|||
|
|||
function createTX() { |
|||
// Addresses
|
|||
var addrs = [ |
|||
'mzTQ66VKcybz9BD1LAqEwMFp9NrBGS82sY', |
|||
'mmu9k3KzsDMEm9JxmJmZaLhovAoRKW3zr4', |
|||
'myqss64GNZuWuFyg5LTaoTCyWEpKH56Fgz' |
|||
]; |
|||
|
|||
// Private keys in WIF format (see TransactionBuilder.js for other options)
|
|||
var keys = [ |
|||
'cVvr5YmWVAkVeZWAawd2djwXM4QvNuwMdCw1vFQZBM1SPFrtE8W8', |
|||
'cPyx1hXbe3cGQcHZbW3GNSshCYZCriidQ7afR2EBsV6ReiYhSkNF' |
|||
// 'cUB9quDzq1Bj7pocenmofzNQnb1wJNZ5V3cua6pWKzNL1eQtaDqQ'
|
|||
]; |
|||
|
|||
var unspent = [{ |
|||
// http://blockexplorer.com/testnet/rawtx/1fcfe898cc2612f8b222bd3b4ac8d68bf95d43df8367b71978c184dea35bde22
|
|||
'txid': '1fcfe898cc2612f8b222bd3b4ac8d68bf95d43df8367b71978c184dea35bde22', |
|||
'vout': 1, |
|||
'address': addrs[0], |
|||
'scriptPubKey': '76a94c14cfbe41f4a518edc25af71bafc72fb61bfcfc4fcd88ac', |
|||
'amount': 1.60000000, |
|||
'confirmations': 9 |
|||
}, |
|||
|
|||
{ |
|||
// http://blockexplorer.com/testnet/rawtx/0624c0c794447b0d2343ae3d20382983f41b915bb115a834419e679b2b13b804
|
|||
'txid': '0624c0c794447b0d2343ae3d20382983f41b915bb115a834419e679b2b13b804', |
|||
'vout': 1, |
|||
'address': addrs[1], |
|||
'scriptPubKey': '76a94c14460376539c219c5e3274d86f16b40e806b37817688ac', |
|||
'amount': 1.60000000, |
|||
'confirmations': 9 |
|||
} |
|||
]; |
|||
|
|||
// define transaction output
|
|||
var outs = [{ |
|||
address: addrs[2], |
|||
amount: 0.00003000 |
|||
}]; |
|||
|
|||
// set change address
|
|||
var opts = { |
|||
remainderOut: { |
|||
address: addrs[0] |
|||
} |
|||
}; |
|||
|
|||
var tx = new TransactionBuilder(opts) |
|||
.setUnspent(unspent) |
|||
.setOutputs(outs) |
|||
.sign(keys) |
|||
.build(); |
|||
|
|||
print(''); |
|||
print('Customer created transaction:'); |
|||
print(tx.getStandardizedObject()); |
|||
print(''); |
|||
|
|||
return tx.serialize(); |
|||
} |
|||
|
|||
/** |
|||
* Helpers |
|||
*/ |
|||
|
|||
function clientLog(args, isError) { |
|||
var log = document.getElementById('log'); |
|||
var msg = args[0]; |
|||
if (typeof msg !== 'string') { |
|||
msg = JSON.stringify(msg, null, 2); |
|||
if (isError) msg = '<span style="color:red;">' + msg + '</span>'; |
|||
log.innerHTML += msg + '\n'; |
|||
return; |
|||
} |
|||
var i = 0; |
|||
msg = msg.replace(/%[sdji]/g, function(ch) { |
|||
i++; |
|||
if (ch === 'j' || typeof args[i] !== 'string') { |
|||
return JSON.stringify(args[i]); |
|||
} |
|||
return args[i]; |
|||
}); |
|||
if (isError) msg = '<span style="color:red;">' + msg + '</span>'; |
|||
log.innerHTML += msg + '\n'; |
|||
} |
|||
|
|||
function print() { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
if (!isNode) { |
|||
return clientLog(args, false); |
|||
} |
|||
var util = require('util'); |
|||
if (typeof args[0] !== 'string') { |
|||
args[0] = util.inspect(args[0], null, 20, true); |
|||
console.log('\x1b[32mCustomer:\x1b[m'); |
|||
console.log(args[0]); |
|||
return; |
|||
} |
|||
if (!args[0]) return process.stdout.write('\n'); |
|||
var msg = '\x1b[32mCustomer:\x1b[m ' |
|||
+ util.format.apply(util.format, args); |
|||
return process.stdout.write(msg + '\n'); |
|||
} |
|||
|
|||
function error() { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
if (!isNode) { |
|||
return clientLog(args, true); |
|||
} |
|||
var util = require('util'); |
|||
if (typeof args[0] !== 'string') { |
|||
args[0] = util.inspect(args[0], null, 20, true); |
|||
console.log('\x1b[32mCustomer:\x1b[m'); |
|||
console.log(args[0]); |
|||
return; |
|||
} |
|||
if (!args[0]) return process.stderr.write('\n'); |
|||
var msg = '\x1b[32mCustomer:\x1b[m \x1b[31m' |
|||
+ util.format.apply(util.format, args) + '\x1b[m'; |
|||
return process.stderr.write(msg + '\n'); |
|||
} |
|||
|
|||
/** |
|||
* Execute |
|||
*/ |
|||
|
|||
if (isNode) { |
|||
if (!module.parent) { |
|||
sendPayment(function(err) { |
|||
if (err) return error(err.message); |
|||
print('Payment sent successfully.'); |
|||
}); |
|||
} else { |
|||
var customer = sendPayment; |
|||
customer.sendPayment = sendPayment; |
|||
customer.print = print; |
|||
customer.error = error; |
|||
module.exports = customer; |
|||
} |
|||
} else { |
|||
var customer = sendPayment; |
|||
customer.sendPayment = sendPayment; |
|||
customer.print = print; |
|||
customer.error = error; |
|||
window.customer = window.sendPayment = customer; |
|||
window.onload = function() { |
|||
var form = document.getElementsByTagName('form')[0]; |
|||
var memo = document.querySelector('input[name="memo"]'); |
|||
var loader = document.getElementById('load'); |
|||
loader.style.display = 'none'; |
|||
form.onsubmit = function() { |
|||
form.style.display = 'none'; |
|||
loader.style.display = 'block'; |
|||
form.onsubmit = function() { return false; }; |
|||
customer.sendPayment(memo.value || null, function(err) { |
|||
loader.style.display = 'none'; |
|||
if (err) return error(err.message); |
|||
print('Payment sent successfully.'); |
|||
}); |
|||
return false; |
|||
}; |
|||
}; |
|||
} |
|||
|
|||
}).call(function() { |
|||
return this || (typeof window !== 'undefined' ? window : global); |
|||
}()); |
@ -0,0 +1,28 @@ |
|||
<!doctype html> |
|||
<title>Payment Protocol</title> |
|||
<link rel="stylesheet" href="/style.css"> |
|||
|
|||
<h1>Payment Protocol</h1> |
|||
|
|||
<p> |
|||
<a href="https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki"><strong>BIP-70</strong></a> |
|||
is here! |
|||
</p> |
|||
|
|||
<form method="POST" action="/-/request"> |
|||
<ul> |
|||
<li>BitPay T-Shirt: <strong>0.00002000 BTC</strong></li> |
|||
<li>BitPay Mug: <strong>0.00001000 BTC</strong></li> |
|||
</ul> |
|||
<p>These items will cost you a total of <strong>0.00003000 BTC</strong>.</p> |
|||
<p>Would you like to checkout?</p> |
|||
<input type="text" name="memo" placeholder="Message to merchant..." value=""> |
|||
<input type="submit" value="Checkout"> |
|||
</form> |
|||
|
|||
<p id="load">Loading...</p> |
|||
|
|||
<pre id="log"></pre> |
|||
|
|||
<script src="./bitcore.js"></script> |
|||
<script src="./customer.js"></script> |
@ -0,0 +1,3 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
module.exports = require('./server'); |
@ -0,0 +1,375 @@ |
|||
#!/bin/bash
|
|||
|
|||
/** |
|||
* Payment-Server - A Payment Protocol demonstration. |
|||
* Copyright (c) 2014, BitPay |
|||
* https://github.com/bitpay/bitcore
|
|||
*/ |
|||
|
|||
/** |
|||
* Modules |
|||
*/ |
|||
|
|||
var https = require('https'); |
|||
var fs = require('fs'); |
|||
var path = require('path'); |
|||
var qs = require('querystring'); |
|||
var crypto = require('crypto'); |
|||
|
|||
// Disable strictSSL
|
|||
process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = '0'; |
|||
|
|||
/** |
|||
* Dependencies |
|||
*/ |
|||
|
|||
var argv = require('optimist').argv; |
|||
var express = require('express'); |
|||
var bitcore = require('../../'); |
|||
|
|||
var PayPro = bitcore.PayPro; |
|||
var Transaction = bitcore.Transaction; |
|||
var TransactionBuilder = bitcore.TransactionBuilder; |
|||
|
|||
/** |
|||
* Variables |
|||
*/ |
|||
|
|||
var isNode = !argv.b && !argv.browser; |
|||
|
|||
var app = express(); |
|||
|
|||
var x509 = { |
|||
priv: fs.readFileSync(__dirname + '/../../test/data/x509.key'), |
|||
pub: fs.readFileSync(__dirname + '/../../test/data/x509.pub'), |
|||
der: fs.readFileSync(__dirname + '/../../test/data/x509.der'), |
|||
pem: fs.readFileSync(__dirname + '/../../test/data/x509.crt') |
|||
}; |
|||
|
|||
var server = https.createServer({ |
|||
key: fs.readFileSync(__dirname + '/../../test/data/x509.key'), |
|||
cert: fs.readFileSync(__dirname + '/../../test/data/x509.crt') |
|||
}); |
|||
|
|||
/** |
|||
* Ignore Cache Headers |
|||
* Allow CORS |
|||
* Accept Payments |
|||
*/ |
|||
|
|||
app.use(function(req, res, next) { |
|||
var setHeader = res.setHeader; |
|||
|
|||
res.setHeader = function(name) { |
|||
switch (name) { |
|||
case 'Cache-Control': |
|||
case 'Last-Modified': |
|||
case 'ETag': |
|||
return; |
|||
} |
|||
return setHeader.apply(res, arguments); |
|||
}; |
|||
|
|||
res.setHeader('Access-Control-Allow-Origin', '*'); |
|||
|
|||
res.setHeader('Accept', PayPro.PAYMENT_CONTENT_TYPE); |
|||
|
|||
return next(); |
|||
}); |
|||
|
|||
/** |
|||
* Body Parser |
|||
*/ |
|||
|
|||
app.use('/-/pay', function(req, res, next) { |
|||
var buf = []; |
|||
|
|||
req.on('error', function(err) { |
|||
error('Request Error: %s', err.message); |
|||
try { |
|||
req.socket.destroy(); |
|||
} catch (e) { |
|||
; |
|||
} |
|||
}); |
|||
|
|||
req.on('data', function(data) { |
|||
buf.push(data); |
|||
}); |
|||
|
|||
req.on('end', function(data) { |
|||
if (data) buf.push(data); |
|||
buf = Buffer.concat(buf, buf.length); |
|||
req.paymentData = buf; |
|||
return next(); |
|||
}) |
|||
}); |
|||
|
|||
/** |
|||
* Router |
|||
*/ |
|||
|
|||
// Not used in express 4.x
|
|||
// app.use(app.router);
|
|||
|
|||
/** |
|||
* Receive "I want to pay" |
|||
*/ |
|||
|
|||
app.uid = 0; |
|||
|
|||
app.post('/-/request', function(req, res, next) { |
|||
print('Received payment "request" from %s.', req.socket.remoteAddress); |
|||
|
|||
var outputs = []; |
|||
|
|||
var po = new PayPro(); |
|||
po = po.makeOutput(); |
|||
// number of satoshis to be paid
|
|||
po.set('amount', 0); |
|||
// a TxOut script where the payment should be sent. similar to OP_CHECKSIG
|
|||
po.set('script', new Buffer([ |
|||
118, // OP_DUP
|
|||
169, // OP_HASH160
|
|||
76, // OP_PUSHDATA1
|
|||
20, // number of bytes
|
|||
0xcf, |
|||
0xbe, |
|||
0x41, |
|||
0xf4, |
|||
0xa5, |
|||
0x18, |
|||
0xed, |
|||
0xc2, |
|||
0x5a, |
|||
0xf7, |
|||
0x1b, |
|||
0xaf, |
|||
0xc7, |
|||
0x2f, |
|||
0xb6, |
|||
0x1b, |
|||
0xfc, |
|||
0xfc, |
|||
0x4f, |
|||
0xcd, |
|||
136, // OP_EQUALVERIFY
|
|||
172 // OP_CHECKSIG
|
|||
])); |
|||
|
|||
outputs.push(po.message); |
|||
|
|||
/** |
|||
* Payment Details |
|||
*/ |
|||
|
|||
var mdata = new Buffer([0]); |
|||
app.uid++; |
|||
if (app.uid > 0xffff) { |
|||
throw new Error('UIDs bigger than 0xffff not supported.'); |
|||
} else if (app.uid > 0xff) { |
|||
mdata = new Buffer([(app.uid >> 8) & 0xff, (app.uid >> 0) & 0xff]) |
|||
} else { |
|||
mdata = new Buffer([0, app.uid]) |
|||
} |
|||
var now = Date.now() / 1000 | 0; |
|||
var pd = new PayPro(); |
|||
pd = pd.makePaymentDetails(); |
|||
pd.set('network', 'test'); |
|||
pd.set('outputs', outputs); |
|||
pd.set('time', now); |
|||
pd.set('expires', now * 60 * 60 * 24); |
|||
pd.set('memo', 'Hello, this is the server, we would like some money.'); |
|||
var port = +req.headers.host.split(':')[1] || server.port; |
|||
pd.set('payment_url', 'https://localhost:' + port + '/-/pay'); |
|||
pd.set('merchant_data', mdata); |
|||
|
|||
/* |
|||
* PaymentRequest |
|||
*/ |
|||
|
|||
var cr = new PayPro(); |
|||
cr = cr.makeX509Certificates(); |
|||
cr.set('certificate', [x509.der]); |
|||
|
|||
// We send the PaymentRequest to the customer
|
|||
var pr = new PayPro(); |
|||
pr = pr.makePaymentRequest(); |
|||
pr.set('payment_details_version', 1); |
|||
pr.set('pki_type', 'x509+sha256'); |
|||
pr.set('pki_data', cr.serialize()); |
|||
pr.set('serialized_payment_details', pd.serialize()); |
|||
pr.sign(x509.priv); |
|||
|
|||
pr = pr.serialize(); |
|||
|
|||
// BIP-71 - set the content-type
|
|||
res.setHeader('Content-Type', PayPro.PAYMENT_REQUEST_CONTENT_TYPE); |
|||
res.setHeader('Content-Length', pr.length + ''); |
|||
res.setHeader('Content-Transfer-Encoding', 'binary'); |
|||
|
|||
res.send(pr); |
|||
}); |
|||
|
|||
/** |
|||
* Receive Payment |
|||
*/ |
|||
|
|||
app.post('/-/pay', function(req, res, next) { |
|||
var body = req.paymentData; |
|||
|
|||
body = PayPro.Payment.decode(body); |
|||
|
|||
var pay = new PayPro(); |
|||
pay = pay.makePayment(body); |
|||
var merchant_data = pay.get('merchant_data'); |
|||
var transactions = pay.get('transactions'); |
|||
var refund_to = pay.get('refund_to'); |
|||
var memo = pay.get('memo'); |
|||
|
|||
print('Received payment from %s.', req.socket.remoteAddress); |
|||
print('Customer Message: %s', memo); |
|||
print('Payment Message:'); |
|||
print(pay); |
|||
|
|||
// We send this to the customer after receiving a Payment
|
|||
// Then we propogate the transaction through bitcoin network
|
|||
var ack = new PayPro(); |
|||
ack = ack.makePaymentACK(); |
|||
ack.set('payment', pay.message); |
|||
ack.set('memo', 'Thank you for your payment!'); |
|||
|
|||
ack = ack.serialize(); |
|||
|
|||
// BIP-71 - set the content-type
|
|||
res.setHeader('Content-Type', PayPro.PAYMENT_ACK_CONTENT_TYPE); |
|||
res.setHeader('Content-Length', ack.length + ''); |
|||
res.setHeader('Content-Transfer-Encoding', 'binary'); |
|||
|
|||
transactions = transactions.map(function(tx) { |
|||
tx.buffer = tx.buffer.slice(tx.offset, tx.limit); |
|||
var ptx = new bitcore.Transaction(); |
|||
ptx.parse(tx.buffer); |
|||
return ptx; |
|||
}); |
|||
|
|||
(function retry() { |
|||
var timeout = setTimeout(function() { |
|||
if (conn) { |
|||
transactions.forEach(function(tx) { |
|||
var id = tx.getHash().toString('hex'); |
|||
print(''); |
|||
print('Sending transaction with txid: %s', id); |
|||
print(tx.getStandardizedObject()); |
|||
|
|||
var pending = 1; |
|||
peerman.on('ack', function listener() { |
|||
if (!--pending) { |
|||
peerman.removeListener('ack', listener); |
|||
clearTimeout(timeout); |
|||
print('Transaction sent to peer successfully.'); |
|||
} |
|||
}); |
|||
|
|||
print('Broadcasting transaction...'); |
|||
conn.sendTx(tx); |
|||
}); |
|||
} else { |
|||
print('No BTC network connection. Retrying...'); |
|||
conn = peerman.getActiveConnection(); |
|||
retry(); |
|||
} |
|||
}, 1000); |
|||
})(); |
|||
|
|||
res.send(ack); |
|||
}); |
|||
|
|||
/** |
|||
* Bitcoin |
|||
*/ |
|||
|
|||
var conn; |
|||
|
|||
var peerman = new bitcore.PeerManager({ |
|||
network: 'testnet' |
|||
}); |
|||
|
|||
peerman.peerDiscovery = true; |
|||
|
|||
peerman.addPeer(new bitcore.Peer('testnet-seed.bitcoin.petertodd.org', 18333)); |
|||
peerman.addPeer(new bitcore.Peer('testnet-seed.bluematt.me', 18333)); |
|||
|
|||
peerman.on('connect', function() { |
|||
conn = peerman.getActiveConnection(); |
|||
}); |
|||
|
|||
peerman.start(); |
|||
|
|||
/** |
|||
* File Access |
|||
*/ |
|||
|
|||
app.use(express.static(__dirname)); |
|||
|
|||
/** |
|||
* Helpers |
|||
*/ |
|||
|
|||
var log = require('../../util/log'); |
|||
|
|||
log.err = error; |
|||
log.debug = error; |
|||
log.info = print; |
|||
|
|||
var util = require('util'); |
|||
|
|||
function print() { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
if (typeof args[0] !== 'string') { |
|||
args[0] = util.inspect(args[0], null, 20, true); |
|||
console.log('\x1b[34mServer:\x1b[m'); |
|||
console.log(args[0]); |
|||
return; |
|||
} |
|||
if (!args[0]) return process.stdout.write('\n'); |
|||
var msg = '\x1b[34mServer:\x1b[m ' |
|||
+ util.format.apply(util.format, args); |
|||
return process.stdout.write(msg + '\n'); |
|||
} |
|||
|
|||
function error() { |
|||
var args = Array.prototype.slice.call(arguments); |
|||
if (typeof args[0] !== 'string') { |
|||
args[0] = util.inspect(args[0], null, 20, true); |
|||
console.log('\x1b[34mServer:\x1b[m'); |
|||
console.log(args[0]); |
|||
return; |
|||
} |
|||
if (!args[0]) return process.stderr.write('\n'); |
|||
var msg = '\x1b[34mServer:\x1b[m \x1b[31m' |
|||
+ util.format.apply(util.format, args) |
|||
+ '\x1b[m'; |
|||
return process.stderr.write(msg + '\n'); |
|||
} |
|||
|
|||
/** |
|||
* Start Server |
|||
*/ |
|||
|
|||
server.on('request', app); |
|||
server.app = app; |
|||
server.port = +argv.p || +argv.port || 8080; |
|||
|
|||
if (!module.parent || path.basename(module.parent.filename) === 'index.js') { |
|||
server.listen(server.port, function(addr) { |
|||
if (!isNode) return; |
|||
var customer = require('./customer'); |
|||
customer.sendPayment(function(err) { |
|||
if (err) return error(err.message); |
|||
customer.print('Payment sent successfully.'); |
|||
}); |
|||
}); |
|||
} else { |
|||
module.exports = server; |
|||
} |
@ -0,0 +1,66 @@ |
|||
/** |
|||
* Stylesheet for Payment Protocol |
|||
*/ |
|||
|
|||
/** |
|||
* Raleway |
|||
*/ |
|||
|
|||
@font-face { |
|||
font-family: 'Raleway'; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
src: local('Raleway'), url(http://themes.googleusercontent.com/static/fonts/raleway/v7/cIFypx4yrWPDz3zOxk7hIQLUuEpTyoUstqEm5AMlJo4.woff) format('woff'); |
|||
} |
|||
|
|||
/** |
|||
* Ubuntu |
|||
*/ |
|||
|
|||
@font-face { |
|||
font-family: 'Ubuntu'; |
|||
font-style: normal; |
|||
font-weight: 400; |
|||
src: local('Ubuntu'), url(https://themes.googleusercontent.com/static/fonts/ubuntu/v5/lhhB5ZCwEkBRbHMSnYuKyA.ttf) format('truetype'); |
|||
} |
|||
|
|||
article, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary { |
|||
display: block |
|||
} |
|||
|
|||
html { |
|||
width: 840px; |
|||
font-family: "Raleway", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", "Helvetica", "Verdana", sans-serif; |
|||
font-size: 22px; |
|||
line-height: 30px; |
|||
-webkit-font-smoothing: antialiased; |
|||
text-rendering: optimizeLegibility; |
|||
color: #000; |
|||
background-image: -webkit-gradient( linear, 0 0, 0 100%, color-stop(0, rgba(0, 0, 0, 0.15)), color-stop(0.2, transparent), color-stop(0.8, transparent), color-stop(1, rgba(0, 0, 0, 0.15))); |
|||
background-image: -moz-linear-gradient( -90deg, rgba(0, 0, 0, 0.15) 0%, transparent 20%, transparent 80%, rgba(0, 0, 0, 0.15) 100%); |
|||
background-attachment: fixed; |
|||
background-color: #c1d3e3; |
|||
} |
|||
|
|||
input { |
|||
display: block; |
|||
margin: 0 0 20px 0; |
|||
font-family: "Raleway", "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", "Helvetica", "Verdana", sans-serif; |
|||
font-size: 22px; |
|||
line-height: 30px; |
|||
-webkit-font-smoothing: antialiased; |
|||
text-rendering: optimizeLegibility; |
|||
color: #000; |
|||
} |
|||
|
|||
body { |
|||
padding: 20px; |
|||
text-shadow: rgba(0, 0, 0, 0.025) 0 -1px 0, rgba(255, 255, 255, 0.2) 0 1px 0; |
|||
} |
|||
|
|||
h1 { |
|||
width: 350px; |
|||
color: #000; |
|||
font: 60px/1.0 "Ubuntu", "Helvetica", "Verdana", "Arial", sans-serif; |
|||
margin-left: 20px; |
|||
} |
Loading…
Reference in new issue