GetPaid needs customizable forms

I would like some advice from Zope and Plone folks about how to create forms that are not only easy for other developers to specialize, but which allow several specializations to be composed together. While I have used zope.formlib and z3c.form before for simple tasks, I have not yet been able to tell whether they support these more advanced kinds of operations.

Some background: I am doing some work on GetPaid for Plone with the generous funding of Derek Richardson who, if his dreams had not carried him away from grad school at the end of the Spring semester, would have tackled this same work as part of the 2009 Google Summer of Code.

The current mechanisms that GetPaid provides for customizing its checkout process are very primitive, and my task is to improve them. That is why I have been thinking about customizing forms.

Plugging in payment processors

When a Plone user has filled their shopping cart with goods and presses the “Checkout” button, GetPaid begins stepping them through a checkout process of several forms that must all be satisfied before, finally, the user's credit card will be charged and their purchase recorded. GetPaid's current definition of a “Payment Processor” requires only that it provide three functions authorize(), capture(), and refund() that talk on GetPaid's behalf to an online merchant account.

This design works fine for traditional payment processors that are comfortable letting Plone handle the credit card information, but these days site owners usually want to delegate the dangerous task of handling card numbers to an “off-site” payment site. And these off-site payment processors turn out to be but one example of the many ways in which GetPaid expansion modules want to customize (or hijack?) the GetPaid checkout wizard. Consider the following scenarios:

  • Google Checkout sample button
    Something like Google Checkout is comfortable taking the whole checkout process out of your hands; the “Checkout” button at the bottom of your shopping cart is replaced with a branded “Google Checkout” button that sends the user right to Google. Currently in Getpaid, that button can only be customized by providing completely new page templates for the shopping cart that duplicate everything already there, but which have a Google image on the checkout button.
  • Services like PxPay (at least the way we use them) let the GetPaid checkout process take care of collecting the customer's address and shipping information, but then need the user redirected off of the Plone site. Again, entire views get replaced just to provide an alternate submit button.
  • Many site owners want to allow several payment processors, with the user choosing which one gets used since the user might already have an account with Google Checkout or be previously registered with PayPal. This suggests the need for a “meta” payment processor which can be given a list of payment processors and, when the user reaches the first checkout step for which those payment processors differ, presents the user with a screen from which they select the processor that they like.
  • Some sites have specialized information that they need to collect from users: they want to ask for extra address information, or extra shipping options for certain products, or need more contact information from the user when they are checking out.

So, currently, in pretty much every one of the above situations, the answer to, “How do I tweak the GetPaid checkout process?” is, “Override the entire view or module that you need to customize, by providing your own copy that looks exactly the same as the default one, except for the one thing you need changed.”

If you are curious, I have created a diagram showing which payment processors have to override which parts of GetPaid.

Not only does this wanton overriding mean that modules have to meticulously track the changes that GetPaid makes to the forms they are overriding, but it means that customizations are inherently not composable: you can't put two of them together.

The problem of composability is a big one. Imagine that you live in Argentina and want to install a module that adjusts GetPaid's mailing address form to match the way postal addresses are formatted locally, but that you also want to use an off-site payment processor that needs to insert its custom “Checkout” button on the bottom of the mailing address form. Since the two modules will provide competing versions of the entire mailing address form, only one of them can “win”, and the customization that the other module wants to present will not be displayed.

Four crazy ideas

How might checkout process customizations become operations that could be combined and composed, instead of each one of them being a fragile, blanket replacement of an entire part of GetPaid? Here are four ideas that I have thought through today as I sat with my trusty pencil and yellow pad:

  • Plug-in points. Currently, GetPaid just has those three functions mentioned above (authorize(), capture(), and refund()) where payment processors can plug into its checkout logic. One way of solving the problems we are encountering is simply to provide a lot more plug-in points where GetPaid's default behavior can be adjusted or subverted. It looks like this would have to include separate views for every button, for the body of every major form, and for the transitions between forms. It is doable within the time I have left for this project, and serves as my default, conservative option if nothing more spectacular can happen right now. But it's very brittle: every time in the future that someone thinks of a new way of adjusting or tweaking GetPaid, they have to ask us to create a new plug-in point.
  • Request-level wrapper. It might be possible (I mention this technique only for the sake of completeness, to spur our imaginations) to implement an off-site change processor as a wrapper around GetPaid that interacted at the bare level of incoming HTTP requests and outgoing HTML pages or redirections. If you have chosen Google Checkout, a special wrapper would be installed that would rewrite the shopping cart “Checkout” button with its own. If you need another field added to the final checkout page, you would create a wrapper that adds the field when it sees the HTML for that form going out, and that intercepts the additional field on its way back in. Such wrappers would at least be composable: you could have several active at once, that each made their own modification to a single page so that the user could see an address form that was formatted for Argentina and that had the correct checkout button on the bottom.
  • Wrap a form that provided an API. What do forms “look like” to the code that calls them? If we imagine a form library where a Form is separate from the FormRenderer that introspects and traverses the properties of each form to turn it into HTML, then each GetPaid extension could be a wrapper around the GetPaid form object(s) that passes through most requests intact, leaving most parts of most forms alone, but that intercepts certain requests and returns different answers than GetPaid would return. The Argentina module would wrap the default GetPaid shipping form and, when traversed for its sub-forms and fields, would return a series of fields specific to Argentina. The off-site payment processor wrapper would just adjust the properties of the checkout button before returning them. And, these two wrappers would be composable! If you wrapped them, in any order, around the default GetPaid checkout forms, then you could enjoy both customizations.
  • Forms as modifiable objects. What if forms — or even a small collection of forms, like the GetPaid checkout process — were objects that you could adjust and edit and modify before asking them to be rendered? Then, each GetPaid customization could be written as a small routine that takes, as its argument, a description of the entire checkout process, and that modifies that description by tweaking, adding, and removing everything that it needs to. Before rendering a view for the user, GetPaid would construct a copy of the checkout process, and then pass it, in turn, to each module. The Argentina module would reach in and tweak some address fields. The off-site processor would reach in and adjust the checkout button sitting below them. And the result could then be rendered. The one problem, when we compare this approach to the previous scenario, would be how to intercept new data that would be coming in as a result of added form fields. I suppose the form descriptions would have to include callback functions for processing them, and that extension modules would just add their own callbacks.

