paypal payment processor

View: New views
5 Messages — Rating Filter:   Alert me  

paypal payment processor

by silviot :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi,

while writing tests for the getpaid.paypal module this came to my mind:
the approach currently is that we don't create an order if it's not paid, but we instead create it after the paypal process is over and an IPN comes.
Customer data is also collected on the paypal site.
Some customers are propably going to place an order and for some reasons (computer crash or phone call etc) wait some time before paying it, or lose their browser session.
I really think we should create the order object and destroy the cart before the customer is sent to paypal, collecting data on the getpaid-enabled site as well.
We could then provide the "pay with paypal" button on the order summary page. This would allow orders to be paid after they have been taken.
It would also simplify the solution of a bug with the current implementation; the function order_id of the view getpaid.paypal.views.PayPalCheckoutButton is:
 30     def order_id(self):
 31         return 'PUT ORDER ID HERE'
and it would be handy to have the actual order id available when we create the button.
BTW, we should also fix the personal details form and separate "first name" from "surname": we can't programmatically split "Full name" when we pass it to paypal.
One more issue: anonymous users can't see their own orders, so they wouldn't be able to pay with the described setup.
We could solve this providing a token in the emails we send after the order is created; the token could be the md5 hash of the date and time of the order.
Would this be secure enough? It would prevent a random person from collecting personal data of users, would it?
I currently use this approach in production and, at least for me, it has worked well; but I'm not sure it's secure enough. Anyway, users like *a lot* not to be forced to remember username/password pairs. This would also allow them to email the link to the order details page to a friend, and he could see only that particular order. This could be useful for gifts if the recipient wants to check the shipping status.
What do you think?

         Silvio

--~--~---------~--~----~------------~-------~--~----~
GetPaid for Plone: http://www.plonegetpaid.com (overview info) | http://code.google.com/p/getpaid (code and issue tracker)
You received this message because you are subscribed to the Google Groups "getpaid-dev" group.
To post to this group, send email to getpaid-dev@...
To unsubscribe from this group, send email to getpaid-dev+unsubscribe@...

For more options, visit this group at
http://groups.google.com/group/getpaid-dev?hl=en?hl=en
-~----------~----~----~----~------~----~------~--~---


Re: paypal payment processor

by Brandon Craig Rhodes :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message


Silvio <silviot@...> writes:

> I really think we should create the order object and destroy the cart
> before the customer is sent to paypal, collecting data on the
> getpaid-enabled site as well.

First:

I am "-1" on destroying the cart.  If the HTTP forwarding to PayPal
fails - if, say, there is a DNS glitch or their Internet connection goes
down for a moment - then the user will have to return to the store to
press "Checkout" again, but all the hard work to fill their cart will be
gone.  They'll have to go all over the site again, refilling their cart,
and then maybe find that their link to PayPal is down *again* when they
finish and hit "Checkout" the second time - and now, again, their cart
is destroyed and gone.  This is not an acceptable customer experience.

In crafting the customer experience, in fact, I'm going to consistently
argue that we should be acting as much like Amazon as possible, because
I think that their approach works extremely well.  It looks like neither
GetPaid nor Satchmo has spent enough time looking at corner cases that
Amazon handles well and trying to emulate them.  For example: it is
actually irresponsible to empty the cart upon payment, because the user,
in another browser tab, might have already added new items to their
shopping cart that are not line items in the order they're checking out
with.  Amazon handles this entirely correctly: if I put A and B in my
shopping cart, then press "Checkout", then while waiting for one of the
checkout screens to process I open another tab and put C in my cart - so
that my cart now has A, B, and C, in it - then when the payment goes
through only A and B disappear from my cart and C is still there,
waiting for me to start a new purchase.

So, again, we should not destroy the cart until we know the customer has
succeeded in paying; and, second, when we do get payment notification we
shouldn't "destroy" the cart, we should just remove from it whichever
items were actually paid for, using the little list of items that PayPal
will send us back in their IPN message.

Second:

I have a less strong opinion on creating an Order before they actually
leave the site.  The issues would seem to be:

Con

* Orders are designed to hold information, but in this case there's
  nothing to store except the fact that the user pressed "Checkout" and
  left the site.  You'd have all these orders sitting around with
  nothing but an order number and a copy of a cart inside of them,
  because the user reached PayPal and was annoyed with its interface and
  decided not to check out after all.

* It requires a JavaScript trick: the "PayPal" button actually has to
  redirect to an internal page that creates the order and then uses
  JavaScript to press the "Submit" button on the actual form that POSTs
  to PayPal.  If the JavaScript doesn't work with their browser, then
  the "PayPal checkout" button will just have taken them to a screen
  with another PayPal checkout button, and making users click twice to
  leave the site is something that lots of store owners might want to
  avoid (I don't know many store owners, I must admit, but rumor has it
  that they tend to like customers to be as few clicks away from
  finishing paying as possible!).

