Tarsnap now takes credit cards (without Paypal)

One of the most frequent complaints I've heard about Tarsnap over the past four years has been that the only way to pay for Tarsnap usage has been via Paypal. While Paypal works — most of the time — it occasionally rejects payments or flags them as "suspicious", and has a reputation for "freezing" accounts — a bad thing to have happen if you want to access your money in the next 6 months, and a very bad thing to have happen if you're running a company and don't have any other way to take payments. Thanks to Stripe, Tarsnap is no longer wholly dependent upon Paypal: You can now make payments by entering your credit card details directly into the Tarsnap website.

I've been excited about Stripe ever since one of the founders, Patrick Collison, emailed me in 2009 to say that he was "trying to build a credit-card processor that doesn't suck". My immediate reaction: "I think that might be the most ambitious plan I've ever heard from someone associated with YC". When Patrick went on to describe how it would work — abstracting away all the paperwork of credit card "merchant accounts", having clean RESTy APIs, avoiding the need for PCI compliance by never having credit card numbers touch my server, etc. — I knew it was going to be a service I would want to use. It took a while before this became possible — when Stripe launched last year, it was limited to the US, and Tarsnap is a Canadian company — but last week they emailed to invite me to beta test their soon-to-be-publicly-launched support for Canadian merchants.

One of the neat tricks used by Stripe is how they allow people to accept credit cards without handling any credit card details. When you fill out the credit card form on the Tarsnap website — or most other Stripe-using sites — and hit the submit button, Stripe's javascript leaps into action. Rather than having the credit card details submitted along with the rest of the form, the Stripe javascript sends them directly to the Stripe servers, which send back a single-use "token". This token then gets submitted with the rest of the form; and when it comes time to charge the credit card, instead of sending an API request to the Stripe servers saying "please charge $X to this credit card", an API request goes out saying "please charge $X to that card I haven't seen but was given this token in place of". Obviously if an attacker manages to compromise the web server which is serving up the credit card entry form, they can redirect the card details — that's unavoidable — but since no credit card numbers ever reach the web server there's no risk of them being improperly stored — a fact of critical importance to the credit card networks, since it ensures that an attacker could only get a few hours or days of cards (assuming the attack is discovered reasonably promptly) rather than being able to retrieve years of previously-used cards.

As useful as this trick is, it posed a problem for Tarsnap. Users make Tarsnap payments after logging in, so any javascript loaded into Tarsnap's payment page is executed with the credentials of a logged-in user. Now, I'm confident in the Stripe team's attention to security, but I don't call Tarsnap "online backups for the truly paranoid" for nothing: There's no way I'm going to trust someone else's javascript with that power. (For the same reason, I use Google Analytics on the Tarsnap website but only when pages are loaded via HTTP, not on pages loaded via HTTPS.)

To get around this problem, I took advantage of a feature of Javascript's "Same Origin" policy: If a page on domain X contains an <iframe> tag which loads content from domain Y, any javascript on the iframed page is executed with the privileges of domain Y — meaning that it can't do anything naughty to content from domain X. Hopefully at some point Stripe will provide such an iframe solution themselves; but until then, since I figured it might be useful to more people than just me, I decided to make this available as a free service to the world. Paymentiframe.com hosts a CGI script which generates a Stripe "payment iframe" given the necessary parameters (form submission URL and Stripe publishable API key, plus other optional parameters to change the submission button text or add hidden form fields) — now anyone who wants to accept credit cards via Stripe can drop a single <iframe> tag into their website and avoid any javascript "contamination".

With that done, the remaining Stripe integration was easy: When a form arrives at the Tarsnap web server with a Stripe token, the Tarsnap account management code goes through the usual steps to ensure that there is a logged-in user, then sends an HTTPS request to the Stripe API endpoint. It gets back a success or failure response — the body of which it doesn't even parse, since Stripe also signals success or failure via the HTTP status code — then performs Tarsnap's standard payment processing steps of adjusting the user's Tarsnap account balance and sending out an email.

I did cheat in one way however: If the Tarsnap server crashes after sending the Stripe API request but before recording the payment, there is no way for it to automatically recover; I would have to manually process the payment in that case. To do things entirely "correctly" I would have to use Stripe's webhooks — like Paypal's IPNs, these send notifications to a CGI script when an event has occurred, and (most importantly) keep resending the notifications until they are successfully processed. I spent a long time agonizing over this — my background in mathematics makes me a firm believer in doing things right and handling even the most obscure edge conditions — but ultimately I decided that this was a sufficiently unlikely case that I could afford to handle it by hand if it arose.

But there you have it: An iframe and an API call, and Tarsnap users no longer need to go anywhere near Paypal. A fact which I'm sure will make many Tarsnap users very happy, and one in particular happier than most: Stripe has been using Tarsnap since late 2010.

Posted at 2012-08-13 15:00 | Permanent link | Comments
blog comments powered by Disqus

Recent posts

Monthly Archives

Yearly Archives