Is there anything here worth trying?

These are just the ideas that have come to me today, as I've scribbled and sketched and sat down to re-read all of the design patterns in the Gang of Four Book looking for inspiration. (You might recognize the third option above as something like the “decorator” design pattern!) Does zope.formlib or z3c.form or anything else like that in the Zope universe provide the kind of services I would need from a form library to implement the ideas outlined above?

If you're a Zope or Plone person with an inkling of an answer, then please let me know!

Posted: Thursday, September 10th, 2009 at 12:25 am
Categories: Computing, Plone, Python, Zope

You can leave a response, or trackback from your own site.

  • Chris McDonough

    I have had good luck using formish (http://ish.io) so far on a project. The code is very straightforward and hackable, not a lot of magic, and it’s possible to dynamically construct both “schemas” and “forms” (the two are separate entities in formish) fairly easily. A form/schema pair could be modified arbitrarily by some defined pipeline as you describe in your last paragraph above, at least.

  • Martin Aspeli

    I didn’t read the entire post (sorry), but if you’re interested in extensible, composable forms, you may be interested in plone.autoform and plone.directives.form (grokkers for the former). Dexterity uses that to let people compose add/edit forms from a set of “behaviours”.

    http://plone.org/products/dexterity/documentation/manual/developer-manual

    See PyPI pages for tests as well.

    Martin

  • Robin Whitlock

    I am not a plone developer so I use iFrame tag to include my Forms payment solution. Well it is a forms server (WebNet) that allows for products, which can be sold. I would dearly love for a Plone solution hence my interest and sharing of my ideas. Basically I collect the form properties together (description, qty, price etc) from a page admin form (built by WebNet) and store them away (in mySQL). When a user clicks on a link to the page the form is rendered as display or input mode depending on the login rights (defaults are in place, so display for a payment form except qty). When a page is originally built the DB table is created (or altered) and a script built (Rexx) to handle it. So all the smarts are in place to collect and display the data on a page basis. These pages are then attached to a dynamic menu system in categories for ease of navigation. If one of the properties on the page is a product Id then a “Buy” button is rendered and linked to gateway processing page which has logic to process the basket and select the payment gateway(s) which are part of the user profile and each gateway supported will have the necessary logic to facilitate the gateway provider’s requirements.
    If this base logic could be of use to build into a Plone based framework then I could throw away my iFrame tag, and of course replace mySQL with the ZODB.
    WebNet has a home at http://www.webnet.net.au, hope this is of use to you and wish you luck for us all.

  • Jeremy McMillan

    I’ve been away from Plone programming for a bit, but #4 seems like the most elegant pattern, and a cursory scan of Martin’s Dexterity links seem to dovetail with the idea I had of conditionally allowing decorators on views. That’s my vote!

  • Mikko Ohtamaa

    Lots of adapters.

    I recommmend the following pattern.

    Adapters are created for plug-in points. They take parameters like layer (add-on product, site customization layer) and form type interface. Adapters resolve itself to IFormCreator object.

    IFormCreator object returns the form object, or the whole browserview object, which manages the form. Internally IFormCreator may subclass the existing page, take the default/existing form schema, duplicate it, mangle widgets and whathever we are currently doing with Archetypes schema.

  • Ezima Ikechukwu

    I have a very good way of solving the problem.
    I found out how to solve it. I want you to contact me so as to let you know how to solve it and also give you the necessary details. call me on +2348028442314.
    I will be expecting your call. Thanks

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>