Bitcore - Payment Protocol Library Implementation
I recently attempted to implement the Bitcore Payment Protocol Library and ran into several pieces that took me a few hours of debugging to wrap my head around.
I've decided to share some sample code that I wrote to, hopefully, save others from this headache in case they also decide to implement using this library.
The notable "gotchas" were these:
When setting the Payment Details from an output, use the ".message" field: e.g. details.set('outputs', output.message);
Bitcore Payment Protocol Lib expects the Certificates in "der" format - not "pem". For the Private Key, ".pem" appears fine.
Be careful when you are reading the above Certificates in. You must read these as binary files - not UTF8 files.
If you're using a Certificate that is not derived direct from a Root Cert Authority (e.g. you're using LetsEncrypt), then you must include the chain of certs.
Code with GOTCHA lines is below (untested, but is mostly copy+paste of what I used with a few modifications).
This doesn't appear to be working with Bitcoin.com Wallet still. If I work out why, I'll post amended code - but BIP70 implementations seem to be inconsistent across the board.
const express = require('express')
const fs = require('fs');
const router = express.Router()
// GOTCHA - Not mine, but apparently order here matters!
const PaymentProtocol = require('bitcore-payment-protocol');
const bitcore = require('bitcore-lib-cash');
// BIP70 Payment Request Endpoint
router.get('/req', async (req, res) => {
// Setup certificates
// GOTCHA: NOTE THAT THESE ARE IN .der FORMAT - DO NOT READ AS UTF8
var certificate = fs.readFileSync('./cert.der');
var chain = fs.readFileSync('./chain.der');
// GOTCHA: NOTE THAT THIS IS IN .pem and that I'm therefore reading as "utf8"
var privKey = fs.readFileSync('./privkey.pem', 'utf8');
var now = Date.now() / 1000 | 0;
// Create an output
// GOTCHA: ADDRESS MUST BE A BITCOINCASH: ADDRESS!
var address = bitcore.Address.fromString("bitcoincash:qz2fn6wwwxs2wcdf9cfdhv4ln0qvjadg6csjcjasuf");
var script = bitcore.Script.buildPublicKeyHashOut(address);
var rawScript = new Buffer(script.toString(), 'ucs2');
var output = new PaymentProtocol().makeOutput();
output.set('amount', 10000);
output.set('script', rawScript);
// Construct the payment details
var details = new PaymentProtocol('BCH').makePaymentDetails();
details.set('network', 'main');
details.set('outputs', output.message);
details.set('time', now);
details.set('expires', now + 60 * 60 * 24);
details.set('memo', "Message that the user will see");
details.set('payment_url', `https://your-service.com/ack`);
details.set('merchant_data', new Buffer("INVOICE_ID_AND_THINGS_OF_THAT_SORT")); // identify the request
// Load the X509 certificate
var certificates = new PaymentProtocol().makeX509Certificates();
// GOTCHA: CHAIN THE CERTIFICATES - THIS IS WHY IT'S AN ARRAy
certificates.set('certificate', [certificate, chain]);
// Form the request
var request = new PaymentProtocol().makePaymentRequest();
request.set('payment_details_version', 1);
request.set('pki_type', 'x509+sha256');
request.set('pki_data', certificates.serialize());
request.set('serialized_payment_details', details.serialize());
request.sign(privKey);
// Serialize the request
var rawBody = request.serialize();
// Set output headers
res.set({
'Content-Type': PaymentProtocol.LEGACY_PAYMENT.BCH.REQUEST_CONTENT_TYPE,
'Content-Length': request.length,
'Content-Transfer-Encoding': 'binary'
});
res.send(rawBody);
});
// GOTCHA
// In ExpressJS, due to the Content-Type header, req.body will be empty by default.
// You have to explicitly tell ExpressJS to process raw body as follows:
// app.use(bodyParser.raw({ type:'*/*' }));
// That said, I was lazy - you can probably just put this middleware straight
// into this endpoint and actually select based on Content Type.
router.post('/ack', async (req, res) => {
// Decode payment
var body = PaymentProtocol.Payment.decode(req.body);
var payment = new PaymentProtocol().makePayment(body);
var merchantData = payment.get('merchant_data');
var transactions = payment.get('transactions');
var refundTo = payment.get('refund_to');
var memo = payment.get('memo');
// TODO Send payment
// Make a payment acknowledgement
var ack = new PaymentProtocol().makePaymentACK();
ack.set('payment', payment.message);
ack.set('memo', 'Thank you for your payment!');
var rawBody = ack.serialize();
res.set({
'Content-Type': PaymentProtocol.LEGACY_PAYMENT.BCH.PAYMENT_ACK_CONTENT_TYPE,
'Content-Length': rawBody.length,
'Content-Transfer-Encoding': 'binary'
});
res.send(rawBody);
});
Useful links:
https://github.com/bitpay/bitcore-payment-protocol
https://github.com/bitpay/bitcore-payment-protocol/blob/master/docs/index.md
https://medium.com/@nusrath501khan/creating-a-bip-70-payment-request-183933c33259
Godspeed.
Great to see more technical articles. Personally, I'd add "JavaScript" somewhere in the title - otherwise it's not obvious about which language this is. And I agree with btcfork - a photo of Jeffrey Epstein as a lead image is pretty weird for this article :)