Rotary International: Technical Bits
Amazee Labs partnered with Rotary International to overhaul their online giving platform. We’ve already talked about the What/Why in various case studies and went over the UX and design workflow. In this blog post, I’ll go over the technical aspects of how we used React, Drupal and RaiseNow to create a customizable widget for accepting donations that can be used across all of Rotary’s web properties.
There’s a huge variety of things you can build with React, ranging from a single element to a full-blown single page app (SPA) with server-side rendering (SSR). For Rotary, the purpose of the project was to improve the web-based donation experience, which really boils down to a single form. I classify our app as a “widget” in terms of functionality and complexity. It doesn’t require routing or SSR, but it does need to implement complex business rules and get deployed to numerous pages.
We do have our own build system using webpack, but aren’t doing anything special besides preconfiguring for our local dev environment and stack preferences. You can use create-react-app and get the same results. The only requirement is that the output of the build is one single “build.js” file which includes everything needed to render the form. More on this later.
After some experimentation, we landed on the functional style for components on this project. This means every component is a stateless function, and Higher Order Components handle all logic. We rely heavily on the recompose library to glue everything together. Here’s an example country selector:
The form is built with the idea that it could live on any page on any website. An essential aspect of that goal is to make the experience identical everywhere it’s shown. The biggest threat to that are styles from the parent site that might override the form styles. However, it’s also vital that we allow parent sites to customize the styling of the form to match colors and branding. To help sandbox the application we picked styled components for all our CSS. This gives us generated class names which are highly unlikely to conflict with any existing or new classes on parent sites and is great for architectural aspects of CSS like the layout. Styled components also let us add our own class names to elements, so they can be targeted from outside CSS and guaranteed to stay the same even if components get shuffled around or changed.
For state management, we use recompose/withReducer to implement a Flux architecture. We didn’t find it necessary to implement a full state library like Redux, but there are some advantages that might make it worth it in the future (middleware, advanced debugging, etc).
The last significant piece was allowing customization. A big part of that happens on the backend, discussed later on. Another part is being able to override settings on the specific widget “instance” for a page. The standard process for including widgets in a page (any widget, not just our form) usually comes down to “paste this script tag in your WYSIWYG editor.” We take the same approach. To facilitate customization, we expose two functions to create and destroy the React application. The initializer function accepts arguments which allow passing in different config options like turning on debug mode or overriding the API url. The destroy function cleanly removes all traces of the form from the running page.
Drupal serves three main roles for our form. First, it allows editors to create and customize separate instances of the form to be shown on different websites and pages, without developer intervention. Second, it facilitates data loading and communication between various internal Rotary APIs and React. Third, it allows members that have recurring donation setup to log in and manage their donations.
The first part is accomplished by creating a new, custom, “Donate App” entity using the Entity API module. Each entity is a separate, configurable, donation form widget that can be embedded in a page. We used the core Field API to take advantage of advanced form controls, and integrated with the i18n module to provide translations. We also used a custom module, very similar to panels, to facilitate modular content requirements.
The data facilitation is handled in two directions. Rotary has a lot of information stored in various systems and CRMs that dictate business rules like, which countries donations are accepted from, which currencies can be charged, what organizations receive the funds, and the internal credit exchange rate, to name a few. To ensure the donation form always has the correct information, Drupal queries a set of private, internal, APIs provided by Rotary, and then injects only the data needed directly into the “build.js” file before sending to the browser. The advantages are twofold: the final script sent to the browser has everything needed to render the form, and no further requests are needed. The internal APIs are not required to be made public, nor are new public/scrubbed versions required to be built. The final output is cached and gzipped to reduce download times. For the moment, this is done in a straightforward, “dumb” way, but the concept has worked and is being improved as I write this.
There are also parts of the form that require sending data to Drupal so that it can be saved out-of-band. In these cases, Drupal exposes a public API which acts as a proxy for a request to an internal API, or saves the data until it’s needed by other systems.
To reduce the workload on Rotary staff, a new self-service portal was created in Drupal which allows members to create, edit, and cancel their recurring donations. There was no online way to do this with the previous donation system. Now, instead of needing to call Rotary to update credit card information when it expires, users can just log in and do it themselves.
Accepting payments online is a hard problem, so Rotary partnered with RaiseNow to handle that process. To be fully PCI compliant, users never enter their payment information on Rotary servers. Instead, RaiseNow provides a payment widget to handle it. The widget itself is very configurable and is designed to be used standalone. Yes, that means the Rotary donation form is a Donate App widget which manages and embeds a second, RaiseNow widget. Many of the form features are too complex for the RaiseNow widget, so we disable those and build them in React. The RaiseNow widget API is flexible enough that React can configure all the necessary information to complete a donation including all the Rotary business data needs.