Less is more: reducing thousands of PayPal buttons into a single iframe, using zoid
This article is part of a series on PayPal’s Cross-Domain Javascript Suite.
PayPal has a vast, vast number of different buttons out in the wild.
Some of these were created by us. Some of them were created by merchants hoping to customize their experience. Some of them should never have seen the light of day, and really make our eyes sore.
But that’s all about to change.
I’m not going to talk about branding, or about keeping a uniform image, or visual recognition, or anything like that. All of that is obvious stuff: even if you disagree, it does make sense for PayPal to have a consistent set of buttons across all merchant sites, so our users see it and recognize it and know immediately “I can pay here!”, without being transported back to the early 90s in the process.
So what are you going to talk about, if not branding?
Well, I’m an engineer, so let’s talk tech? Maybe some javascript? :)
We recently re-architected the PayPal Checkout integration, and along the way we open sourced the libraries that we used to build out the new checkout.js. The two I’m most proud of are post-robot and zoid, which together give the power to build truly cross-domain components. I’ve written about these pretty extensively elsewhere, so feel free to take a look.
What’s cool about these modules is, they let us build the PayPal button entirely into an iframe. This has a number of immediate advantages, but also some pretty interesting technical hurdles.
Pixel perfection
It’s now a totally html, css and svg powered button. This means that no matter what your device is, the button is going to be extremely crisp. Every time I look at an old PayPal button on my retina monitor, I cringe, because usually it’s just a PNG or compressed JPEG, which sometimes scales up horribly.
Sandboxing
The button is now totally embedded and sandboxed inside an iframe, which means there’s no chance it’s going to be affected by stylesheets in the parent page. In a previous iteration, we attempted to build a button.js which rendered directly onto the merchant’s page, and the number of css conflicts was unimaginable. It’s surprising how many sites directly style div
or button
without worrying about parts of the page they didn’t directly render.
Consistency
We can now enumerate every different style of button we want to expose to merchants, and keep control over those styles, while allowing enough options that different sites can pick a style which fits in best with their own design. This lets us focus on having a few nicely designed buttons, rather than a thousand mediocre and terrible ones, and we can improve them in the future too without having people need to upgrade.
The future
Finally, since we’re in an iframe, the button is less of a button now, and more of a mini-web-app inside a frame. The possibilities for providing better user experiences are pretty endless, and a few ideas which spring to mind immediately are:
- Rendering buttons in the native language of each customer.
- Recognizing existing PayPal customers and customizing the experience for them.
- Rendering multiple buttons inside the space we have, each with a shortcut to a different experience.
- Showing your PayPal balance, before you even decide to pay.
- Running AB tests to optimize conversion for different experiences, in the button.
All of this is good for PayPal, and good for merchants too, if it drives extra sales on their online stores.
So how does it work?
I promised I’d get a little technical, so here goes. The button is now built using zoid, making it a fully cross-domain component. If you’re familiar with React, it’s a very similar principle: I render the button, and pass down some data and callbacks, in a very “data-down, actions-up” way:
paypal.Button.render({ env: 'sandbox', style: { color: 'blue' }, payment() {
// set up the payment, on click
}, onAuthorize() {
// handle the success case
}}, '#container');
Now we have a button!
The difference between zoid and React is, instead of rendering on the same page like React, zoid renders into an iframe, and passes its props down into the iframe over the cross-domain boundary. The web-app inside the iframe can then be implemented in whatever framework the author chooses — this is where React might come in, for example.
All the iframe needs to do is call the correct callback when it’s ready. In this case, for example, the button iframe will call window.xprops.onAuthorize()
when the payment is complete.
But wait, how does the button render the Checkout app up to the parent page when you click?
This is one serious limitation of iframes. They can’t render anything outside of their own boundaries. And a button is generally a pretty small element, so it doesn’t leave a lot of room to display an entire log-in and check-out experience.
This is where zoid comes in again. Using this library, we built a Checkout
component, and it lets us render remotely to whichever window I want! zoid orchestrates the entire thing, and I can forget that I’m even rendering to a window that I don’t have any direct control over.
So what the button actually ends up doing is calling Checkout.renderTo
, and passing the target window along with any props.
paypal.Checkout.renderTo(window.parent, {
payment: window.xprops.payment,
onAuthorize: window.xprops.onAuthorize,
...
}
Now we have the Checkout app in an iframe too, but on the parent page!
In this case, the button is just passing on the props that it was passed from the parent window, like onAuthorize
, since it doesn’t need to directly call them itself. When the checkout app is done, it will call onAuthorize
, which will message back to the button, and then back up to the parent window. Then the merchant can get a call to their callback, and continue on to finalize the payment.
You keep talking about passing functions between different windows… that’s possible now?
Yeah, I forgot to mention — post-robot can do that too! This makes building a component a whole lot easier: instead of manually setting up post-message listeners for each callback I want to create, I just pass across the callbacks I want to expose, and post-robot handles the messy parts to make it work! I’ll save the nitty-gritty explanation for another article, if there’s any interest.
Suffice to say, this really opens up the floodgates for some really interesting things to be done, having different windows communicating by passing callbacks and promises back and forth. We can really start to think of an iframe as a first-class interactive citizen on our page, rather than just a page we load in a sandbox then forget about.
So are there any downsides to this?
Well, obviously rendering an iframe is a little slower than rendering natively on the same page. So building these kinds of cross-domain components is only really recommended if you need to sandbox functionality, display secure information, or share a web-flow with other sites that you don’t own. That makes PayPal’s use-cases a perfect candidate, especially since it’s no slower than having a merchant do a redirect to paypal.com
for the customer to check out.
zoid also gives you a mechanism to pre-render some html and css into the iframe before it loads the full component. Right now we only really use this for rendering loading-spinners, but there’s bound to be some more creative uses for this pattern.
Try it!
If you’re interested in trying the new PayPal button, give it a go!
Even if you’re totally anti-PayPal, feel free to try some of the modules we’ve published. They’re 100% open-source, with no strings attached, and they’re a fantastic way to build shareable, distributable components, and still use your favorite front-end framework for the implementation.
Thanks!