read.cash Log in
@jimtendo

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.

$2.15

8 comments

Log in to join in Reading is open to everyone. Replying needs an account.