PHP Tutorial: PayPal Payment Data Transfers (PDT)

MoneySay you have a PayPal “Buy Now”-button on your website and you have assigned return URLs like http://example.com/order?done and http://example.com/order?canceled. You can then welcome the user back after a successful payment. But what if you wanted to say something more interesting than just “hey, welcome back” when they click on that “Return to Merchant”-button? And can you know if the order was actually done or canceled? Maybe you’d like to log the transaction in your database and mark a payment as complete or something like that too? In that case you sure can’t trust a simple flag in the address bar…

Payment Data Transfer (PDT) is a secure method to retrieve the details about a PayPal transaction so that you can display them to your customer. It is used in combination with Website Payments Standard, so that after a customer returns to your website after paying on the PayPal site, they can instantly view a confirmation message with the details of the transaction. — PayPal

I’ve tried to figure out how to use PDT and found that most samples and classes to build from are usually quite ugly, old or outdated. I didn’t find them too useful anyways… So, therefore, I’ve tried to do my own thing based on the documentation found on the PayPal Developer websites. (Seriously, how many versions of documents and developer websites do they have anyways? It’s like a complete jungle…)

Since the documentation was a bit of a mess, I thought I make a small tutorial on the steps needed to get started. That way I can learn it better myself and hopefully help some other poor souls that need to figure this stuff out as well. Please provide feedback if you have any! Would love to make this page nicer and clearer if possible :)

Step 0: Sign up for a Developer Account

Before you start playing around with PayPal stuff, I would recommend that you get a PayPal developer account. You should also set up a business account in the PayPal Sandbox which you can then use instead of your real account. A lot safer, cheaper and easier than using a real one.

Step 1: Enable PDT for your PayPal account

For this step I’ll just quote their PDT page (one of many, it seems):

Apparently they have changed their UI now, so the instructions in their PDT documentation is wrong… Thanks for the heads up Yuriy! The description below should be correct now :)

To use PDT, you must activate PDT and Auto Return in your PayPal account profile. You must also acquire a PDT identity token, which is used in all PDT communication you send to PayPal.

Follow these steps to configure your account for PDT:

  1. Log in to your PayPal account.
  2. Click the Profile subtab.
  3. Click the My Selling Tools button in the left column.
  4. Find the Selling online section and click Update in the Website preferences row.
  5. Under Auto Return for Website Payments, click the On radio button.
  6. For the Return URL, enter the URL on your site that will receive the transaction ID posted by PayPal after a customer payment.
  7. Under Payment Data Transfer, click the On radio button.
  8. Click Save.
  9. Find the Selling online section and click Update in the Website preferences row again.
  10. Scroll down to the Payment Data Transfer section of the page and take note of your PDT identity token.

The last point is particularly important and you should not only view the token, but note it down. Reason being that you need this in your code when we’re going to talk to PayPal later.

Screenshot of PayPal Payment PreferencesAs an aside, I would also recommend you turn off the “Block Non-encrypted Website Payment”, at least while testing, since it’s a lot easier to make PayPal buttons on the fly without encrypting them. I currently don’t know how, but I’ll probably try to figure out soon. I would also recommend that you set PayPal Account Optional, so that people can make payments without signing up (since some people don’t want to be bothered with that). To the right I have put a screen shot of that page with the mentioned options marked in yellow. Click on it for full-size view :)

Step 2: Create a button

The PayPal buttons consist of a form which has a number of hidden key/value pairs and a submit button. You can find info on how to make those in their Website Payments Standard Integration Guide so I won’t explain how to do that here. What I would like to mention in this case though, is that you can, if you need to, override the return URL that you set in the previous step for specific buttons. For example you can return them to the order they paid for rather than just a general return page.

<input type="hidden" name="return" value="http://business.example.com/order/123/" />
<input type="hidden" name="cancel_return" value="http://business.example.com/order/123/" />

I use the same URL for both since we will check things with PDT anyways. Here is an example button with the data I use in this tutorial:

<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" accept-charset="utf-8">
    <p>
    <input type="hidden" name="cmd" value="_xclick" />
    <input type="hidden" name="charset" value="utf-8" />
    <input type="hidden" name="business" value="paypal@business.example.com" />
    <input type="hidden" name="item_name" value="Stuffed bear" />
    <input type="hidden" name="item_number" value="BEAR05" />
    <input type="hidden" name="amount" value="20.00" />
    <input type="hidden" name="currency_code" value="USD" />
    <input type="hidden" name="return" value="http://business.example.com/order/123/" />
    <input type="hidden" name="cancel_return" value="http://business.example.com/order/123/" />
    <input type="hidden" name="bn" value="Business_BuyNow_WPS_SE" />
    <input type="image" src="https://www.paypal.com/en_US/i/btn/btn_buynowCC_LG.gif" name="submit" alt="Buy Now" />
    </p>
</form>

So, the user will click the button, go through the payment procedure, and at the end there will be a link/button to return to the merchant. When that is clicked, we get to the next step.

Step 3: Catch the return

The URL they are returned to could for example look like this (split for easier reading):

http://business.example.com/order/123/?tx=6BC88318RN685282M
&st=Completed
&amt=20.00
&cc=USD
&cm=
&item_number=BEAR05

As you can see we get some GET parameters to work with here, but the only one you need is the Transaction ID, tx:

if(isset($_GET['tx']))
{
  $tx = $_GET['tx'];
  // Further processing
}

The reason we don’t care about the rest is that they’re not trustworthy at all. Since they are GET parameters we can easily tamper with them in the address bar of our browser, and we just can’t have any of that, can we?

To get good and trustworthy data we need to send this transaction id back to PayPal together with our PDT Identity Token. If the id and token are valid, we will get real and actual transaction details back in return.

Step 4: Do a POST request back to PayPal

Sending the Transaction ID and our PDT Identity Token back to PayPal can be done in various ways from PHP. I used cURL which I found to be pretty clean and easy to use.

// Init cURL
$request = curl_init();

// Set request options
curl_setopt_array($request, array
(
  CURLOPT_URL => 'https://www.sandbox.paypal.com/cgi-bin/webscr',
  CURLOPT_POST => TRUE,
  CURLOPT_POSTFIELDS => http_build_query(array
    (
      'cmd' => '_notify-synch',
      'tx' => $tx,
      'at' => $your_pdt_identity_token,
    )),
  CURLOPT_RETURNTRANSFER => TRUE,
  CURLOPT_HEADER => FALSE,
  // CURLOPT_SSL_VERIFYPEER => TRUE,
  // CURLOPT_CAINFO => 'cacert.pem',
));

