Sundry Howtos
For clients with irregular CC payment needs, a credit card gateway that doenst charge a monthly fee, and doesnt require a merchant bank account is required. Paymex worked quite well in that regard, but Paypal also offers accounts like this. However integrating Paypal is a more difficult proposition.
Their documentation is scatter over several domains, versions and much of it SSL. The PDF Website Integration Guide, which is to be your primary reference, runs to 384 pages, and contains much duplication, contradiction, and confusion. The basic thing to understand is that there are numerous ways to run it, none straightforward and this document will limit to the websites payment standard, running under the free standard business account.
With Paymex IPN works seamlessly as follows:
1. Your site sends the user off to the payment gateway, returning after a sucessful payment with a few useful parameters specified on the url query. However obviously this cant be trusted to confirm the payment becasue anyone could submit the same url/query.
2. So your site then opens a connection to the Payment gateway`s IPN server, asking for verification of the payment, which they send straight back via a TCP socket.
With paypal IPN works differently:
1. Your site sends the user off to the payment gateway, returning after a sucessful payment with a confusing range of options ranging from exactly nothing, to a profuse quantity of parameters via POST.
2. Your IPN code has to wait for paypal to contact you, sending the parameters via POST, which you then confirm back, which paypal then reconfirms is a successful payment. So three steps to IPN.
From the guide:
`The IPN message service should not be considered a real-time service. Your checkout flow should not wait on an IPN message before it is allowed to complete. If your website waits for an IPN message, checkout processing may be delayed due to system load and become more complicated because of the possibility of retries.`
If you follow one of the howtos such as this or this one youll most likely spend two days problem solving as i did. (The alternative of wading through 384 pages is always an option!)
But Paypal also have a thing called PDT, payment data transfer, which works more like Paymexes IPN. That is paypal send the user back to the return address with a queryline that gives only enough info to verify the payment. Eg:
http://example.com/paypal.php
?do=success
&tx=7M803713EY7828518
&st=Completed
&cc=NZD
&amt=3.00
&cm=dg79n8j9ljbbqct38sqhob89n0
&item_number=1025
Nb. cm=custom and thats a md5 we send to make sure we get the same person back. item_number is also ours, thats our order id. Do is ours (from the return url query) and rest are shortened versions of paypals std vars.
Nb. this terse return query overrides any rm var setting. When PDT is on rm does nothing.
You then use the returned transaction id (tx) in combination with the seller`s auth token (at=found in seller prefs) to send off the PDT validation query via socket. PDT sends a reply confirming a valid or invalid payment along with a full set of transaction vars.
This is all good, but heres the catch:
`However, there are cases, such as with pending transactions, where you won`t receive notification of all transactions. For this reason, PayPal strongly recommends that you also enable Instant Payment Notification (IPN)` -- PDT guide.
So you are back to square one.
The problems with relying on IPN alone are:
1. the parameter called rm has three possible values according to the manual, all of which are useless. 0 and 1 are return via GET, 2 via POST, but all leave via a form submission that is triggered by the user having to click a small somewhat unnoticable faux text link (return to xyz site). Its actually a graphic that looks like a text link, and even if you specify GET its a form using GET not a url query. The net result is that the user gets lumped with a browser nag (unsafe content about to me submitted) owing to leaving SSL via a non ssl form submission. (NB: BTW if you use 0 or 1 it removes any query string you had on the return url.) (Nb. the IPN reply is always done using POST, regardless of rm, pdt or anything else.)
2. Even though many parameters are sent in the original POST submission to paypal, you have to make a range of seller prefs changes to get payments to work. In some cases the same parameters are set in both seller prefs and in the payment submission. Its hard to know which take priority in many places. Note that you have to click the suckerfish type link labeled profile in my account! If you just hover youll be given a sample of options of which seller prefs is not one of them! Their idea of UI is not that hot.)
3. The solution to problem 1 is to go into the payee`s paypal account, and under profile | seller prefs | website payments turn on auto return. This avoids the stupid faux link, and gives the user a page saying they will be redirected to your site in 5 seconds. But oddly the return in this case is a plain url link to the return url address parameter, with no GET or POST info about the payment. Thus its problematic to rematch the user back to their order. This is sort of solved on page 273 of the manual which states:
`With Auto Return turned on in your account profile, you can set the value of the return HTML variable on individual transactions, which overrides the value of the return URL that you stored on PayPal as part of the Auto Return feature. For example, you might want to redirect payers to a URL on your site that is specific to that person, perhaps with a session-id or other transaction-related data included in the URL. To set the return URL for individual transactions, include the return variable in the HTML.`
So that solution is to put your internal order number and a security hash on the query string of the return url, not in the other fields as usual. Paypal doesnt make this easy that is for sure, but once its working the desired effect is more or less achieved.
4. Note that the fact they you cant initiate IPN means you cant provide a real time post payment action such as a download link or subscription.
While in practice most transactions are IPN verifyed by the time the 5 sec redirect is up, but if there is a backlog queue, it may not. Hence post payment all you can do is either say to the user your payment looks fine, and you will recieve confirmation via email shortly. As alternatives if the IPN is done by the time they get back, then give them their whatever download. If it isnt some people send the user a page saying please wait for confirmation, which meta refreshs every 5 seconds until the IPN is complete, timing out at say 30s and giving up.
A better solution is to use both PDT and IPN, with PDT taking precedence, and using IPN as a backstop. This way the majority of transactions occur in real time, allowing you and your user to get on with the what ever you are selling.
OK heres the code snippit we use to send the form.
//add std parameters
$return1=`http://example.com/paypal.php?do=success`;
$return2=`http://example.com/paypal.php?do=cancel`;
$return3=`http://example.com/paypal.php?do=ipn`;
$hiddeninserts.=`<input type=`hidden` name=`business` value=`info@exampleblablabla.com` />`; //paypal account
$hiddeninserts.=`<input type=`hidden` name=`notify_url` value=`$return3` />
$hiddeninserts.=`<input type=`hidden` name=`item_name` value=`Assorted Stuff` />`; //keep it simple ie 1 x Bedding purchase
$hiddeninserts.=`<input type=`hidden` name=`quantity` value=`1` />
$hiddeninserts.=`<input type=`hidden` name=`cmd` value=`_xclick` />`; //pay method=buy now
$hiddeninserts.=`<input type=`hidden` name=`image_url` value=`https:`; //example.com/imgs/logo.jpg` />`; //add a local logo file (150 x50) for paypal to use. (Needs to be SSL to avoid nags)
$hiddeninserts.=`<input type=`hidden` name=`currency_code` value=`NZD` />
$hiddeninserts.=`<input type=`hidden` name=`lc` value=`NZ` />
$hiddeninserts.=`<input type=`hidden` name=`no_shipping` value=`1` />`; //1=dont prompt for ship address
$hiddeninserts.=`<input type=`hidden` name=`no_note` value=`1` />`; //1=dont prompt for comment
$hiddeninserts.=`<input type=`hidden` name=`rm` value=`2` />`; //return method
$hiddeninserts.=`<input type=`hidden` name=`night_phone_a` value=`64` />`; //area code of phone no
//add parameters unique to this order
if (ereg(`?`,$return2)) $return2.=`&`; else $return2.=`?`;
$return2.=`item_number=$order_id`;
$hiddeninserts.=`<input type=`hidden` name=`return` value=`$return1` />`; //be aware that these 3 are also set in PP seller prefs too
$hiddeninserts.=`<input type=`hidden` name=`cancel_return` value=`$return2` />`; //but these take priority.
$hiddeninserts.=`
<input type=`hidden` name=`amount` value=`$amount` />
<input type=`hidden` name=`first_name` value=`$first_name` />
<input type=`hidden` name=`last_name` value=`$last_name` />
<input type=`hidden` name=`address1` value=`$address1` />
<input type=`hidden` name=`address2` value=`$address2` />
<input type=`hidden` name=`state ` value=`$suburb` />
<input type=`hidden` name=`city` value=`$city` />
<input type=`hidden` name=`zip` value=`$postcode` />
<input type=`hidden` name=`country` value=`$country` />
<input type=`hidden` name=`email` value=`$email` />
<input type=`hidden` name=`night_phone_b` value=`$phone` />
<input type=`hidden` name=`item_number` value=`$order_id` />
<input type=`hidden` name=`custom`value=`$securityhash` />
`;
Youll have to fix the messed up quote marks and add the rest of the form, use js to auto submit the form or whatever. But you get the idea.
First if you have a personal account with Paypal you must upgrade it to a business account (otherise seller prefs wont be there). Do this in my account, click the unobtrusive upgrade link near nav bar. If creating a new paypal account choose business account, not personal or premier. These accounts are all free, OTOH Pro accounts are needed for:
Next go into seller prefs, and turn on IPN, and turn on auto return, and turn on PDT. Save the seller auth token or `at`. Enter default values for the return addresses, but know that they are overridden by the return parameters your cart form sends them.
PDT validation involves sending the at and tx to paypal with a pdt cmd. Your paypal.php page has its three incarnations, sucess does pdt,and says thanks. Cancel says try again by clicking here. IPN is called by Paypal only and therefore can output nothing to you or your user. If PDT hasnt occured yet, IPN is not replied to ie ignored for now, causing it to retry later. If PDT has been attempted but failed, then IPN is triggered, then do your post payment actions instead of having sucess do it, lastly dieing quietly. If PDT had previously suceeded then IPN replys saying in effect thanks anyway, to prevent it retrying later on.
Its a complex solution but there you go. Thats paypal--absolutely nothing is straightforward.
The IPN code is hard to debug, so make sure you add in logging to db/file/email at several places through the code , for example, IPN started. IPN succeeded, IPN failed with msg etc.
Paypal docs:
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNIntro
https://www.x.com/docs/DOC-1449 (PDT guide)
https://cms.paypal.com/cms_content/en_US/files/developer/PP_WebsitePaymentsStandard_IntegrationGuide.pdf
Paypal classes:
[http://webspaces.net.nz/files/paypal.zip]
[http://www.micahcarrick.com/php-paypal-ipn-integration-class.html]
[http://sourceforge.net/projects/paypal/]