paypro: add payment protocol example.
This commit is contained in:
parent
078d85ea19
commit
8c74b94791
|
@ -0,0 +1 @@
|
|||
../../browser/bundle.js
|
|
@ -0,0 +1,493 @@
|
|||
/**
|
||||
* 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 = 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,20 @@
|
|||
{
|
||||
"name": "payment-server",
|
||||
"description": "Payment Protocol (BIP-70) for Bitcoin",
|
||||
"author": "Christopher Jeffrey",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.js",
|
||||
"bin": "./index.js",
|
||||
"preferGlobal": false,
|
||||
"repository": "git://github.com/bitpay/bitcore.git",
|
||||
"homepage": "https://github.com/bitpay/bitcore",
|
||||
"bugs": { "url": "https://github.com/bitpay/bitcore/issues" },
|
||||
"keywords": ["bitcoin", "bip-70", "payment", "protocol"],
|
||||
"tags": ["bitcoin", "bip-70", "payment", "protocol"],
|
||||
"dependencies": {
|
||||
"express": "4.6.1",
|
||||
"request": "2.39.0",
|
||||
"optimist": "0.6.1"
|
||||
},
|
||||
"engines": { "node": ">= 0.10.0" }
|
||||
}
|
|
@ -0,0 +1,376 @@
|
|||
#!/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('bitcore');
|
||||
|
||||
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 bitcorePath = path.dirname(require.resolve('bitcore/package.json'));
|
||||
var log = require(bitcorePath + '/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