Pro

* Using the user IDs and timestamps on all of those empty Order objects,
  you could see how many visitors filled their cart then pressed
  "Checkout" without completing the process.

Please add any more Pro and Con here that you can think of!

But, it seems to me that this mechanism uses space, time, and additional
complexity, in order to accomplish something that the off-site service
does anyway.  I mean, the off-site service has several steps in its
checkout process, and if you want analytics on checkout process
abandonment you've got to go check out whatever stats PayPal keeps for
you, to see who left the process and quit at which stages in the
checkout.  And, since you have to use PayPal to get complete analytics
anyway, why invest in extra complexity on our end just to keep a
duplicate copy of one of the several numbers?

But, I'm not an actual Store Owner, so I'm quite open to changing my
opinion here based on actual GetPaid users if they (or you) would like
to share more about how they actually use the service in practice.

> We could then provide the "pay with paypal" button on the order
> summary page.

Ah!  I see!  You are not advocating JavaScript, you're advocating
two-step checkout process: the person sees their cart and presses
"Checkout", then is shown their cart *again* and has to press "PayPal
Checkout" to actually go off-site.  

> It would also simplify the solution of a bug with the current
> implementation; the function order_id of the view
> getpaid.paypal.views.PayPalCheckoutButton is:
>  30     def order_id(self):
>  31         return 'PUT ORDER ID HERE'
> and it would be handy to have the actual order id available when we
> create the button.

My proposal, which I entirely forgot to mention in the sprint docs, is
that every time we generate the "PayPal" form - which might be dozens of
times, at least, during each person's shopping experience - we generate
a fake "Order Id" that embeds the user's Plone ID somewhere secretly
inside of it.  That way, when we later get an IPN message for that Order
and see that it doesn't exist yet, we can create it and auto-associate
it with the right cart owner using their embedded Plone Id.

