Okay!  I've done a bit of successful work, and now need some serious
input from the community to tidy it up.

I have created a branch of GetPaid that successfully mocks up one
possible design for eliminating ZCML overrides for the Google Checkout
payment processor.  Anyone should be able to try it out:

$ svn co 
https://getpaid.googlecode.com/svn/getpaid.buildout/branches/brandon-no-overrides
$ cd brandon-no-overrides
$ python2.4 bootstrap.py
$ bin/buildout -c 316.cfg
$ bin/instance fg

Once the instance is up and running, use the ZMI to create a Plone site
that includes "GetPaid", and visit the GetPaid Setup's "Payment Options"
form.  If you have named the Plone site "/Plone", then the options will
be at this URL:

http://localhost:8080/Plone/@@manage-getpaid-payment-options

If you will create a buyable piece of content (I always just throw in a
Page after marking Pages as purchasable) and add it to your cart, so
that the Shopping Cart portlet appears, you will see a fake "Checkout"
button that changes depending on which payment processor you have
selected.  The same button will appear on the main shopping cart view!

In both cases, the fake button will be labeled either "[ Null Payment
Processor Button ]" or "[Google Checkout Button]" depending on whether
the "Testing" or "Google Checkout" payment processor is currently
selected.  And, it all works without requiring a single ZCML override!
(If you check, you'll find that Google Checkout's "override.zcml" files
are all gone from this branch of GetPaid.)

So, getting rid of overrides turns out to be very simple.

It's the design questions that this raises that are going to require the
project to stretch across another few days. :-)

Let's tackle these big questions one at a time.


1. How should we "look up" the active Payment Processor?
---------------------------------------------------------

I was rather surprised when I saw the code that determines which payment
processor is the currently selected or active processor for the site.
One instance of the code lives in ``PloneGetPaid/browser/checkout.py``::

        siteroot = getToolByName(self.context, "portal_url").getPortalObject()
        manage_options = IGetPaidManagementOptions(siteroot)
        processor_name = manage_options.payment_processor

        if not processor_name:
            raise RuntimeError( "No Payment Processor Specified" )

        processor = component.getAdapter( siteroot,
                                          interfaces.IPaymentProcessor,
                                          processor_name )

There are three steps here.  First, something called a "tool" is used to
select the "siteroot" by using an ASCII name string.  This is a bit odd
to someone like me from the Grok world, where we find the current site
root by calling "get_application(current_context)", but if it's the
normal way of doing things in Plone then I'm cool with it.

Second, the GetPaid "Manage Options" are pulled out of the site: these
are all of the options controlled by the "Site Setup -> GetPaid" series
of forms.  I find it a bit odd that they're not available as a utility,
so that you could just ask for "IGetPaidManagementOptions" and have the
site lookup occur automatically as part of utility resolution.  But,
again, if this is the Normal Plone Way then it's fine.

Finally, we pull out the value of the "payment_processor" option and
then manually look up the IPaymentProcessor registered as an adapter of
the siteroot under that name.  This is where things are just weird to me. :-)

Isn't the whole point of the site root, and of local utilities in Zope
in general, that you can ask for a resource that - without your having
to know - is customized within the current site, and you just magically
get the right answer without knowing that?  This stunt, of adapting the
site root to an interface of a particular name that the user has chosen,
just seems like a long-way-'round procedure to simulate what happens
more naturally if you just create an ISelectedPaymentProcessor interface
and have the payment processor selection form register the chosen
processor as a site-wide utility uniquely providing that interface.

Using a utility - if, indeed, they exist under Plone the way we're using
Plone here; maybe utilities are a Zope 3 feature that's not fully
supported under Plone yet, and that's the reason for these idioms? -
would also make the ZCML registrations of each payment processor less
odd.  Currently, the Google Checkout processor, to take one example,
says things like this in its ZCML::

   <adapter
      for="Products.CMFCore.interfaces.ISiteRoot"
      provides="getpaid.core.interfaces.IPaymentProcessor"
      name="Google Checkout"
      factory=".googlecheckout.GoogleCheckoutProcessor" />