// Execute request and get response and status code
$response = curl_exec($request);
$status   = curl_getinfo($request, CURLINFO_HTTP_CODE);

// Close connection
curl_close($request);

The cURL options we set means roughly: Use this URL, do a POST, send these variables, return the transfer as a string when I call curl_exec and do not include the HTTP headers in that string. The reason behind the last two are in this post. I’ve commented them out, and if it works without them, just leave them out. For me it worked fine on one server, while on another I had to add them.

Note: For security reasons, PayPal will only allow you to use the transaction id to get a response up to 5 times. After that it will be invalidated and you will get an error back instead of the transaction data. Fair enough for regular use, but found it a bit annoying while developing since it took some tries to get the result I wanted :P

Step 5: Check the response

The response will be a string that looks something like this:

SUCCESS
mc_gross=20.00
invoice=AF000001
protection_eligibility=Eligible
address_status=confirmed
payer_id=ADSZV7LHTCJM4
tax=0.00
address_street=Candy+Road+77
payment_date=12%3A04%3A18+Oct+19%2C+2010+PDT
payment_status=Completed
charset=windows-1252
address_zip=99501
first_name=Alice
mc_fee=0.88
address_country_code=US
address_name=Alice+Foobar
custom=
payer_status=unverified
business=paypal%40business.example.com
address_country=United+States
address_city=Anchorage
quantity=1
payer_email=alice.foobar%40example.com
txn_id=6BC88318RN685282M
payment_type=instant
last_name=Foobar
address_state=AK
receiver_email=paypal%40business.example.com
payment_fee=0.88
receiver_id=JNNUMM42GJB9U
txn_type=web_accept
item_name=Stuffed+bear
mc_currency=USD
item_number=BEAR05
residence_country=US
receipt_id=3354-8100-5749-2926
handling_amount=0.00
transaction_subject=Stuffed+bear
payment_gross=20.00
shipping=0.00

To make sure everything is well we must check that the status code is 200 and that the first line says SUCCESS. On failure it would say FAIL, or something like that. We can do that like this:

if($status == 200 AND strpos($response, 'SUCCESS') === 0)
{
    // Further processing
}
else
{
    // Log the error, ignore it, whatever
}

That response is not very useful right now though. Even if it is valid, the string is just, well, a long string. Not to mention it’s URL encoded and possibly in the wrong character encoding. We want to clean it up and turn it into a much more handy associative array.

Step 6: Clean up the response

We want to remove the first line, URL decode the string, turn it into an associative array and convert the encoding if needed. This is how you might do that:

// Remove SUCCESS part (7 characters long)
$response = substr($response, 7);

// URL decode
$response = urldecode($response);

// Turn into associative array
preg_match_all('/^([^=\s]++)=(.*+)/m', $response, $m, PREG_PATTERN_ORDER);
$response = array_combine($m[1], $m[2]);

// Fix character encoding if different from UTF-8 (in my case)
if(isset($response['charset']) AND strtoupper($response['charset']) !== 'UTF-8')
{
  foreach($response as $key => &$value)
  {
    $value = mb_convert_encoding($value, 'UTF-8', $response['charset']);
  }
  $response['charset_original'] = $response['charset'];
  $response['charset'] = 'UTF-8';
}

// Sort on keys for readability (handy when debugging)
ksort($response);

The $response should now contain a nice and sorted associative array with the correct encoding and it’s ready to be used for whatever purpose you need.

Step 7: What now?

What you do with the validated and cleaned up data depends on what you need and what you want. You could use it to display a nice helpful message to the user, store it somewhere, maybe mark something as paid in your database, et cetera. You should also most likely check that the data you have gotten corresponds to what you expected. For example if the amount they paid matches the amount they should have paid and so on.

Important thing to remember about PDT

You are actually not guaranteed to get this PDT data. Unless the user clicks that last button in the payment process at PayPal and returns to your page, you will get nothing. They could for example close the browser tab when the payment is done or the power could go out just before they had the opportunity to. Theoretically it could happen!

If you want something that is fully reliable, you need to have a look at PayPal Instant Payment Notifications (IPN). PayPal sends these notifications to a certain URL you have provided behind the scenes and it is not part what the user does or does not do. They may not be instantaneous, but you are guaranteed to eventually get them.

What you most likely want is a combination of these two. Use PDT to give the returning users a quick response and store what you can/need in the database. Then use IPN to fill out the gaps of missed PDTs or missing transaction details (IPN will give you many more details than PDT does).

It took some time, but I have finally finished a tutorial on IPN Check it out if you’re interested 8)

Working sample

Since the code above was a bit broken up, I have gathered it all in a single example page that should be operational. It contains a simple Buy Now button and it will display the PDT response when you return from the fake purchase. Since it uses the PayPal Sandbox you need to be logged in to your developer account. For the payment you can use one of your existing test accounts or you can pretend to be a person that just wants to use his credit card without an account (If you need help coming up with a fake credit card number that validates, have a look at darkcoding.net/credit-card-numbers).

You find it over at samples.geekality.net/pdt.

The process_pdt function you can see in the source code should be pretty self-contained and you should be able to just drop it in to whatever you are working on and use it out of the box. Just remember to fill in your actual identity token ;)

And that’s the end of this tutorial!