But before coding this I wanted to have everyone else think through
whether this would raise any security concerns (I can't see any myself)
and I wanted opinions on whether it should be explicit, where the user
themselves (if they check) could see that the order number is
"69391959632-brhodes", or whether we should encode it somehow as part of
the order number so that it looks like numbers or hex codes too.

> BTW, we should also fix the personal details form and separate "first
> name" from "surname": we can't programmatically split "Full name" when
> we pass it to paypal.

I'm certainly not an expert on this area, but two objections immediately
occur to me:

1. Isn't the personal details page something that's a Plone-wide
   standard and that we can't change?  Surely we don't want that form to
   change on the day that someone adds GetPaid to a site that's been
   maybe running for years, and have to go back through and add two
   names to all of their users before proceeding?

2. The first+last formula doesn't work everywhere in the world - nor
   even with everyone in a large university.  I think that's why Plone
   has a free-form field.

If PayPal needs first and last, I suspect that we will just have to do
our best using .split() and so forth with the normal Plone name field.
But, again, I could easily be wrong here!

> One more issue: anonymous users can't see their own orders, so they
> wouldn't be able to pay with the described setup.

Can they pay with my proposed setup?  They would just click "Check out
with PayPal" and be immediately whisked off-site?

> We could solve this providing a token in the emails we send after the
> order is created; the token could be the md5 hash of the date and time
> of the order. ... What do you think?

A strongly random UUID, not the date and time, is what is needed here
for each Order; but, aside from that, your idea is a very good one!
It's just like the Tracking links that lots of sites send out: because
no one else can guess a 30-character random string, the URL is
essentially private to whomever reads the email.  That would be a nice
way for people (at least who type their emails correctly) to have a way
back to their Order.  Plus, on the final Order page itself we could say
"to visit this later, bookmark..." and have the link there too.

Anyway, please feel free to provide ideas, thoughts, and counter-objects
to anything above!

--
Brandon Craig Rhodes   brandon@...   http://rhodesmill.org/brandon

--~--~---------~--~----~------------~-------~--~----~
GetPaid for Plone: http://www.plonegetpaid.com (overview info) | http://code.google.com/p/getpaid (code and issue tracker)
You received this message because you are subscribed to the Google Groups "getpaid-dev" group.
To post to this group, send email to getpaid-dev@...
To unsubscribe from this group, send email to getpaid-dev+unsubscribe@...

For more options, visit this group at
http://groups.google.com/group/getpaid-dev?hl=en?hl=en
-~----------~----~----~----~------~----~------~--~---


Re: paypal payment processor

by silviot :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Sun, Nov 1, 2009 at 2:23 PM, Brandon Craig Rhodes <brandon@...> wrote:
First:

I am "-1" on destroying the cart. 
I probably was not clear enough. This morning Lee Joramo drew a very useful diagram that shows what my proposal looks like.
I just uploaded it to Google wiki, together with the email where I expose my ideas. Check it out at 
So, again, we should not destroy the cart until we know the customer has
succeeded in paying; and, second, when we do get payment notification we
shouldn't "destroy" the cart, we should just remove from it whichever
items were actually paid for, using the little list of items that PayPal
will send us back in their IPN message.
I'm -1 about this because it doesn't fit my usecases (but I am aware I'm biased here) and because it seems harder to implement than necessary.

 
 
Second:

I have a less strong opinion on creating an Order before they actually
leave the site.  The issues would seem to be:

Con

* Orders are designed to hold information, but in this case there's
 nothing to store except the fact that the user pressed "Checkout" and
 left the site.  You'd have all these orders sitting around with
 nothing but an order number and a copy of a cart inside of them,
 because the user reached PayPal and was annoyed with its interface and
 decided not to check out after all.
I meant to ask for user info on our site before the payment, and have the paypal forms automatically filled with the data provided to us. 

 (I don't know many store owners, I must admit, but rumor has it
 that they tend to like customers to be as few clicks away from
 finishing paying as possible!).
This is why we should trat anonymous (no user/password) checkout as a first class citizen.
In my experience, at least for the Italian users I'm used to and the kind of business I developed for, asking for a username/password is a thing that I need to avoid as much as possible.

But, it seems to me that this mechanism uses space, time, and additional
complexity, in order to accomplish something that the off-site service
does anyway.
It does use space. I think store owners might be happy with the extra space requirements if it translates to a better user experience.

But, I'm not an actual Store Owner, so I'm quite open to changing my
opinion here based on actual GetPaid users if they (or you) would like
to share more about how they actually use the service in practice.
I'd like to hear more opinions about this too. What do you all think on this ML?
 
> We could then provide the "pay with paypal" button on the order
> summary page.

Ah!  I see!  You are not advocating JavaScript, you're advocating
two-step checkout process: the person sees their cart and presses
"Checkout", then is shown their cart *again* and has to press "PayPal
Checkout" to actually go off-site.
Yes, I'm really positive this is not an issue. I never had the impression any of my users (I use the described approach in production, and it works well) had anything to complain about.  

> BTW, we should also fix the personal details form and separate "first
> name" from "surname": we can't programmatically split "Full name" when
> we pass it to paypal.

I'm certainly not an expert on this area, but two objections immediately
occur to me:

1. Isn't the personal details page something that's a Plone-wide
  standard and that we can't change?  Surely we don't want that form to
  change on the day that someone adds GetPaid to a site that's been
  maybe running for years, and have to go back through and add two
  names to all of their users before proceeding?

2. The first+last formula doesn't work everywhere in the world - nor
  even with everyone in a large university.  I think that's why Plone
  has a free-form field.

If PayPal needs first and last, I suspect that we will just have to do
our best using .split() and so forth with the normal Plone name field.
But, again, I could easily be wrong here!
-1000: programmatically splitting name and surname can lead to weird results. I would never ever do that. 
 
> One more issue: anonymous users can't see their own orders, so they
> wouldn't be able to pay with the described setup.

Can they pay with my proposed setup?  They would just click "Check out
with PayPal" and be immediately whisked off-site?
I'm actually proposing to change that, and ask for name/address before sending them to paypal. 

> We could solve this providing a token in the emails we send after the
> order is created; the token could be the md5 hash of the date and time
> of the order. ... What do you think?

A strongly random UUID, not the date and time, is what is needed here
for each Order; but, aside from that, your idea is a very good one!
It's just like the Tracking links that lots of sites send out: because
no one else can guess a 30-character random string, the URL is
essentially private to whomever reads the email.  That would be a nice
way for people (at least who type their emails correctly) to have a way
back to their Order.  Plus, on the final Order page itself we could say
"to visit this later, bookmark..." and have the link there too.
Good, that should be some attribute on the order, randomly generated on order creation.
This also leaves room for some improvement: in case we need the order url to expire, we could store an expiration date with the token and let the user ask for a new url in case it's expired, providing their email address.

   Silvio
 

--~--~---------~--~----~------------~-------~--~----~
GetPaid for Plone: http://www.plonegetpaid.com (overview info) | http://code.google.com/p/getpaid (code and issue tracker)
You received this message because you are subscribed to the Google Groups "getpaid-dev" group.
To post to this group, send email to getpaid-dev@...
To unsubscribe from this group, send email to getpaid-dev+unsubscribe@...

For more options, visit this group at
http://groups.google.com/group/getpaid-dev?hl=en?hl=en
-~----------~----~----~----~------~----~------~--~---


Re: paypal payment processor

by silviot :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Two more things I forgot to mention:
The proposed workflow is intended to provide a sane user experience allowing the site manager to mix 
cash on delivery, onsite payments as well as offsite payments with a similar process.
I think they don't mix well in the current implementation.

         Silvio

--~--~---------~--~----~------------~-------~--~----~
GetPaid for Plone: http://www.plonegetpaid.com (overview info) | http://code.google.com/p/getpaid (code and issue tracker)
You received this message because you are subscribed to the Google Groups "getpaid-dev" group.
To post to this group, send email to getpaid-dev@...
To unsubscribe from this group, send email to getpaid-dev+unsubscribe@...

For more options, visit this group at
http://groups.google.com/group/getpaid-dev?hl=en?hl=en
-~----------~----~----~----~------~----~------~--~---


Re: paypal payment processor

by Juan Pablo Giménez-2 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

2009/10/31 Silvio <silviot@...>
Hi,

while writing tests for the getpaid.paypal module this came to my mind:
the approach currently is that we don't create an order if it's not paid, but we instead create it after the paypal process is over and an IPN comes.
Customer data is also collected on the paypal site.
Some customers are propably going to place an order and for some reasons (computer crash or phone call etc) wait some time before paying it, or lose their browser session.
I really think we should create the order object and destroy the cart before the customer is sent to paypal, collecting data on the getpaid-enabled site as well.

+1, a "review your order" page should be handy to create the order without the need of js, but adds an extra click...

BTW, while implementing getpaid.nmi I faced another orders workflow problem. There is a subcriber to the IWorkflowTransitionEvent than when the workflow triggers to charging state tries to found the processor and call capture to get the transaction result,

        processor = component.getAdapter( context,
                                          interfaces.IPaymentProcessor,
                                          self.order.processor_id )

        result = processor.capture( self.order, self.order.getTotalPrice() )
   
        if result == interfaces.keys.results_async:
            return
        elif result == interfaces.keys.results_success:
            self.order.finance_workflow.fireTransition('charge-charging')
        else:
            self.order.finance_workflow.fireTransition('decline-charging', comment=result)
 
But we're not implementing an IPaymentProcessor, so this fails and we can't move the order over the wf... a real pain because without it we only have orders in the "REVIEWING" state.

My first fix was a change on payment.DefaultFinanceProcessorIntegration than caused some problems to Brandon.

So after some testing I ended up with this solution,

First I created a new Orded class inheretied from getpaid one...
class INMIOrder(interfaces.IOrder):
    """
    """
   
class Order(BaseOrder):
    """
    """
    implements(INMIOrder)

Now the processor implements an orderid method,
    def orderid(self):
        cartutil=getUtility(IShoppingCartUtility)
        cart=cartutil.get(getSite())
        # we'll get the order_manager, create the new order, and store it.
        order_manager = getUtility(IOrderManager)
        new_order_id = order_manager.newOrderId()
        order = Order()
       
        # register the payment processor name to make the workflow handlers happy
        order.processor_id = 'getpaid.nmi.processor'

        # FIXME: registering an empty contact information list for now - need to populate this from user
        # if possible
        order.contact_information = payment.ContactInformation()
        order.billing_address = payment.BillingAddress()
        order.shipping_address = payment.ShippingAddress()

        order.order_id = new_order_id
       
        # make cart safe for persistence by using pickling
        order.shopping_cart = loads(dumps(cart))
        order.user_id = getSecurityManager().getUser().getId()

        order.finance_workflow.fireTransition('create')
        order.finance_workflow.fireTransition('authorize')
       
        order_manager.store(order)

        return order.order_id

To get this working I needed my own FinanceProcessorIntegrator,
class FinanceProcessorIntegration(payment.DefaultFinanceProcessorIntegration):

    def __call__( self, event ):
        # NMI processor is async, so just return
        return 
and register it,
  <adapter
     for=".nmi.INMIOrder
          getpaid.core.interfaces.IDefaultFinanceWorkflow"
     provides="getpaid.core.interfaces.IWorkflowPaymentProcessorIntegration"
     factory=".nmi.FinanceProcessorIntegration"
     />

And thats all... now I have a working orders workflow for my offsite processor...
IMHO this could be moved to getpaid.core.processors, so offsite processors don't need to worry about order creation, wf states and events, only aprove or decline orders based on external response...

Regards,
Juan Pablo

--~--~---------~--~----~------------~-------~--~----~
GetPaid for Plone: http://www.plonegetpaid.com (overview info) | http://code.google.com/p/getpaid (code and issue tracker)
You received this message because you are subscribed to the Google Groups "getpaid-dev" group.
To post to this group, send email to getpaid-dev@...
To unsubscribe from this group, send email to getpaid-dev+unsubscribe@...

For more options, visit this group at
http://groups.google.com/group/getpaid-dev?hl=en?hl=en
-~----------~----~----~----~------~----~------~--~---