Wednesday, May 20, 2015

Using HTTP 402 for a Bitcoin Paywall

Bitcoin. Perfect for Microtransactions?
The punchline for today's comic is hidden behind a paywall. It is a literal wall, built by the frogs to hide the last frame of the comic. However, if you have some Bitcoin to spare (it costs 0.001 coins, about $0.25 US at the present time) you can actually pay for the wall to be removed.

A fair amount of interesting work went in to this comic, but it all revolves around the concept of using the HTTP 402 response code.

Response code 402 is officially "reserved for future use" but I felt that it was about time the future showed up. It's 2015. Where is my moon colony?? Anyway, 402 means "Payment Required" and was apparently intended to be a way for web servers to indicate to clients that the requested resource had to be purchased. Unfortunately, a way to pay for those resources was never worked out and the response code has languished in the realm of TBD for many years.

But these days we have Ajax, REST web services, and Bitcoin. It's all coming together. People have kicked around the notion of integrating 402's with Bitcoin transactions for a few years now but no significant implementation has emerged. With today's comic, sadly, nothing has changed.

I did, however, create a functional web content paywall for Bitcoin microtransactions. Here's how it works...

When a client - be it a human-operated web browser or another computer program - accesses a URL which does not dispense free content, the server will return a 402 response instead of the content. That response also includes three special headers, examples of which are shown here:

X-Payment-Types-Accepted: Bitcoin
X-Payment-Address-Bitcoin: putarealbitcoinaddresshere
X-Payment-Amount-Bitcoin: 1.234

The first, X-Payment-Types-Accepted, should be a comma-separated list of acceptable payment types. In my example and in my comic I am only accepting Bitcoin for now, although a similar form of payment such as Litecoin would easily be possible in place of or in addition to Bitcoin. Each item listed in this header should be used to check the other 2 headers. The X-Payment-Address-XXX header specifies an address to which the payment should be sent. The X-Payment-Amount-XXX header specifies how much this particular resource costs.

The key is that the last part of the address and amount headers should match an item in the list of types accepted. If I wanted to accept either Bitcoin or Litecoin, my headers might look like this:

X-Payment-Types-Accepted: Bitcoin, Litecoin
X-Payment-Address-Bitcoin: putarealbitcoinaddresshere
X-Payment-Amount-Bitcoin: 1.234
X-Payment-Address-Litecoin: putareallitecoinaddresshere
X-Payment-Amount-Litecoin: 2.345

It's then up to the client to deal with this response. Today, browsers will silently ignore this extra information and just tell you that the response code means "Payment Required" without letting you know how to pay. In the future, browsers might prompt you to submit the payment (see this: Zero Click Bitcoin Micropayments). Today, though, you have to do things manually.

In certain cases, the server could include the Bitcoin address and price in the HTML that accompanies the 402 response. Since I am just requesting the paid resources via Ajax, I didn't bother with that and don't include anything human-readable in the response. Here is how my client-side code works when you view the comic:

function checkForPayment() {

    $.ajax({
        url: "/paidContent/paywall-comic",
        dataType: "html",
        success: function(data) {

            $('#comicArea').html(data);

        },
        error: function(xhr, respText, et) {

            if (xhr.status === 402) {

                var addr = xhr.getResponseHeader("X-Payment-Address-Bitcoin");
                var cost = xhr.getResponseHeader("X-Payment-Amount-Bitcoin");

                var bcurl = "bitcoin:" + addr + "?amount=" + cost;
                var bcqr = encodeURIComponent(bcurl);

                if ($("#paydiv").html() === "") {
                    $("#paydiv").html("<p>Pay with Bitcoin!<br>" +
                                      "<a href='" + bcurl + "'><img src='/qrc?text=" + 
                                      bcqr + "'></a><br>" +
                                      "Send " + cost + " BTC to<br>" +
                                      addr + "</p>");
                }

                setTimeout(checkForPayment, 2000);

            } else {
                console.log("failed");
            }

        }

    });

}

When the free version (missing the last frame) of the comic loads, checkForPayment() is called and attempts to get the data for the complete comic from the URL /paidContent/paywall-comic. Anything on my site with a path starting with /paidContent/ will require purchasing. On a successful response, which you'll only get if you've paid, the unpaid version of the comic is replaced with the data from the server.

The interesting part is the error response handling on line 11. A 402 response is an error because it is in the 400 range - indicating a client-side error. It's not a very serious error - just asking for something for which you have not paid! I am using jQuery so the first parameter passed to the error handler function is the jqXHR object. There are many things you can do with that object, but I only need to check the response status to see if it is a 402, and if so I read the values of the X-Payment-Address-Bitcoin and X-Payment-Amount-Bitcoin headers. Yes, I am ignoring my own X-Payment-Types-Accepted header because I happen to know that it only contains Bitcoin (that's the only kind of coin I have right now). If I expected other types, the right thing to do would be to read the X-Payment-Types-Accepted header and loop over the list of values to get the names of the other headers.

I take the address and price and submit them to my QR Code image generation URL to make an easy, scannable way to pay - but also just display the address and price to the user. Again, I'm doing this on the client side because I'm requesting the data with Ajax. If the user navigated directly to a URL which required payment, the server could have responded with that HTML as part of the 402 page, much in the same way servers respond with custom (and sometimes helpful) 404 pages today.

The last part is to check the /paidContent/paywall-comic URL again to see if a payment has gone through. Since it can be a few seconds before Coinbase tells my server that payment has been sent to the address, I took the quick and dirty route of setting a timeout and running the whole checkForPayment() function again. I could have connected a Websocket to wait for payment confirmation from the server if I wanted to get fancy - a website that was expected to get more traffic than my comic would probably want to go that route instead. A possible future enhancement would be to include an additional header with a recommendation for the client - specifying if it should refresh, wait, redirect so some other URL, etc.

In the future, I might put more special features on Amphibian.com on the /paidContent/ URL, instead of it just being part of the joke. Maybe special comics that you can only access by paying a few cents? I'm not sure yet, but I tried to develop a framework that robust enough to support future expansion. At this point, I can easily make the server request payment for anything thanks to the Express middleware that I wrote combined with the Coinbase Node module. But that's too much for a single blog post so I'll talk about that on Friday!

I also hope that more web sites adopt and expand on this model now that I have it working in a semi-legitimate application. In my opinion, it beats displaying ads for enabling content creators to monetize. You can help me monetize by paying to get the whole comic today!

Amphibian.com comic for 20 May 2015

No comments:

Post a Comment