This page describes a proposed implementation of reusable donation form logic.
Our frontend to accept donations is currently split across several systems, and causes two major problems:
- Configuration can only be done by tech staff, and is hidden in many places, in many formats including inlined with code and templates.
- UI is implemented in triplicate, in different programming languages, and of course there are many inconsistencies.
The solution outlined here is meant to:
- Consolidate configuration and provide a management UI.
- All donation forms will be supported by the same server-side code.
- Donation forms can be generated and displayed anywhere, using builtin code, or custom code which interfaces with the backend.
Design and implementation
Diagrams are generated from an ArgoUML model, gateway_caps.
A draft implementation is published in Gerrit.
In the normal donation flow, the donor gets an initial form which is customized for her country and language, and amounts are displayed in the national currency, but no other assumptions are made. A list of available payment methods is displayed.
There are alternative ways to enter the workflow, see below.
Select payment method
The donor chooses a payment method, at which point the page is refreshed with a method-specific form:
If the payment method requires further information from the donor, we display another form, and the donor submits to confirm payment.
- (Non-existent image link? Was: [[File:SubmitDonation.svg|Submit donation]] )
Some methods might redirect to the gateway or display an iframed, remotely-hosted form, instead of requesting payment details on our webpage.
When our servers contact the gateway,
Payment lifecycle is outside the scope of this document. A GatewayPayment is created at this point, and is never destroyed. We usually refer to this object using the gateway transaction ID.
Alternative entry points
The donor might enter the pipeline with special needs, or may develop special needs during the course of making a donation. Normal donation workflows can be converted to alternates if, for example, the donor decides to change currency at any time.
To pay in a currency other than one's national currency:
Generic donation interface, making no assumptions other than choosing a best-guess interface language. If default values are set, there is a "Change" link to modify them. We might want a security token to enable generic mode.
Prefab donation, when a donor comes through an URL generated by Special:DonationReferral. This URL will have information about the transaction pre-coded as parameters. If more details are required from the donor, or the "prompt" parameter is set, then a form is shown. Otherwise, the transaction will be pursued until its completion, and the donor redirected to a thank-you page, as in the following diagram:
These will be maintained as Twig html templates, for now.
Banner mixin form
When a "dropdown" banner includes the donation form, we will provide support via the mixin mechanism. The banner may specify alternatives which are to be tested, such as switching between the experimental and control set of ask amounts, translation, etc.
Some payment methods cannot be used as a banner mixin, for example methods which require iframed content must be loaded from a dedicated page. These methods should cause a redirect to a payments box Special:DonationForm.
- Disable donations from a country. Specify a message to be displayed.
- Enable a gateway.
- Enable a new payment method for a gateway.
- Split gateway transactions between multiple accounts, based on their settlement currency.
- Set ask amounts for a country. Set additional sets of ask amounts for that country, to do A/B testing.
- Set the list of countries or currencies served by a gateway. Set it differently for a single payment method.
- Disable recurring donations through a payment method in a country.
- Temporarily disable a currency through a gateway.
Changes will be logged.
There will be a Special:DonationReferral page to generate URLs to a custom donation form. All values are optional.
- billing country
- payment method
- gateway - in the rare case we have a reason to force a transaction using a particular gateway
- prompt - whether to display a confirmation page
In some cases, the referral URL will include a security token, so that we are not facilitating 'sploitation.
Class design and implementations are a work in progress,
DonationForm can be used as a donor interface, or as a web service supporting custom forms.
Donations are backed by a data store with generic access functions getVal and setVal. There are transparent accessors on Donation for fields which control the workflow, like getLanguage and getRecurring, but most of the data fields are opaque, and will be set from the session and from the form by passing an associative array.
A list of required fields is built using the gateway spec and configuration.
Each gateway will define a PaymentMethodSpec for each method it implements. Each spec has a callback to check for supported (country, currency, recurring) environments, and a begin payment thing which determines whether to make an API call to the gateway, display a payment details form through DonationForm, and/or passthrough to completion.
Text is translated using MediaWiki, and eventually using MilkShake's jQuery.i18n.
Customization and experiments
It should be possible to render alternative text and html for everything user-facing, according to multivariate bucketing. There is no provision for this in the design, yet.
Gateway interfacing will be done through DonationInterface, eventually to be replaced by SmashPig.
Payment processing in DonationInterface needs to be decoupled from form handling, by refactoring controller logic and validation out of GW_gateway.body.php, and into the GW adapter class.
My plan is to introduce an "initiate()" method in the adapter, which validates unstaged data and attempts to contact the gateway if necessary.
Initiating a gateway payment can result in five outcomes, which should be represented in the response object:
- Redirect to the gateway
- Render gateway content in an iframe
- Render a gateway and method-specific form. Return an form identifier (form name).
- Thank-you page
- Failure page
Any validation errors should be attached to the relevant input fields.
- Copy all working configuration rules into the Configuration classes.
- Backend support for DonationForm.
- Write a reference HTML/JS donation form which interfaces with the backend.
- Donation form banner mixin. Introduce live as a "dropdown" banner, and QA.
- Implement view-only management UI.
- Deprecate "dropdown" banner forms.
- Introduce on payments boxes, in parallel to DonationInterface forms. QA.
- Move configuration rules into a database.
- Begin implementing management UI features which set rules.
- Deprecate donatewiki and DonationInterface forms.
Constructing the forms is the tricky part. There are some major unresolved issues:
- We need a mechanism to display extra fields based on the country and payment method. This problem can't be reduced to simply showing country-specific and method-specific partial forms, because often the extra fields will be common, like "Postal code". We don't want to display the field multiple times. The mechanism to display can be simple, but the specification seems tricky. Also consider that we might want to expose these extra fields in the Special:DonationConfiguration UI so that admins can so stuff e.g. make Fiscal Number required for all non-card BR transactions.
- Method-specific forms are not gonna be easy to generalize.
- How easy will it be to implement alternative form HTML for experiments?
- How easy to customize default HTML? Will CSS provide enough control?
- It seems like displaying a subset of possible methods is common. Provide a mechanism to limit which methods are provided--provide a way to uniquely identify a usage of donationform such as a specific banner/bucket.
- The workflow should be flexible, decoupled from the server implementation.
- I18n is good in MediaWiki, we should use Matt's Twig-mwmsg plugin. I've rolled in some jQuery.i18n (Project Milkshake) support, but I'm not sure we need it.