In what sense, we have to ask when reading this declaration, is a
payment processor an adapter of an ISiteRoot?  Normally an adapter takes
a feature-rich object, and gives it new behaviors.  But does a site root
really provide behaviors which an IPaymentProcessor is improving upon so
that they are able to process payments?  It's a very odd way of looking
at the problem.  Declaring an IPaymentProcessor to be an adapter seems,
again, to be a way to do manually what utilities do automatically; it
seems to be a way to open a window to the site so that things like its
URL can be grabbed.  But surely a payment processor isn't really a more
specific adaptation of a site root, just because it needs the site's
URL?

Anyway, all of this is quibbling.  The whole edifice seems to work the
way it's currently written.  But I wanted to ask, because there's
obviously some mismatch here between the way PloneGetPaid is written,
and the techniques that I myself am familiar with from Zope 3 land, and
I'd like to understand the differences better so that, as I write more
GetPaid code in the coming days, it matches the philosophy of the
existing code as closely as possible.

Now, one to more meaty matters!


2. How should the "Checkout" button view be rendered?
-----------------------------------------------------

If you tried out my branch before reading this far, you probably find it
very lame that my "Checkout" buttons right now are just bold text with
square brackets around them.  I should explain why, so that you can help
me towards a solution.

PloneGetPaid provides two views that need a "checkout" button:

 1. Products/PloneGetPaid/browser/templates/portlet-cart.pt

    This is the cart portlet, that appears on the right side of most
    pages of the site if you have at least one item in your cart.

 2. Products/PloneGetPaid/browser/templates/cart-actions.pt

    This is the main shopping cart view, that comes up if you click one
    of the "Manage cart" links that PloneGetPaid makes available.

The current production version of PloneGetPaid has both of these buttons
hard-wired in these two views, because, as originally written, they
couldn't possibly go anywhere than to PloneGetPaid's own checkout
wizard.  The problem is that Google Checkout needs both of them to send
you to Google instead!  That's why the current production version of
Google Checkout has those nasty ZCML overrides: because it needs to
fully replace *both* of those views with new copies that provide
specialized "Checkout" buttons.  Yes, that's right: it replaces two
entire views just to replace a single button in each of them!

My new version of PloneGetPaid replaces the hard-coded "Checkout" button
in each view with code that tries to pull in dynamically-generated
button HTML instead, through an expression that currently looks like
this::

            <a tal:replace="structure renderer/checkout_link">Checkout</a>

This required me to place a tiny method named `checkout_link()` in each
renderer, both in ``Products/PloneGetPaid/browser/portlets/cart.py`` and
in ``Products/PloneGetPaid/browser/cart.py``, that needs to go and
figure out how the currently-active payment processor would like its
checkout button rendered.

