|
View:
New views
5 Messages
—
Rating Filter:
Alert me
|
|
|
paypal payment processorHi,
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
|
|
|
Re: paypal payment processorSilvio <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 processorOn Sun, Nov 1, 2009 at 2:23 PM, Brandon Craig Rhodes <brandon@...> wrote: First: 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 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 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.
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 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 I'd like to hear more opinions about this too. What do you all think on this ML?
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.
-1000: programmatically splitting name and surname can lead to weird results. I would never ever do that.
I'm actually proposing to change that, and ask for name/address before sending them to paypal.
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
|
|
|
Re: paypal payment processorTwo 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
|
|
|
Re: paypal payment processor2009/10/31 Silvio <silviot@...>
Regards,Hi, +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): and register it,def __call__( self, event ): # NMI processor is async, so just return return <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... 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
|
| Free embeddable forum powered by Nabble | Forum Help |