Think this was my first longish in-depth post, so if you have any feedback, found it helpful, please leave a comment. Also do that if something is unclear, could be improved or is flat out wrong. Especially if it’s flat out wrong. There’s a lot of wrong stuff on the Internet, and I don’t want to contribute to that :P

  • http://sunriseweb.ca Brad Trivers

    Thanks for the awesome post! I’m trying to integrate PayPal with a reservation system plugin I’m building for a client and couldn’t agree more that the PayPal documentation is all over the map – very hard to determine what I need to do. Your tutorial helped clarify things a lot – it’s great!

    I set up Website Payments Standard and it works well for me – even pre-populating address information based on the WordPress user registration data (a person has to be registered to make an online reservation). Of course the problem came when I needed to confirm payment before I could confirm a reservation (reservations are a custom post type and I’m using the a “draft” post status to indicating a pending reservation – updated to “future” post status when the reservation is confirmed – not possible to create a reservation in the past “published” post status). It’s really too bad that a person using PayPal has to choose to return to the calling site instead of being re-directed there after payment is complete. It looks like a combination of PDT and IPN is the way to go – too bad it’s so complicated.

    It gets even trickier for me since a pending reservation can expire if payment is not received in a given time period (20 minutes is suggested by my client). This means that a person could make a payment using PayPal for a reservation that has expired and they will need to have their money refunded. I suppose I can check if the expired reservation dates are still available and allow them to confirm the reservation anyhow – tricky…

    Anyhow thanks again as this post basically tells me exactly what I have to do on the PayPal side of things – although I have to say I’m sort of dreading the implementation.

    Another thought – maybe there is an existing WordPress plugin that can be integrated with, or at the very least whose code can be used as a base – like this one?

    • http://www.geekality.net Torleif

      Glad you found it useful! Kind of funny I must say; from what you say it seems like you are trying to make something very similar to what I have been working on, hehe. Yes, these things get complicated, so I’m happy that the PayPal stuff can make it a bit all easier.

      Yes, PDT + IPN is the way to go. And how they work are quite similar. The verification is a bit difference, but once you have the data it is pretty similar how you use it and work with it.

      I will try to write a tutorial on how to do IPN sometime. I already have working code for it, and it’s not that complex. The complex parts are of course how you use it in your own project.

      On the matter of refunding, if you play around in the PayPal sandbox, you will discover that Refunds are actually pretty easy to do. And I think it can be done from your code as well through one of their APIs. Might research that later myself and perhaps write some about it when/if I figure it out :)

  • Curran

    Thanks very much. I was having difficulty with PayPal myself. Great tutorial. Would love to see your IPN post! Cheers!

    • http://www.geekality.net Torleif

      Thanks! I’d love to see it too. I have it pretty much mapped out in my head and I know how to do it. Just have to find time to write it… Perhaps tomorrow evening actually. Stay tuned! :)

  • http://guybon.com Tony Hutson

    I am trying to build confidence before adding payments to my cart process and this is just the sort of thing I need.

    Still need to work out encryption but this takes me a long way forward. Thanks.

  • Aslam Shah

    Thank you lots !

  • Sneha

    Hi, I am java developer.
    Thanks alot. You have put all important stuff together in very simple and easy word.
    I want to use PDT+IPN for my site, but not clear how I ll achieve it. Your post will surely help me.

    • http://www.geekality.net Torleif

      Hi! Good to hear it was useful. I really want to get the tutorial on IPN written. Annoys me that I haven’t gotten time to do it yet. Hopefully soon, hehe.

  • http://www.d-engine.nl Jerome Bohg

    Great tutorial. Took me some time though to figure out that when not using SSL I had to change the URL to CURLOPT_URL => ‘www.sandbox.paypal.com/cgi-bin/webscr’. Works now and I’m very happy with this. The script that paypal provides seems a bit outdated although it works. What I still dont get about Paypal is that you do the check after the payent has been made. Wouldn’t it be great to generate an encrypted key from the information in your form and your PDT token. This could then be checked on the paypal website before a buyer makes it’s payment.
    Anyways, thank you very much for sharing your thoughts about handling PDT!

  • http://www.d-engine.nl Jerome Bohg

    I just read my previous comment. What I meant is that I would like to do a check before making a payment so the buyer knows he’s transfering his money to the right account. Of course there should always be a check after the payment is made as well.

    • http://www.geekality.net Torleif

      You can actually encrypt the form data if you want. I didn’t do that here to keep it simple, but for example if you generate the PayPal button through the PayPal button factory (or what it’s called) you can choose to have the button encrypted. That way the form data can’t be changed. This can also be done on the fly in your code.

  • alex maier

    Hey, I am currently a computer science student still in college. I just made a website recently where I have a java applet for a dice game that I made. I can show it on the website no problem, but I want to be able to only show the applet after someone has paid through paypal.

    I took your exact code and changed only 2 things. I changed $your_pdt_identity_token, to be '8gCOx0S4FW-g6s0Pd8kDPl95UXR2ZG5m_Qt2hh6mqhwyK3i-nse6S4aiqMm', (which is my token id for the sandbox account) and changed “paypal@business.example.com” to “admin_1297365642_biz@zonkonline.info” (which is my email address for the sand box).

    After uploading it to my web-server. It looks exactly the same as yours, when I click PayPal button it sends me to my test store and can go through the whole payment process, but when it auto returns me to my site, it doesn’t show me the thank you message. It does show me this,

    GET: Array
    (
        [tx] => 6KL19869KN546713P
        [st] => Completed
        [amt] => 20.00
        [cc] => USD
        [cm] =>
        [item_number] => BEAR05
    )

    PDT:

    Where there is a few things in the GET but nothing in PDT. As well no thank you message. Do you know why this is happening and what to do to fix it?

    Also, when this is fixed, do you know where I could put the code ” ” (which shows the applet) so that it will show after someone has paid.

    Thanks a lot in advance for the response!

    • http://www.geekality.net Torleif

      You are probably getting an error message back from PayPal or something which contains the answer for that. Check the response you get and for any cURL errors in the function. For example:

      echo curl_error($request);
      echo $response;
    • http://www.geekality.net Torleif

      where to put the code depends on what and how you’re doing it. Your function returns false, so you can just have a check somewhere for that.

      However, remember that you can only use the transaction id 5 times. Which means if the user reloads the page 5 times, they can no longer access your thing, even though they have paid. And that would certainly annoy me if I was a user of that. So you should probably rather store the result in a database with some sort of login system or something so that the user can return to your page and use your applet any time.

      • alex maier

        I just don’t know php well at all. Is there someone or some company I can pay a little to write this php code for me?

        I am fine with the user only being able to refresh the page 5 times. I want to charge just over about $1.00 to use the scoreboard and they won’t need to refresh any more than that. If you refresh the page then you would loose all your data from the scoreboard.

        • http://www.geekality.net Torleif

          Most likely yes. I’m not one of them as I have my plate full already, but sure you can find a coder to code for you. That’s usually how programmers survive. Writing code for other people ;)

  • http://www.iaddesign.com sarmen

    i noticed in the source there is a hidden field called amount that holds the amount value of what is being sold. is it possible to do this another way? because anyone can come and change that value to buy something for free.

    • http://www.geekality.net Torleif

      This is also why we have PDT and IPN. Because when someone pays for something, you shouldn’t just ship anything to them because they paid. You should ship it when you have confirmed with PayPal (through PDT or IPN) that they in fact have paid the correct amount.

      If you want to be really secure, however, you can encrypt the form contents.

  • http://twelve6.com Luis

    I have integrated IPN successfully, and know I’m trying to add PDT. I have enabled it in my paypal, also enabled auto return and set up the location, as well as enabled IPN. However I can seem to get the tx variable when paypal return to the return page I do get a GET string but I’t looks alot like the IPN string I believe they are identical, in that string I find tdn_id variable witch I have come to find is the same value as tx. do you have any idea what I am doing wrong and why I dont just get the tx variable fron sandbox.paypal?

    • http://www.geekality.net Torleif

      I tested the sample code I made, and it still works. If you use PDT and have set it up correctly you should be getting the following GET variables: tx, st, amt, cc, cm and item_number.

      It could maybe be related to what they talk about in this thread:

      With regards to the issue with PDT not returning the ‘tx’ variable in the return URL when using PDT, that issue should have been resolved through a recent system update. I also tested it from here and it works.

      Otherwise, make sure you have used the correct token, correct URLs, et cetera.

      • http://twelve6.com Luis

        Hey thanks for the response, I got it working not with CURL for some reason I couldn’t get it to work with curl I should check if it’s installed I guess. but I got it to work with fsockopen() I have removed the headers successfully and validated for Success and status code 200. I am trying to remove “SUCCESS” with substr() but can’t do it like you do it because I get “3a0″ before “SUCCESS” any idea why?

        • http://twelve6.com Luis

          I didn’t use strpos() to remove success but when I turned it into an associative array fixed char encoding I was removed automatically. you think this will be trouble in the future?

          • http://www.geekality.net Torleif

            No idea! There are many different ways you can do what I did. The one I chose was simply the one I understood best and the one which was fastest in my benchmarking.

            If you have tested your way to do it well, it should work :)

  • http://escteam.com fred

    I use your code to check the success of transactions but the $_POST and $payment_data are both completely empty…

    Been trying to do this for 5 days now, using my code, paypals code, third party code, your code…

    • http://www.geekality.net Torleif

      Do you get the transaction id through the $_GET variable?

  • Manish Pal

    hi,

    I used your code for payment data transfer. The PDT sample in your website – http://samples.geekality.net/pdt/ is showing perfect values.

    And after the payment it is automatically having few extra parameters in its url –
    http://samples.geekality.net/pdt/?tx=18H60911CF7180847&st=Completed&amt=20.00&cc=USD&cm=&item_number=BEAR05

    But when I am using your php source, there are no such extra parameters. And also none of the values are showing up, i.e. first_name, mc_gross, mc_currency.

    Can u help in this ???

    • http://www.geekality.net Torleif

      Not sure what you mean now. You mean the transaction id and such when returning from the paypal payment? Those do not appear? Sure you have enabled PDT in your PayPal settings?

      • Manish Pal

        Yes, I have enabled PDT.
        I think the transaction id is not returning.
        Do not know why, but it is happening ?
        Hence need your help….

        Just for example from my first question, I have given two links for your website itself.
        The first link is of the original webpage in which you have given PDF sample,
        and the second link is the page after the payment is made.

        The second link is having – transaction id, status, amt, cc & item number – in its url.

        I have enabled PDT in my account, bu then also when I am using the php source code, it is not showing any transaction id, status, etc – is not showing up.

        Hope, now it is clear…..

        • http://www.geekality.net Torleif

          Checked all the variables in the script? Do you have the correct business name? Are the return and cancel urls correct?

          • Manish Pal

            http://www.manishpal.com/paypalpdt/

            Above is the url for testing the PDT with your php source code.
            Have changed only the business email and token.

            I think there is a little difference in the php source and the sample webpage you have given, because the tx, st, cc, amt, is not coming in the url.
            Something is missing.

            Hope by seeing you will know it better.

            Regards,
            Manish Pal

  • http://www.geekality.net Torleif

    @Manish Pal:

    Tried it out, and you’re right, I can’t see any parameters coming in either. Are you sure you have enabled PDT correctly with the business account you’re using?

    • Manish Pal

      I am trying some other scripts for PDT.
      Hope I will succeed.

      Till than, I personally thank you very much for all your support and time you have given.
      Thanks from my heart.

      Regards,
      Manish Pal

      • http://www.geekality.net Torleif

        You’re welcome! Please let me know if you figure out what was wrong :)

        • Manish

          It took me so many days, my problem is solved.
          I have used your script, but it didn’t worked.

          Then after I used the sample php script given by PayPal.
          PayPal’s sample script is working great……..

          Thought to show you the below link –
          http://www.manishpal.com/paypalpdt/new_buy.php

          In the last confirmation page – I showed all the required data which i want to know about the payment and the payer.

          You are a professional and your advise is required – if i am missing something!

          As a simple layman – it makes me very happy to complete one small project.
          Hurray.

          Thanks and Regards,
          Manish Pal

  • Mamunul Mazid

    You are great boss. For few days i am searching for step by step process to integrate paypal. I didnt find. This tutorial is very easy to understand and I am going to implement it today.Thanks, For this tutorial.

  • http://reden87.info/ reden87

    hi. great tutorial. when will be the IPN part come? (:

  • http://battlewithcancer.com Glen

    I can’t thank you enough. I have coded a sophisticated wallet-based payment system before in PHP using Intellipay, including automated recurring txns and lots of bells and whistles. I practically built a banking application. It wasn’t easy and is a complete overkill for what I need today on a new project. So I thought PayPal would be a breeze after that.

    OMG! Their website and documentation is so bad, and ways of doing things insecure and filled with holes. Everybody who implements PayPal has to go through this?? It’s hard to believe. I spent the last few days hunting for answers in the documentation which you describe as a jungle. That’s a polite way of putting it. I then looked at alternatives like Amazon checkout and Google checkout instead, but they aren’t exact replacements for what you can simply do with PayPal.

    Then I found your website last night. Wonderful – thank you, thank you! I went to bed happy! You fill in the gaps, pointing out the important stuff and warning us of the pitfalls. It’s so simple once you know. Thank you so much for making the IPN tutorial too.

    Why can’t PayPal employ a few people like you?

    • http://www.geekality.net Torleif

      I know right? Really wish PayPal and a bunch of other enterprise companies would stop being so enterprisey. Sooo much marketing talk and jargon talk and blah. Incredibly annoying when you’re a developer trying to figure stuff out and make something :(

      Happy I can help someone!

  • Ivan

    Thank you very very much for your info, you are awesome.
    I would also like to point out that there is a little mistake in your regex (after urldecode($response)); it should be something like this:

    preg_match_all('/([^=\s]++)=([^\s]+)/m', $response, $m, PREG_PATTERN_ORDER);
    • http://www.geekality.net Torleif

      Thanks for the feedback! The added ^ might make it more correct, although it does work as it is, as far as I know. Your [^\s] would make it break if there was whitespace in a value though.

      • Ivan

        You are right; fixed:

        /([^=\s]++)=(([^=]*\s)+)/m
      • Ivan

        Mmm… i was doing some tests without the linke breaks so the special character dot in the regex wasn’t breaking the selection after each value; my mystake.

        • http://www.geekality.net Torleif

          Actually, what I already have should be the correct one. Forgot I already had the leading ^ there: /^([^=\s]++)=(.*+)/m

          It goes: Beginning of a line, anything that’s not a whitespace or equal sign, equal sign, anything that’s not a new line.

          The ++ and *+ are possessive quantifiers, which speeds up the matching.

  • Yuriy

    Activate PDT.
    They (PayPal) probably changed the “profile” page layout (I haven’t seen previous). There is no column “Seller Preferences ” anymore.

    But there is a “My selling tools” Menu Item on the left instead. And when you click – on the right – there is Group “Selling online” with item “Website preferences” inside. When you click “Update” – then you see “Auto return” and “PDT” settings – actually this is a page exactly as screen shot in your tutorial.

    Thanks for your tutorial!

    • http://www.geekality.net Torleif

      Thanks for the heads up! Checked it out, and you were indeed correct. Updated the tutorial so it should be correct now :)

  • http://www.phpcontext.com Mike

    Hey man, step 6 code is ugly, slow (because of PCRE) and complex enough, one could use this instead:

    // make query string from that string and parse it
    parse_str(str_replace("\n", '&', $response), $params);

    // one line, better?
    echo '<pre>';
    print_r($params);
    • http://www.geekality.net Torleif

      Hey, thanks for the feedback! The way I did step 6 was the fastest I could come up with at the time. Even tested various ways of doing it, and that was the fastest and cleanest way. Not sure I tried anything similar to yours though… and it does look quite simple and efficient, I must say.

      Have you tested that it works with the rest of the code and with what you get from PayPal?

  • http://iarfhlaith.com Iarfhlaith Kelly

    Hey Torleif,

    For the past couple of days (yes, days!) I’ve been in and out of the PayPal website, finding myself with tens of browser tabs open on the PayPal domain, struggling to find a decent example of how the PDT/IPN combo should work and your post here along with it’s IPN counterpart is by far the clearest and most effective post I’ve seen about integrating PayPal WPS into a PHP site.

    Thank you!

    • http://www.geekality.net Torleif

      Thanks for the positive feedback! Glad to hear it helped you :)

  • Sandro

    Really great tutorial, thanks very much!
    But I have problems with umlauts (such as: äöü). When Paypal (SandBox) returns item_name the umlauts are no longer legible.
    Have you any idea how I could solve the problem?

    I set by sending:

    and the request:
    and by requment curl_init
    CURLOPT_ENCODING => ‘utf-8′,

    • http://www.geekality.net Torleif

      Well, obviously an encoding issue. But can’t help you other than telling you to check the encoding :P Make sure you’re sending what you think you’re sending, et cetera.

  • ronald

    Hi,

    Is it possible to download the whole script. Still new to php and trying learn and understand the whole process.

    thanks . ..

    • http://www.geekality.net Torleif

      If you look at the sample you’ll find the whole source there.

  • Michael Smith

    Great tutorial, Torleif! It’s made things a lot clearer for me (and I’m working through the IPN one now).

    The problem I’m having today is that I got PDT working fine in the sandbox, but going to the live PayPal site, I don’t get any GET values back from my AutoReturn (no st or tx, for example). As far as i can tell, I’ve got both the sandbox and live accounts set up identically (Auto Return on, PDT on, different id tokens noted).

    GRRR!!! Anyway, thanks for your great work!!

    Michael

  • http://www.creativecanvasaz.com reggie

    can’t get your PDT code to quite work. the sample works well. i tried to extract code from index.php and input it into my html (and renaming file .php) but after the paypal transaction is complete. When i return to the webpage after paypal completes the transaction(i’m using the sandbox), my webpage is blank. I can see the transaction data in the URL, but my page doesn’t load and i don’t display the transaction data. do you have an email where I can contact you? i code appears to be “right there” just need the nudge to complete. new to php & xml. thx

    • http://www.geekality.net Torleif

      Do you have a web server set up to properly process php files? Try to create a PHP file with only the following contents:

      <?php phpinfo();
      • http://www.creativecanvasaz.com reggie

        a few things: yes, i created the page and got info…version(4.3.11), configuration, etc…

        i am using enabled logging of my scripts and found i was getting a Parsing error on line 56 …
        “foreach($response as $key => &$value) ”
        should be
        “foreach($response as $key => $value)”

        now what gets displayed is the data in my token file..which is at the top of my .php file. is there a way to have it not display?
        also, i know i have a few things i need to straighten out and it is detailed. write to my email and I can forward u the info…thx again.

        • http://www.creativecanvasaz.com reggie

          FYI got another error :
          “PHP Fatal error: Call to undefined function: curl_setopt_array() in /process_pdt.function.php on line 23″
          fixed by updating to PHP version 5.3.6 and all is well for now.

  • http://www.creativecanvasaz.com reggie

    i get no payment data. all i get after the
    GET: and the transaction data is “PDT ” and empty space follows. Do I not get payment data because i didnt create the form data as you did in index.php? how could I get that info from paypal?

    • reggie

      More info :

      i get
      CURL_INIT Resource id #3
      from the curl_init()

      but i get nothing from these two
      $response = curl_exec($request);
      and
      $status = curl_getinfo($request, CURLINFO_HTTP_CODE);

      do i need to set a timer or delay?

      thx again

      • reggie

        problem solved. for testing i don’t want certificates
        so set ” CURLOPT_SSL_VERIFYPEER => FALSE, ”

        this got me a response.

        thx again

  • Don S.

    On the PDT Sample page, the “View Source” do not display anything. It does have the following error shown at the bottom of the Sample page:

    Webpage error details

    User Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; GTB7.2; .NET CLR 2.0.50727; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 1.1.4322; InfoPath.3; BRI/2; BO1IE8_v1;ENUS)
    Timestamp: Mon, 16 Jan 2012 20:25:16 UTC

    Message: Expected identifier, string or number
    Line: 17
    Char: 3
    Code: 0
    URI: http://samples.geekality.net/samples.js

    I like your tutorial; very clear. However I would like to have the combined source code.
    Thank you…….
    Don

    • http://www.geekality.net Torleif

      Hm, that’s strange. I can view the source of both files in the PDT sample without any problems. What browser are you using?

      • Don S.

        Thanks for the reply. I was not looking in the right place. However, you website does exhibit the error message which I noted in my previous post. I am using IE8.

        Thanks again….

  • Toroid

    in Sandbox there is no My Selling Tools button it is the PDT stuff
    is under Website Payments Prefefences .
    Excellent tutorial, your comment about using PDT and IPN most useful.
    Nowhere in the mess that is the PayPal manuals does it give any idea about this. Finding information was an absolute nightmare, so big thanks.

    • http://www.geekality.net Torleif

      Oh really? Maybe it’s a bit different from the regular site? Maybe they’re not updating them in sync or something…

      Yeah, it was a huge pain when I tried to figure this out as well, which is why I decided to note it all down in case I or others needed it again :)

  • Martin

    Hi Torleif,
    Just thinking… is there any way of specifying a return URL that includes the tx variable e.g. download.php?tx=TRANSACTIONID without using auto return and PDT?
    I’ve tried and tried but don’t think it’s possible. I only ask because I don’t really have a need for PDT – all my background work is done with IPN.
    I’d just like to send my visitors to a download page (they’re purchasing a digital download) without having to worry about a “thank you for your payment” message, which I’d prefer to display on PayPal.
    The download URL is emailed out (via the IPN page) as well as displayed as a link from PayPal, and I don’t want to display the thank you message if the user’s come from the email link.
    Thanks again for all your help with the two tutorials!
    Martin

    • http://www.geekality.net Torleif

      Not exactly sure what you’re trying to accomplish, but could you perhaps just use the download.php file as the PDT return uri? or have a page with “Thank you for the payment, here’s your download” and that download link?

  • jev

    This really is a great resource.I’d even struggled befor finding this with getting my sandbox account working!
    The main problem I had was understanding Certificates – still don’t! I kept getting cURL errors about the path of cacert.pem. Anyway I did what somebody else had suggested and set CURLOPT_SSL_VERIFYPEER => FALSE,

    All is now fine. Many thanks for taking the trouble to write this.

    • http://www.geekality.net Torleif

      Yeah, you either need to set it to FALSE or specify the path. When specifying the path you of course also need to make sure to put a certificate file at that path :)

  • http://codepixelz.com Utsav

    Well I know I am over a year late on this article, but yet, I tried using Paypal PDT and all i get is st=pending never do I get Completed which is why the CURL returns FAIL.

    What probably could be wrong? I am stuck here for quite some time now and any help would be much appreciated.

    • http://codepixelz.com Utsav

      Got It! It was with the currency issue. My account had USD as deafult and It seems I hadn’t allowed direct approval of other currency (AUD) to be precise. It’s done now.

      • http://www.geekality.net Torleif

        Well there you go ;) Good to hear you figured it out!

  • nickyc

    Great tutorial

    In case it helps anyone else here’s the problem i had, and (what seems to be) a solution.

    I was posting to http://www.sandbox.paypal.com/cgi-bin/webscr and getting the expected reply (tx etc)
    I was then creating a curl post back to that address, but paypal wasn’t talking back to me.
    I used the curl_getinfo($ch, CURLINFO_HTTP_CODE) from your article and found a 302 HTTP error.
    I changed the url in my curl post from http to https and all is now well, so far..

    Thanks again!!

    • http://www.geekality.net Torleif

      Yup, needs to be https I think. Good to hear you got it working :)

  • Sergey

    I wonder why you do

    preg_match_all(‘/^([^=\s]++)=(.*+)/m’, $response, $m, PREG_PATTERN_ORDER);
    $response = array_combine($m[1], $m[2]);

    Why not?

    $response = explode(“\n”, $response);

    Just curiosity.

    • http://www.geekality.net Torleif

      Because that was the fastest solution I found for splitting the whole response into key-value pairs :)

  • Stuart MacCaulay

    I am lazy trying to fiddle with code as many other people are but who won’t admit it. Get us a plugin for this, even if premium.

    • http://www.geekality.net Torleif

      And what would this plugin of yours do exactly?

  • http://computerduniya.co.in Pawan Kumar

    When return from paypal i got this error
    “”Parse error: syntax error, unexpected T_STRING, expecting ‘)’ in wwwroot\subscribe\success.php on line 19″

    and line 19 is
    17. ‘cmd’ => ‘_notify-synch’,
    18. ‘tx’ => $tx,
    19. ‘at’ => $uG0B76u2miOQ-0llP8uPAh8VncwC0nRhtE1N3iuv2wavMkMbsgvdl29vwxm,

    • http://www.geekality.net Torleif

      um, what in the world is $uG0B76u2miOQ-0llP8uPAh8VncwC0nRhtE1N3iuv2wavMkMbsgvdl29vwxm? Is that a variable with a crazy name? Or should it really have been a string?

      • Pawan

        this is my Identity Token

        • http://www.geekality.net Torleif

          Yeah, if that’s really your code, I don’t think that’s valid PHP syntax.

    • Yeah

      Change to:

      ‘at’ => ‘$uG0B76u2miOQ-0llP8uPAh8VncwC0nRhtE1N3iuv2wavMkMbsgvdl29vwxm’,

  • http://ThankYou! Ken Slater

    This was really quiet helpful. I tried the published PayPal flow, and I just could not get it to work, and found debugging difficult and slow. Decided to give your code a go, and it just worked. Thanks so much, you have made my life much easier (a volunteer webmaster for a non-profit).

    Now I have to code to handle the multitude of possible error conditions, but this task seems much less painful now that the SUCCESS flow is operational.

    • http://www.geekality.net Torleif

      Good to hear! I actually wrote this while working as a volunteer for a non-profit too ;)

      Getting the success-flow working is a great first step!

  • http://www.thequiltedrose.com.au/products.php Tim

    I’ve been trying for a month now to get the details back form paypal and this is the first that has helped. Stupid paypal documentation. I’m still new to PHP and am learning as i go. But now i have a problem when the data comes back.

    I have copied exactly as you have and changed the identity token to my sandbox account. Made a copy of the cacert.pem and put it in the same directory.

    But i came up with nothing when printing the final array. Tracking back through, i found i am getting Resource id #6 at the $request variable at the end of step 4.

    I tried putting FALSE at CURLOPT_SSL_VERIFYPEER as ‘reggie’ did (dated October 25, 2011) but this didn’t solve the issue. And this happens for any transaction i have with the paypal sandbox. I’m stuck as to what i can do.

    • http://www.geekality.net Torleif

      You should check the status and if there are any curl errors with the curl_error function.

      • http://www.thequiltedrose.com.au/products.php Tim

        I tried the curl_error but it didn’t show anything. Just came up blank when i used curl_error($request)
        Not sure if that is how it is supposed to be referenced.

        However, it did bring my attention to a value that was not set. $tx

        i put in $tx=$_GET['tx']; after $request = curl_init(); and it worked immediately.

        Not sure if this helps identify the original problem but it is solved now.

        Great piece of code this. Simple and easy to follow. Excellent.

        • http://www.thequiltedrose.com.au/products.php Tim

          Yeah. looking back on your code i see where i missed the line. i missed it when bringing it across.

          • http://www.geekality.net Torleif

            Yeah, missing the transaction id will absolutely make it not work ;)

  • laszlo

    Hi Torleif. I’m a bit puzzled about the part: “Follow these steps to configure your account for PDT”
    Which account ? In my case there are 4:
    – real PayPal account
    – real PayPal Sandbox account
    – fake seller
    – fake buyer
    To me, the account that should be configured for PDT is the fake seller.
    I can indeed follow the configuration steps using my real PayPal account.
    But my real account shouldn’t have anything to do with the sandbox testing.
    To make matters worse, both my real accounts use the same credentials.

    • laszlo

      After some digging I stumbled upon an information that states:
      “To activate PDT you must have a PayPal premier or business account rather than just a personal account.”
      But this defeats the purpose of sandbox testing. I don’t want to create a premier or business account just yet.

      • http://www.geekality.net Torleif

        “Your account” refers to the seller/business account. For sandbox testing you need to set up a fake seller/business account. For production use, you need to set up an actual seller/business account. Simple as that :)

        • laszlo

          That’s true, but my point is that the sandbox account is useless unless you have a real premier or business account.
          Thanks for your reply and the articles. They really save people a lot of time and frustration.

  • deniele

    your tutorial is great, i had tried your code, all goes smoothly, perfect ….,
    once again, thank so much….

  • http://www.zachrodimel.com Zach Rodimel

    Hi, thanks for the awesome tutorial. I’m having a bit of issue getting it setup. I’m confused in your process_pdt.function.php script; the first list include ‘token'; Where/what is token? I am getting an error when I tried to use this that no file or directory exist.

    Also, just to verify, the includes for the process_pdt.function.php script goes in the returned file after the paypal purchase, correct?

    Thanks in advance for any help you can offer.

  • che

    hi.. please help me with my paypal problem..
    i understand that i need the returned transaction id from paypal to work with PDT, but my problem is when the “If you are not redirected within 10 seconds, click here.” is clicked, it doesn’t return the transaction id or tx in the url, unlike when it is auto-redirected it returns the tx in the url.. can i remove that link or do something for it to return the tx in the url? thanks..

    • http://www.geekality.net Torleif

      No idea :S

  • http://joeycaparas.com Joey Caparas

    Hi there,

    Thanks for this brilliant piece of code. I actually got to familiarize myself with the CURL library and REGEX while learning PDT.

    For those who are receiving null values, you can change:

    ( $status == 200 && strpos($response,'SUCCESS') )

    to

    ( $status == 200 && strpos($response,'SUCCESS') === 0 )
    • http://www.geekality.net Torleif

      Good to hear! Was familiarizing myself with CURL when I wrote this too ;)

      About your change, isn’t that what I already have in my code?

  • http://www.peaknature.co.uk Chris

    Hi Torleif,

    Thanks for the tutorial, it made light work of the paypal code they produce.

    I had everything working until recently. I now get an error when I return to my site from paypal. The GET array is there but not the PDT.

    I’m given an error that the token hasn’t been included however, the PDT token from my sandbox account is there in the process_pdt function.

    I’ve tried it with my live token as well but had no luck.

    Any ideas?

    Chris

    • http://www.peaknature.co.uk Chris

      Hi again,

      sorted it! I had to comment out the CURLOPT_CAINFO => ‘cacert.pem’,

      Cheers,

      Chris

  • RandyHeber

    Nice saved me a bunch of time Thanks

  • http://www.facebook.com/jpcaparas Joey Caparas

    I agree with Chris. There seems to be an issue right now with the certificate of Paypal. I have commented out
    CURLOPT_CAINFO => ‘cacert.pem’ to make the page work. It still works either way.

    • http://www.geekality.net/ Torleif Berger

      Tried to clear it up in the post now. Those last two curl options are only really necessary if it doesn’t work without them. For one server it worked fine for me without, while on a different one I had to add them.

  • http://jamesn.net James N.

    Anybody know of a way to specify a unique identifier for a payment that will be passed back to you from PayPal via PDT/IPN? Considered using item_code but that seems a bit hax?

    Otherwise how do you know what payment a PayPal transaction_id relates to?

    • http://www.geekality.net/ Torleif Berger

      I don’t remember the name of the field, too long since I’ve been working with PayPal stuff, but I remember there was a field you could set to a unique identifier. When that is set PayPal will also prevent a duplicate id from being processed.

    • Minztine Propaganja

      I know that this is an old post and I am too late for this but I just want to help for others. I use this to send
      <input type="hidden" name="custom" value="”>

      the transacID of course is unique.

  • http://twitter.com/yogesh061 Yogesh Agarwal

    I am not sure why but when I tried this I got no response and status is 0. Any idea what could be the issue here?

    • http://www.geekality.net/ Torleif Berger

      Try commenting out these lines, if you haven’t already:
      CURLOPT_SSL_VERIFYPEER => TRUE,
      CURLOPT_CAINFO => ‘cacert.pem’,

      • http://twitter.com/yogesh061 Yogesh Agarwal

        I figured that out.. thank you!

        • Lucas

          I am also having this problem.
          Doesn’t matter if I comment those lines, there is no response and status returns 0.

          Could you elaborate what you did to fix this, please?

          • http://www.geekality.net/ Torleif Berger

            Check if curl_error returns anything.

  • Rafael

    This is a great article buddy, got me from no idea how to use it to fully implement it on my site in 30 mins, thanks a lot you should add a donate button so I can buy you a beer when my client pays me

  • Zoltan

    Hi, I cannot get a working code :( . I have created a paytest.php with these codes and below them a button form. When I press “add to cart” button i can see the item, I can login with test acc to paypal, i get back acc details but when I press “pay now” it just waits with a rotating loading icon. If I turn off payment data transfer then it goes back to paytest.php (without pdt data of course). Could you please advise me what should I check?

    • http://www.geekality.net/ Torleif Berger

      No idea, unfortunately :/

  • Purvi

    Gr8 tutorial.. But when I am trying it, return url does not send any parameters. not even transaction id “tx”
    It does come back to my site, after clicking on return. But wout any details

    • http://www.geekality.net/ Torleif Berger

      Sure you’ve activated PDT in the PayPal settings?

    • Jason

      I am having the same issue, not receiving the tx parameter back. Weird because I have everything set up correctly. I have a standard account do I need a paid account?

  • Vineet Garg

    Good One..really helpfull :)

  • LikeMyStyle

    Hi, Im sorry sir…but i just followed ur tutor on my live server, it seem’s it shows only GET result, and i cant get PDT result…perhaps the $payment_data['xxx'] was not working tho?

  • TharinduDG

    Is there a way that I can send some data to the customer via the IPN ? It says that the values under the ‘custom’ variable are never presented to the customer.
    My intention is to pass some sensitive information to the customer after he/she had made a payment.
    Please Help.
    Thank You :)

    • http://www.geekality.net/ Torleif Berger

      If you want to display something sensitive, you should probably display it to them on your website or in an email. After the IPN has gone through. So I’d probably either email them the info when you get the confirmation through IPN, or email them a link to a page with the info.

  • http://www.e-vance.net/ E-VANCE

    Thanks for such a clear & thoughtful guidance. Kudos!

  • http://www.facebook.com/alihadith Alihadith Tua

    hi. iwant use “ipn” not “pdt”, what are the post variables for the shipping details of the buyer when using ipn?

  • http://www.facebook.com/alihadith Alihadith Tua

    thanks, i have solved the issue on my website, this s great tutorial, :)

  • Terry Bullock

    I’ve been looking at PayPal’s confusing and self-conflicting documentation all damn day, and yours is the clearest, easiest explanation I’ve seen. Wish I’d come across this page earlier and saved myself some coffee… Great tutorial!

  • yeah

    Does anyone know how to make this work with subscriptions? I mean, it does work, but I can’t seem to get data like the amount for each payment, trial period, etc…

    • your dad

      mc_gross

  • Przedsiębiorca

    Thanks this is very helpfull

  • dksgjhdfjk

    Thank you

  • Deepak

    Thnnxxxxx, you have solved my problem .

    You did really a god work .

    deepak

  • Armand

    Thanks for great tutorial, very helpfull

  • Pingback: Como implementar el pago por Paypal en un sitio web » Blog OpenAlfa

  • Pingback: How to implement payments with Paypal on a web site » OpenAlfa Blog

  • Topher Hunt

    Thanks Torleif – life saver post!

  • Martin

    I would like to ask: The paypal account “web preference” Return URL setting must be same to form post variable “return”?

    • http://www.geekality.net/ Torleif Berger

      The web preference is a default return URL which you can then override using the form post variable. So no, they don’t have to be the same.

  • Sam

    Thank you for writing this tutorial. I was so confused with the Paypal documentation jungle. I finally understood the difference between PDT and IPN

    • http://www.geekality.net/ Torleif Berger

      So was I!

  • SIDESTEAL

    Big thanks for this tutorial on Paypal PDT in PHP – I got all I needed from and more. Seriously, thanks :)

    • http://www.geekality.net/ Torleif Berger

      Happy to hear :)

  • Akshat

    Great :)

  • michael

    Thanks, this is a great help!

  • lllll

    Thank you, this lessno is great for me. (bow)

  • Rob

    Very helpful post..thank you

  • dave

    just to let you know that you should also check out the paypal code here

    https://github.com/paypal/pdt-code-samples/blob/master/paypal_pdt.php

  • Brieneke

    Torleif, Thank you for your clear article :) finally managed to get it to work! I had some trouble getting payment data (parameters) back to my success/cancel view. For others who have the same problem:

    For me it turned out to be the hidden input field ‘return’ and ‘cancel_return’. They returned without the parameters and where overwriting the ‘Retour-URL’ set in paypal that had the parameters. So leave them out of your form. Hopefully it works for you too! :)

    • http://www.geekality.net/ Torleif Berger

      If I understand you correctly, your return URL shouldn’t have *any* parameters in them, neither in your form or in paypal. There are data-fields for that.

  • Vik

    Great tutorial. Helped alot and saved so much of my time.
    Thanks !!

  • Pingback: The care, feeding, and breeding of PayPal on your web site | API

  • Vedran Brnjetić

    Oh thank you man so much, Very helpful.

    • Vedran Brnjetić

      You might just clear up which URL to use once we are happy with testing and want to go live.
      ” CURLOPT_URL => ‘https://www.sandbox.paypal.com/cgi-bin/webscr’ ”
      I believe you just ommit the sandbox part, but it should be explicitly written somewhere.

  • Gary

    Can we still use the hidden $custom, I am having terrible trouble getting it to work on my main paypal but worked ok in the test version

    • http://www.geekality.net/ Torleif Berger

      You tell me. Haven’t worked with this for several years now. Seems to still work for a lot of people, but try see if they have updated their APIs or something.

      • Gary

        It used to work for me on my old server… Thanks
        but I had a crsf protection that stoped it on the new I find out today in the codeigniter part, but its still not getting through now I have disabled it. So I must have changed something or they have that stops it working.