Monday, May 25, 2015

Using the Coinbase Merchant API with Node

I've been writing about different pieces of my Bitcoin integration since last week when Wednesday's comic broke the Fourth Wall while building a paywall. First I explained the client-side code that handled the 402 response. Then on Friday I gave an outline of my Express middleware that controls the access to non-free resources. I used several stubs and mock objects in that post because the actual implementations of the pieces will vary based on where they are integrated - it's just not very interesting what I did with my database access because it's probably different from yours.

But one last part in which people may be interested is the integration with Coinbase's Merchant API via their Node module. I used Coinbase in my implementation because their API was very easy to work with and the integration points are extremely modular. It is set up in such a way that I could easily replace Coinbase with another service such as BlockCypher or calls to the Blockchain API. But I had a wallet with Coinbase already so I went with that...

There are really only two interactions with Coinbase that I perform. One is creating a new Bitcoin address that is tied to my Coinbase wallet. The other is processing the callback from Coinbase informing my server that payment has been made.

Creating a new address is very simple if you've set up your Coinbase API access correctly. Log in to your Coinbase account and go to https://www.coinbase.com/settings/api to create a new API key. Make sure it has the "address" permission and it is enabled. You have to verify every step with 2-factor authentication so have your phone nearby. If possible, set the IP restriction on your key as an extra precaution against unauthorized access.

Your key has two parts, the key and the secret. After creation, you can copy them and use them with the Coinbase Node module. Keep them secure! Don't put them directly in your code and accidentally push it to a public repo on GitHub! Besides the key and secret, you'll also need the Coinbase account number for which you want to generate addresses. Your account number is basically just a Coinbase identification for each wallet you have with them. It's displayed as part of the URL when you are looking at your wallet on their web site.

Once you have those three things, to generate a new address you do something like this:

var Client = require("coinbase").Client;

var client = new Client({
    apiKey: "your-coinbase-api-key",
    apiSecret: "your-coinbase-api-secret"
});

var accountId = "your-coinbase-account-id";

var Account   = require("coinbase").model.Account;
var myBtcAcct = new Account(client, {
    "id" : accountId
});

var secretCode = "secret-code-unique-to-each-address";

var args = {
    callback_url: "http://example.com/callback?secret=" + secretCode,
    label: "your address label"
};

myBtcAcct.createAddress(args, function(err, data) {

    if (err) {
        console.log("unable to create address: " + err);
    } else {
        console.log(data.address);
    }

});

The basic steps are to create a client, create an account object with that client, and then call createAddress on that account. The createAddress function takes an arguments object as the first parameter, in which you specify an optional callback URL and label. The label is just some text you can add for your own reading later. It has no functional aspect. But the callback URL is very important. As you can see on line 18, I use a secret code as part of the callback query string. In my example it is hard-coded, but in real life you'll want to generate a different one for every address. If you keep the secret code only on your server - never send it to the client in any way - it ensures that someone won't be able to send their own POST to your callback URL and trick your server into thinking that they have paid. Store the address and secret code in your database and make sure they match in your callback processor. If they don't, something is wrong and you shouldn't honor the validity of the callback!

If you wanted, you could take the code above and plug it into the example app from Friday's post to generate real addresses. If you wanted to handle the real callbacks in that app as well, you'd want to be able to process the actual data format that comes from Coinbase with those POSTs. The POST to your callback URL will contain this as the body:

{
  "address": "somebitcoinaddress",
  "amount": 1.234,
  "transaction": {
    "hash": "somebiglonghashvaluething"
  }
}

For me, the most important parts were the secret code in the callback query string, the Bitcoin address from the body, and the amount from the body. I don't need the transaction hash, but other applications might. The two pieces of information from the POST body are easy to extract if you use the body-parser middleware for JSON. Here is an example of an app that just takes the callbacks - you could substitute the callback route from Friday's post with this one if you want.

var express = require("express");
var bodyParser = require('body-parser');

var app = express();
app.use(bodyParser.json());

app.get("/callback", function(req, res, next) {

    var secretCode = req.query.secret;
    var addr = req.body.address;
    if (addr) {

        var amountRecieved = req.body.amount;

        // make sure the secret and address
        // match your records, and if so
        // mark the transaction as paid if
        // the amount >= the price.

    } else {

        console.log("something went wrong!");

    }

    // always give coinbase a 200
    res.sendStatus(200);

});

// ------------ start listening
var server = app.listen(3000, function() {
    console.log("listening on port %d", server.address().port);
});

One thing to note (that I missed at first) is that a customer doesn't have to pay the full amount in a single transaction. If you were charging 0.05 BTC, it would be perfectly fine to get two callbacks, each indicating a payment of 0.025 BTC. When handling the data, make sure you add up amounts sent to the same address instead of just taking the latest.

I think I've covered all the major parts of my paywall function now. Just to be clear, I'm not actually advocating everyone putting up paywalls on all their content. Last week's comic was just a joke that happened to include a novel way to handle online micropayments. I do believe that there are lots of legitimate use cases for Bitcoin microtransactions on the web, particularly for content creators such as myself. Charging a few cents worth of cryptocurrency here and there for a pieces of premium content or special features could be a viable alternative to displaying ads on the web (tipping is another alternative, which I've discussed previously).

So enjoy today's comic - completely free!

Amphibian.com comic for 25 May 2015

No comments:

Post a Comment