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