On Wed, Aug 19, 2009 at 7:54 AM, Brandon Craig Rhodes<[email protected]> wrote: > > 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.
I think that GetPaid started life in Plone 2.5 which has an older version Zope 2 and consequently older versions of Five and Zope 3 libraries. (Zope 3.2?) That would have influenced how things are done. I dunno what the status of Plone 2.5 support is today for GetPaid. Is it still a supported target? > 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! Yup! > 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. Mikko has also implemented something along the same lines in the multiple payment processor work. While work focuses on a different use case there is significant overlap. I think it's worth looking where other external/async processors fit themselves into GetPaid. googlecheckout fits itself quite early to the shopping experience. The google checkout site itself is responsible for capturing details about the shopper and the shipping address. There are other processors such as pxpay that fit themselves in quite late in the shopping experience. Typically these processors only handles the step in the wizzard where the credit card details are captured. Yet they still require the use of overrides to preform that. (A little bit more on that below.) > 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. Agreed! I think there is only one place where it does really look like an IPaymentProcessor. That's in the getpaid configuration pages. Where the shop manager selects which processor to use and configures that processor. (Which you tackle in 4 below.) > 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... I've lost track of what people mean by PayPal in this context. [Opps later you use PayFlowPro which is more explicit. Wow this is a long email.] As there are a range of valid products that PayPal offer that make sense in the context of GetPaid. PayPal offer products for implementing both sync and async processors (to use the old getpaid terminology). getpaid.payflowpro fits into II. But getpaid.paypal is different. I think the google checkout processor is an anomaly in the whole getpaid eco system. It effectively replaces the whole checkout wizard. There is a class of external (asynchronous) payment processors that require overrides. Yet don't take over the whole wizzard. Just the last step of the wizzard to collect the credit card details. Including: A. getpaid.ogone B. getpaid.pypay C. getpaid.paypal ? D. getpaid.luottokunta ? E. getpaid.verkkomaksut ? F. ... > 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? I agree that this part of the UI is very important. I agree that the argument about googlecheck being the freak of the current crop of payment processors. But I think you should consider how to include the "regular" external payment processors into the mix without having to use overrides. I doubt that it's technically difficult. But we should have an explicit plan for that. > 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? Correct. I have only read and commented the specs for the multiple payment processor work. I agree that there is strong overlap in concerns at this point too. There is already an early release of the multiple-payment-processor work. http://pypi.python.org/pypi/getpaid.paymentprocessors > 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, Yup. > 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! Thank you for your work on this! My suggestion is that your next steps are to have a look in detail at one of the "regular" external (asynchronous) payment processors. And review the work on the multiple-payment-processor. -- Michael Dunstan --~--~---------~--~----~------------~-------~--~----~ 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 -~----------~----~----~----~------~----~------~--~---
