Thanks for the replies guys. I started off with pbreit's snippet: http://groups.google.com/group/web2py/browse_thread/thread/6cc84d9fb609e74/cdb0d848ed691e40?q=paypal+pbreit#cdb0d848ed691e40
First I ran into a problem in which paypal is saying I'm not authorized to make the api call. Some googling and found a suggestion to remove the 'subject' from the call, and it worked, at least on the part where the api credentials are being verified. Now when I test on sandbox it redirects fine. However, it displays the 'regular' login screen. When I try to login using a test personal account, it goes to the user's My Account panel. I know it should be showing a confirmation page for the purchase, amirite? And afterwards it has to go back to my return url for further processing on my end. Is that behavior limited to IPN? I know this approach does not make use of the IPN. Here's the modified/simplified version of pbreit's code that I use: ############################################ # models ############################################ paypal_config = { 'user': 'my_api_username', 'pwd': 'my_api_pwd', 'signature': 'my_api_sig', 'version': '66.0', 'button_source': 'https://www.paypal.com/en_US/i/btn/ btn_buynowCC_LG.gif' , 'return_url' : 'http://localhost:8000/controller/default/ paypal_return', 'cancel_url' : 'http://localhost:8000/controller/default/ paypal_cancel', 'notify_url' : 'http://localhost:8000/controller/default/ paypal_ipn', } db.define_table('purchase', Field('owner', 'reference auth_user', readable=False, writable=False, default=auth.user_id), Field('amount'), Field('item'), Field('invoice', unique=True), Field('status', readable=False, writable=False, default='pending'), Field('transaction_id'), Field('date', 'datetime', default=request.now)) ############################################ #module ############################################ import urllib import cgi def setec(config, purchase): values = {'USER': config['user'], 'PWD': config['pwd'], 'SIGNATURE': config['signature'], 'VERSION': config['version'], 'METHOD': 'SetExpressCheckout', 'PAYMENTREQUEST_0_AMT': purchase.amount, 'PAYMENTREQUEST_0_CURRENCYCODE': 'USD', 'RETURNURL': config['return_url'], 'CANCELURL': config['cancel_url'], 'PAYMENTREQUEST_0_PAYMENTACTION': 'Sale', 'PAYMENTREQUEST_0_INVNUM': purchase.invoice, 'PAYMENTREQUEST_0_NOTIFYURL': config['notify_url']} return cgi.parse_qs(urllib.urlopen('https://api-3t.paypal.com/ nvp', urllib.urlencode(values)).read()) def getec(config, token, recipient_id): values = {'USER': config['user'], 'PWD': config['pwd'], 'SIGNATURE': config['signature'], 'VERSION': config['version'], 'METHOD': 'GetExpressCheckoutDetails', 'TOKEN': token} return cgi.parse_qs(urllib.urlopen('https://api-3t.paypal.com/ nvp', urllib.urlencode(values)).read()) def doec(config, token, purchase, payerid): values = {'USER': config['user'], 'PWD': config['pwd'], 'SIGNATURE': config['signature'], 'VERSION': config['version'], 'METHOD': 'DoExpressCheckoutPayment', 'BUTTONSOURCE': config['button_source'], 'TOKEN': token, 'PAYMENTREQUEST_0_PAYMENTACTION': 'Sale', 'PAYMENTREQUEST_0_DESC': 'Order #%s' % (purchase.id), 'PAYMENTREQUEST_0_AMT': purchase.amount, 'PAYMENTREQUEST_0_CURRENCYCODE': 'USD', 'PAYERID': payerid} return cgi.parse_qs(urllib.urlopen('https://api-3t.paypal.com/ nvp', urllib.urlencode(values)).read()) ############################################ # controller ############################################ import random, string def paypal(): session.recipient_id = '' item = request.args(0) invoice = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(9)) + item.upper() purchase = db.purchase.insert( owner=auth.user.id, status = 'pending', item = item, amount = TEMPLATE_PRICE, invoice = invoice) paypal = local_import('paypal', reload=True) row = db(db.purchase.id==purchase).select().first() res = paypal.setec(paypal_config, row) if res['ACK'][0]=='Success': session.recipient_id = auth.user.email token = res['TOKEN'][0] print 'token:', token url = 'https://www.sandbox.paypal.com/cgi-bin/webscr%3Fcmd %3D_express-checkout%26useraction%3Dcommit%26token%3D%27' redirect('%s%s' % (url, token)) return locals() else: session.flash = 'Error: Purchase failure' return locals() def paypal_return(): token = request.vars.token recipient_id = session.recipient_id paypal = local_import('paypal', reload=True) payment = paypal.getec(paypal_config, token, recipient_id) if payment['ACK'][0]=='Success': invoice = payment['PAYMENTREQUEST_0_INVNUM'][0] purchase = db(db.purchase.invoice==invoice).select().first() if purchase.amount != payment['PAYMENTREQUEST_0_AMT'][0]: session.flash = 'Error: Template price was not paid in full' return locals() purchase.update_record( amount=payment['PAYMENTREQUEST_0_AMT'] [0]) payerid = request.vars.PayerID res = paypal.doec(paypal_config, token, purchase, payerid) if res['ACK'][0]=='Success': purchase.update_record(status='completed', date=request.now, payment_id=res['PAYMENTINFO_0_TRANSACTIONID'][0]) session.flash = 'Template purchase success' return locals() else: session.flash = 'Error: Payment not verified by Paypal' return locals() else: session.flash = 'Error: Unsuccessful purchase' return locals() return locals() def paypal_cancel(): return locals() def paypal_ipn(): return locals() ****************************** Any feedback is greatly appreciated. Thanks, Arbie On May 29, 8:12 am, pbreit <pbreitenb...@gmail.com> wrote: > Express Checkout doesn't need the crypto stuff. > > And the crypto stuff is actually optional on PayPal "_cart" and "_xclick". > Just make sure to review your payments visually or with IPN to make sure > there was no tampering.