Symfony的IPN Paypal 3

I work on a website based on Symfony 3 and PayPal API.

I tried to use IPN outside Symfony and it worked well.

Now I try to implements this into my controller and nothing happens.

This code comes from the Paypal's Github.

public function paypalAction(Request $request){
  // STEP 1: Read POST data

  // reading posted data from directly from $_POST causes serialization
  // issues with array data in POST
  // reading raw POST data from input stream instead.
  $raw_post_data = $request->getContent();
  $raw_post_array = explode('&', $raw_post_data);
  $myPost = array();
  foreach ($raw_post_array as $keyval) {
    $keyval = explode ('=', $keyval);
    if (count($keyval) == 2)
       $myPost[$keyval[0]] = urldecode($keyval[1]);
  }
  // read the post from PayPal system and add 'cmd'
  $req = 'cmd=_notify-validate';
  if(function_exists('get_magic_quotes_gpc')) {
     $get_magic_quotes_exists = true;
  }
  foreach ($myPost as $key => $value) {
     if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
          $value = urlencode(stripslashes($value));
     } else {
          $value = urlencode($value);
     }
     $req .= "&$key=$value";
  }


  // STEP 2: Post IPN data back to paypal to validate

  $ch = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
  curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
  curl_setopt($ch, CURLOPT_POST, 1);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
  curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));

  // In wamp like environments that do not come bundled with root authority certificates,
  // please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set the directory path
  // of the certificate as shown below.
  // curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__) . '/cacert.pem');
  if( !($res = curl_exec($ch)) ) {
      // error_log("Got " . curl_error($ch) . " when processing IPN data");
      curl_close($ch);
      exit;
  }
  curl_close($ch);


  // STEP 3: Inspect IPN validation result and act accordingly

  if (strcmp ($res, "VERIFIED") == 0) {
      // check whether the payment_status is Completed
      // check that txn_id has not been previously processed
      // check that receiver_email is your Primary PayPal email
      // check that payment_amount/payment_currency are correct
      // process payment

      // assign posted variables to local variables

      $item_name = $_POST['item_name'];
      $item_number = $_POST['item_number'];
      $cart_item = $_POST['num_cart_items'];
      $payment_status = $_POST['payment_status'];
      $payment_amount = $_POST['mc_gross'];
      $payment_currency = $_POST['mc_currency'];
      $txn_id = $_POST['txn_id'];
      $receiver_email = $_POST['receiver_email'];
      $payer_email = $_POST['payer_email'];

      // <---- HERE you can do your INSERT to the database
      return new Response("Paypal worked !");
  } else if (strcmp ($res, "INVALID") == 0) {
      // log for manual investigation
      return new Response("Paypal didn't worked !");
  }
}

Sorry but I am not yet able to post as a comment which I feel this should really be. Few things I wanted to mention, first please check your logs (symfony3: /{project}/var/log/dev.log (or prod.log in prod) and let us know anything related to your requests.

Second, are the IPNs being sent and where to? Something people overlook is that they will run a symfony project locally and forget that paypal can't send it's ipn to your local host... If you want to do this you should use something like ngrok to tunnel your localhost and then have it sent to the tunnel address which is exposing your localhost

Third, I have just finished a project which integrated paypal and the Orderly IPN Bundle was a mega time saver. https://github.com/orderly/symfony2-paypal-ipn Sure receiving and logging the IPN is easy, but Orderly will do a lot more than that - what we should be doing but didn't think about - and it's very easy to integrate. Although, please note Orderly IPN is no longer maintained so if you choose to take this option you may want to refer to these pull requests https://github.com/orderly/symfony2-paypal-ipn/pull/38/files https://github.com/orderly/symfony2-paypal-ipn/pull/42/files (although maybe don't just set 255 to everyone and use some initiative)

Hope this helps in some way.

Oh, PS. Something I ran into which you may - PayPal by default was sending its IPNs as windows-1252 but my database was in UTF-8. Took me a while to catch on to this because no issues were causes until someone with ü in their name made an order. To change this https://simple-membership-plugin.com/setting-utf-8-formatting-for-your-paypal-ipn-messages/

edit: in symfony you should also be passing Request $request into paypalAction instead of using $_GET or $_POST although they will work.

You cannot use file_get_contents('php://input'); because the Request object used it before.

use Symfony\Component\HttpFoundation\Request;

...

public function paypalAction(Request $request)
{
    $raw_post_data = $request->getContent();
    ...
}

Well, I went further into this and I noticed that the problem comes from the PayPal buttons/forms that I use.

Here's the code of a "Buy Now" button which works perfectly with IPN and my controller :

 <form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post">
  <input type='hidden' value="5" name="amount" />
  <input name="currency_code" type="hidden" value="EUR" />
  <input name="shipping" type="hidden" value="0.00" />
  <input name="tax" type="hidden" value="0.00" />
  <input name="return" type="hidden" value="http://www.YOURDOMAIN.com/valid/paiement" />
  <input name="cancel_return" type="hidden" value="http://www.YOURDOMAIN.com/cancel/paiement" />
  <input name="notify_url" type="hidden" value="http://www.YOURDOMAIN.com/validation/paiement" />
  <input name="cmd" type="hidden" value="_xclick" />
  <input name="business" type="hidden" value="YOURMAIL@YOURDOMAIN.com" />
  <input name="item_name" type="hidden" value="A kind of book" />
  <input name="no_note" type="hidden" value="1" />
  <input name="lc" type="hidden" value="FR" />
  <input name="bn" type="hidden" value="PP-BuyNowBF" />
  <input name="custom" type="hidden" value="4" />
  <input alt="Effectuez vos paiements via PayPal : une solution rapide, gratuite et sécurisée" name="submit" src="https://www.paypal.com/fr_FR/FR/i/btn/btn_buynow_LG.gif" type="image" /><img src="https://www.paypal.com/fr_FR/i/scr/pixel.gif" border="0" alt="" width="1" height="1" />
</form>

And this is a "Add to cart" button which doesn't work and gives me no callback from PayPal IPN. I get an empty response.

 <form target="paypal" action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post">
  <input type="hidden" name="cmd" value="_s-xclick">
  <input type="hidden" name="hosted_button_id" value="YOURID">
  <input name="return" type="hidden" value="http://www.YOURDOMAIN.com/valid/paiement" />
  <input name="cancel_return" type="hidden" value="http://www.YOURDOMAIN.com/cancel/paiement" />
  <input name="notify_url" type="hidden" value="http://www.YOURDOMAIN.com/validation/paiement" />
  <input type="image" src="https://www.sandbox.paypal.com/fr_FR/FR/i/btn/btn_cart_LG.gif" border="0" name="submit" alt="PayPal, le réflexe sécurité pour payer en ligne">
  <img alt="" border="0" src="https://www.sandbox.paypal.com/fr_FR/i/scr/pixel.gif" width="1" height="1">
</form>

I don't understand why the IPN works differently depending of the buttons I use.

Note : In any of these cases, the IPN Simulator works perfectly.