A quick note: why do I allow the whole button to be controlled by the
payment processor like this, instead of just the link?  The reason is
that the Google Checkout button needs to be constructed quite
differently than the normal PloneGetPaid button.  The way the views are
currently coded, normal buttons in these views are *always*
type="submit" HTML form inputs whose value="" is their action name; the
normal Plone form machinery wakes up when they are submitted and calls
the right `[email protected](...)`` method on the view.

But a Google Checkout submit button is completely different!  It points
at a completely different URL - one that lives over on Google's site -
and needs to submit several form parameters when it gets there.  Because
of this, it can't actually be part of the same ``<form>`` as all of the
other buttons in the cart portlet or cart management page!  It can't
follow any of the normal rules for a portlet button.  Instead, the new
templates first have to say ``</form>`` to end the "normal" PloneGetPaid
form whose buttons are used for editing the shopping cart, then start a
whole new form that just wraps the one button intended for Google.
There are also cosmetic changes: the Google checkout button uses a
little Google logo inside.

For all these reasons, it doesn't seem reasonable for payment processors
to provide some phalanx of options that the cart views interpret in
order to build the right kind of button for things like Google checkout.
Instead, it seems more reasonable for each payment processor to be given
wholesale control over rendering the full HTML for its own "Checkout"
button.  That makes everything becomes a bit simpler.

So, back to my hard-coded way of asking for a checkout button::

            <a tal:replace="structure renderer/checkout_link">Checkout</a>

This is a mess for at least two reasons.

First, it's a mess because I have to add two ``checkout_link()``
methods, one to the cart portlet and one to the main shopping cart view.
And the methods pretty much repeat the same code: they do the dance that
I quoted up in section 1 for finding the currently selected payment
processor.  This is code which I'll have to factor out later into a
single routine if we decided to keep it.

Second, it's a mess because instead of properly asking for, say, a view
to be rendered, the ``checkout_link()`` methods just ask the payment
processor's method named ``checkout_link()`` (whoops, looks like I was
inconsistent in my naming when writing up this quick prototype; I'll fix
it later to make it match the name of ``checkout_button()`` if we keep
any of this code) to get back the HTML that it should insert where the
button goes.  Right now, these are just the two static strings::

    u'<b>[Google Checkout Button]</b>'
    u'<b>[ Null Payment Processor Button ]</b>'

Why, you'll ask, didn't I go all the way and make these actual templates
attached to views, so that I could go ahead and put the real buttons in?
(Since, after all, they *must* be template-driven views, since they have
variable portions where their submit URLs have to be specified?)  The
reason is that, by the time I found myself down in each
``checkout_link()`` function, I found myself knowing almost nothing
about my environment: neither the context I'm being rendered for, nor
the root of the site I live in, or anything.  Now, this might just be my
fault, for not passing enough variables around; it would certainly be
easy to fix.  But before beefing up the functions, I wanted to ask
whether there isn't some easier way that I could just use a plain view
instead, and eliminate both the new methods on the cart views and the
new methods on the payment processors.

(Oh, by the way: I've only added these new ``checkout_link()`` methods
on the "Testing" and "Google Checkout" payment processors.  If you try
any other processors yet under my new branch they'll break.  It's just a
proof-of-concept, after all.)

What I would really like to do, each of the two places I need a checkout
button rendered, is to just go like this::

  <a tal:replace="structure context/@@getpaid_checkout_button"/>Checkout</a>

The question is: how should the view multiplex so that it winds up
consulting the currently active payment processor to determine what the
button should look like?  There are at least two options, and maybe more
that you'll think of and share with me:

 1. Have a single ``@@getpaid_checkout_button`` view defined by
    PloneGetPaid.  When called, it does the current-payment-processor
    dance, gets from the payment processor the path to the template it
    should use (or, better yet, a class to use as the view), and then
    returns the rendered result.

 2. Have several ``@@getpaid_checkout_button`` views, one for each
    payment processor, and have the site owner's selection of payment
    processor automatically turn on its particular view.  I know that
    this sort of thing is possible in Zope 3, by having the submission
    of the "Payment Processor" form trigger the un-registering of the
    old processor's ``@@getpaid_checkout_button`` view and the new
    registering of the new processor's view.  But, given the look of the
    current-payment-processor dance quoted way up above (how long is
    this email so far, anyway?), it looks like maybe the GetPaid team
    doesn't like registering and un-registering lots of things - or even
    a few things - when a new change processor is selected.  I guess
    that's dangerous because registrations would always have to be
    persisted across site upgrades and everything?  Which would make it
    preferable, each time we need the current payment processor, to
    start with the site root and look it up afresh?  If so, then
    approach #1, above, is fine; I just wanted to ask in case a
    view-registration-driven approach hadn't yet been thought about and
    wound up having advantages.  Not that I can see any. :-)

So: my primitive methods-returning-strings mechanism needs to be
replaced with something fancier like a view.  I like option #1, I think,
but wanted to ask since Plone programming is still new territory for me.

All of the above are small issues compared to this last one.  Here goes!


3. Is Google Checkout a Payment Processor?
------------------------------------------

I would like to argue that Google Checkout is *not* a payment processor.

Yes, that's right.  I humbly submit that it's a conceptual mistake that
Google Checkout tries to shoehorn itself in as an IPaymentProcessor.  To
see this clearly, check out the definition of IPaymentProcessor itself::

  class IPaymentProcessor( Interface ):
      def authorize( order, payment_information ): ...
      def capture( order, amount ): ...
      def refund( order, amount ): ...

This fact was, helpfully, first pointed out to me by my friend Derek
Richardson.  He's right!  Look closely at those three operations, and
consider the fact that not a *single one of those functions* is
implemented by Google Checkout!  It doesn't do any of the functions of a
payment processor at all.  Well, then, what is it?

The answer is that Google Checkout is a *wizard*, not a *processor*.

Let me suggest that the structure of GetPaid (maybe I should draw a real
diagram for this next bit and post it on my blog?) looks like this:

1. The Shopping Cart accumulates objects you want to buy.

2. When your Shopping Cart is full, you want to visit a Checkout
   Wizard.  You are sent to the Checkout Wizard by the "Checkout" button
   at the bottom of any of the Shopping Cart views.

3. The Wizard asks you lots of questions, and gives you lots of chances
   to get the answers right as you traverse its forms and make mistakes
   and correct them.

4. Once the Wizard has the customer's shipping and credit card
   information, it needs to verify the credit card.  So it goes and
   looks for the current Payment Processor and uses its ``authorize()``
   method, and so forth.

The problem with the whole structure of GetPaid at the moment is that it
is *only* built to support its own, built-in checkout wizard.  That's
why the links described in #2, above, are hard-wired: GetPaid is not
built to let any custom actions intrude until its own checkout wizard
has completed and step #4 has sprang into action.

Google Checkout, in other words, does *not* replace the functionality
of, say, Authorize.Net; instead, it replaces the functionality of the
whole, internal checkout wizard!  Google Checkout is the peer, and the
alternative, to all of the forms that GetPaid presents the user with; it
is not a peer, in any functional sense, to normal payment processors
that just handle a simple ``authorize()`` call.

So, even though this first demonstration I've made of ZCML-override-less
Google Checkout magic does so by adding yet another method to the
GoogleCheckoutProcessor, I actually think that's the wrong place for it!
I think it would be a confusion for me to go into ICheckoutProcessor and
add a fourth method, ``get_checkout_button_view()`` or whatever, next to
``authorize()`` and it's cousins because it would be putting apples next
to oranges.  Think of how odd the resulting collection of change
processors would look!  Some would have a checkout-button method, but,
necessarily, then provide no ``authorize()`` method (since by the time
authorization takes place on an offsite checkout wizard, control has
long since left the hands of Plone); and others would have a
checkout-button method that just dumbly echoed the URL of the normal,
internal GetPaid checkout wizard, and that only differed by what they
did when ``authorize()`` was called.

We should therefore have two entirely separate classes of object.

   I.  Checkout wizards.

       A. Google Checkout.
       B. On-site GetPaid Checkout, which needs a Payment Processor.

   II. Payment Processors.

       A. Authorize.Net
       B. PayPal
       C. And so forth...

A Checkout Wizard offers, at the very least, to render a button, if
asked, that will send the user to its first page when clicked.

A Payment Processor takes a credit card number and processes it.

Simple! :-)

But there's one last question raised, which deserves its own little
section of this design argument.


4. Should the UI reveal that Checkout Wizards are different?
------------------------------------------------------------

Given the above argument about GetPaid's internal structure - which I
*think* is sound, but, obviously, which I'm not certain about because
I've felt the need to describe at very great length since this is my
first foray into a GetPaid redesign - how should we present the
difference between a Wizard and a Processor to the user?

There seem at least two options.

 1. Conflate them.

    This would let us keep the GetPaid "Payment Options" site setup page
    just like it is now, with a single pull-down for the Payment
    Processor.  That single pull-down would list both on-site payment
    processors and off-site wizards in a single list.  If the user
    selected something on-site, then the "Checkout" links in his Plone
    instance will point at the built-in wizard.  If instead he selects
    something like "Google Checkout", then his users will fly off-site
    when the select "Checkout" instead.  But only we programmers will
    know that the service which Google Checkout provides inside of our
    site is quite different than the service that, say, Authorize.Net
    offers; the user will see the choice as a simple one.

 2. Separate them.

    Here, there would be two boxes:

       Checkout Wizard:   ___On-site_Checkout___
       Payment Processor: ___Authorize.Net______

    As long as "On-site checkout" was the selected wizard, the second
    box would provide a range of options.  But if they changed the
    wizard to an off-site wizard, like Google Checkout, then the second
    box would need to either gray itself out, or restrict itself to
    something like "Offsite Google processing" as long as they have
    Google Checkout selected as their wizard.

How shall we choose between these two options?  Let me provide four
possible decision-making criteria.

 * Option #1 is simpler, and may therefore be preferable if it is
   possible.

 * Option #2 communicates more about the difference between on-site and
   off-site processing.  This might be a semantic UI advantage.  Also,
   if the on-site wizard grew lots of options in the future that
   governed its behavior, they too would need to be grayed out when an
   off-site processor was selected (hmm, but I guess that could happen
   just as easily under #1, so maybe never mind about that point).

 * Option #2 would allow for off-site wizards that actually offered
   several payment processor back-ends behind them.  I don't know if
   such things actually exist, but we'd automatically have conceptual
   room for them if they did with the two-box form design.

 * I have heard rumors that there is a multiple-payment-processor
   project in the works.  This would, presumably, at least allow the
   site owner to highlight *several* payment processors and let each
   user choose which one to use at checkout time.  Or, am I wrong?
   Maybe the effort is really not about the payment processors sitting
   behind the default on-site checkout wizard, but is going to be
   designed to let people, at check-out, select one among several
   wizards to go visit to finish their transaction?  I suppose the
   latter is more likely, since giving the user the option between
   Authorize.Net and one of their competitors is pretty useless - since
   the user would see the same on-site wizard in either case - compared
   to letting them use Google Checkout or Paypal, where they might
   already have an account, for their check-out instead.

   Anyway, I have a vague idea that option #2 will be more friendly to
   the multiple-payment-processor effort than option #1, because it
   opens the way for more meaningful multiple-choice selections if the
   day comes where, instead of a pull-down box, you can add several
   payment processors.  Think about it; it might, once the site manager
   is done working, look like:

     Selected Payment Processors:

     !  On-site Wizard

        ! Authorize.Net
        ! PayFlowPro

     !  Google Checkout

        ( Google Checkout payment processor )


Okay, I think that exhausts the ideas and confusions that I've got in my
head at the moment.  In summary, I think that refactoring to support
off-site payment processors is simple, but that we have to be
intelligent in choosing which of the several possible simple mechanisms
we choose, because one choice might make things easy for our users,
while another might box us in and make things difficult to configure
orthogonally going forward.

Let me know where I'm right, where I'm wrong, and weigh in on the
direction I should take with this branch.  Once we've chosen a
direction, I'll update all of the other payment processors to match,
write lots of tests, and let everyone try out the branch before I ask to
merge it.

Thanks for any feedback!

-- 
Brandon Craig Rhodes   [email protected]   http://rhodesmill.org/brandon

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"getpaid-dev" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/getpaid-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to