I am working with Drupal 7 & have implemented paypal by coding at one place where my user is suppose to register by paying at paypal & when he returns back I have to register him in callback function if payment is verified.
My callback function is simply not working & all the variables values which I am suppose to get in callback is also returning me false...
Here is my code --
This one I am using to send user to paypal with values--
function user_type_register_form_submit($form, &$form_state){
global $base_url;
$username = $form_state['values']['name'];
$user_email = $form_state['values']['mail'];
$user_type = $form_state['values']['user_type'];
$user_first_name = $form_state['values']['field_first_name']['und'][0]['value'];
$user_last_name = $form_state['values']['field_last_name']['und'][0]['value'];
$user_company_name = $form_state['values']['field_company_name']['und'][0]['value'];
$user_address_line_1 = $form_state['values']['field_address_line_1']['und'][0]['value'];
$user_address_line_2 = $form_state['values']['field_address_line_2']['und'][0]['value'];
$user_city = $form_state['values']['field_user_city']['und'][0]['value'];
$user_state = $form_state['values']['field_user_state']['und'][0]['value'];
$user_zip = $form_state['values']['field_user_zip']['und'][0]['value'];
$user_phone_number = $form_state['values']['field_phone_number_']['und'][0]['value'];
$user_mobile_number = $form_state['values']['field_mobile_number_']['und'][0]['value'];
$user_fax_number = $form_state['values']['field_fax_number_']['und'][0]['value'];
$paypal = array();
$paypal['cmd'] = '_xclick';
$paypal['business'] = 'rajeev_1359782736_biz#gmail.com';
$paypal['page_style'] = 'Primary';
$paypal['bn'] = 'PP-DonationsBF';
$paypal['item_name'] = 'Membership';
$paypal['currency_code'] = 'USD';
$paypal['no_shipping'] = '1';
$paypal['tax'] = '0';
$paypal['lc'] = 'US';
$paypal['rm'] = '1';
$paypal['return'] = $base_url.'/?q=paypal/payment';
$paypal['cancel_return'] = $base_url.'/?q=user/register';
$paypal['uname'] = $username;
$paypal['email'] = $user_email;
$paypal['user_type'] = $user_type;
$paypal['first_name'] = $user_first_name;
$paypal['last_name'] = $user_last_name;
$paypal['comp_name'] = $user_company_name;
$paypal['address1'] = $user_address_line_1;
$paypal['address2'] = $user_address_line_2;
$paypal['city'] = $user_city;
$paypal['state'] = $user_state;
$paypal['zip'] = $user_zip;
$paypal['phone'] = $user_phone_number;
$paypal['mobile'] = $user_mobile_number;
$paypal['fax'] = $user_fax_number;
switch($user_type){
case '0':
dpm("General");
$membership_price = 50;
$paypal['amount'] = $membership_price;
$paypal['item_number'] = 1;
$query = http_build_query($paypal, '', '&');
$form_state['redirect'] = 'https://www.sandbox.paypal.com/cgi-bin/webscr?' .$query;
break;
case '1':
dpm("Student");
$membership_price = 50;
$paypal['amount'] = $membership_price;
$paypal['item_number'] = 2;
$query = http_build_query($paypal, '', '&');
$form_state['redirect'] = 'https://www.sandbox.paypal.com/cgi-bin/webscr?' .$query;
break;
case '2':
dpm("Government");
$membership_price = 50;
$paypal['amount'] = $membership_price;
$paypal['item_number'] = 3;
$query = http_build_query($paypal, '', '&');
$form_state['redirect'] = 'https://www.sandbox.paypal.com/cgi-bin/webscr?' .$query;
break;
}
}
I debugged the query which sending user to paypal & it's look like --
cmd=_xclick&business=rajeev_1359782736_biz%40gmail.com&page_style=Primary&bn=PP-DonationsBF&item_name=Membership¤cy_code=USD&no_shipping=1&tax=0&lc=US&rm=1&return=http%3A%2F%2Fctaep-test.kr001.us%2F%3Fq%3Dpaypal%2Fpayment&cancel_return=http%3A%2F%2Fctaep-test.kr001.us%2F%3Fq%3Duser%2Fregister&uname=admin_list&email=rajeevkr.dei%40gmail.com&user_type=0&first_name=Rajeev&last_name=Kumar&comp_name=&address1=sector+27&address2=&city=noida&state=ky&zip=201301&phone=9650361380&mobile=&fax=&amount=50&item_number=1
This function I am using to receive the callback --
function paypal_payment_paypal_ipn_callback($vars = array()){
header("Content-type: text/html");
header("Expires: Wed, 29 Jan 1975 04:15:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache");
watchdog('paypal', '1');
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
watchdog('paypal', '2');
// post back to PayPal system to validate
$header = '';
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Host: www.sandbox.paypal.com\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
//Added - new
// 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 = file_get_contents('php://input');
$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);
//Added end
if (!$fp) {
watchdog('paypal', 'HTTP error');
} else {
fputs ($fp, $header . $req);
watchdog('paypal', $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
watchdog('paypal', '3');
//dpm($res);
watchdog('pay_status',$res);
if (strcmp ($res, "VERIFIED") == 0) {
//if (strcmp ($res, "INVALID") == 0) {
// assign posted variables to local variables
//$txn_id = $_POST['txn_id'];
watchdog('paypal', '4');
//Information about you:
$receiver_email = $_POST['business'];
//Information about the transaction:
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$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'];
dpm($receiver_email);
//Information about your buyer:
$user_email = $_POST['email'];
$user_name = $_POST['uname'];
$first_name = $_POST['first_name'];
$last_name = $_POST['last_name'];
$company_name = $_POST['comp_name'];
$address_address_line_1 = $_POST['address1'];
$address_address_line_2 = $_POST['address2'];
$address_city = $_POST['city'];
$address_state = $_POST['state'];
$address_zip = $_POST['zip'];
$user_phone = $_POST['phone'];
$user_mobile = $_POST['mobile'];
$user_fax = $_POST['fax'];
//Information about the payment:
$membership_type = $_POST['item_name'];
//Other information about the transaction:
//watchdog('paypal', '4');
// check 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
$password = user_password(8);
$fields = array(
'name' => $user_name,
'mail' => $user_email,
'pass' => $password,
'status' => 1,
'init' => $user_email,
'roles' => array(
DRUPAL_AUTHENTICATED_RID => $membership_type,),
);
// user_save('', $fields);
$account = user_save('', $fields);
dpm("user registered");
}
else if (stripos($res, "VERIFIED") !== false) {
watchdog('paypal', 'INVALID');
// log for manual investigation
}
}
fclose ($fp);
}
return 'Callback Complete';
}
Here is my watchdog errors --
inside-callback : 1
Notice: Undefined index: item_name in paypal_payment_paypal_ipn_callback()
Notice: Undefined index: item_number in paypal_payment_paypal_ipn_callback()
Notice: Undefined index: payment_status in paypal_payment_paypal_ipn_callback()
Notice: Undefined index: mc_gross in paypal_payment_paypal_ipn_callback()
Notice: Undefined index: mc_currency in paypal_payment_paypal_ipn_callback()
Notice: Undefined index: txn_id in paypal_payment_paypal_ipn_callback()
Notice: Undefined index: receiver_email in paypal_payment_paypal_ipn_callback()
Notice: Undefined index: payer_email in paypal_payment_paypal_ipn_callback()
inside-invalid : 4
Although PayPal extended the support for HTTP1.0 until May 1, 2013 they may have updated the Sandbox already for HTTP1.1 support only.
https://www.x.com/content/bulletin-ipn-and-pdt-scripts-and-http-1-1
If this is true you will need to update your callback script as follows.
// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.1\r\n"; // HTTP1.1 Update
$header .= "Content-Length: " . strlen($req) . "\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Host: www.sandbox.paypal.com\r\n"; // Sandbox Host
//$header .= "Host: ipnpb.paypal.com\r\n"; // Live Host
$header .= "Connection: close\r\n\r\n";
For further compatibility
https://ppmts.custhelp.com/app/answers/detail/a_id/926/
1) Your php script should also trim the IPN validation response.
Modify your script:
//From:
if (strcmp ($res, "VERIFIED") == 0) {
..
else if (strcmp ($res, "INVALID") == 0) {
//To:
if (strcmp (trim($res), "VERIFIED") == 0) {
..
else if (strcmp (trim($res), "INVALID") == 0) {
2) In php make sure the last line of your header includes double end-of-line markers: \r\n\r\n as in the example above: $header .="Connection: close\r\n\r\n";
3) In php make sure to open the socket connection to the same host declared in the header. As your header is declaring the host as
$header .="Host: ipnpb.paypal.com\r\n";
You should open the connection to the same host:
$fp = fsockopen ('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30);
The problem is here:
$paypal['return'] = $base_url.'/?q=paypal/payment';
What you have defined there is where the user ends up after making payment; and unless you have enabled PDT, it won't pass the transaction identifier either.
You need to use the notify_url to register an IPN handler:
$paypal['notify_url'] = $base_url.'/?q=paypal/payment';
Related
I have been at this for 3 days now and still can't get it to work.
What I want to do is to get PayPal response from the IPN listener so that I can modify my database accordingly, but no matter what I do, it just won't work. I have already done the following in my PayPal Sandbox account:
Enabled Auto Return
Set Auto Return URL ('paypal/success')
Enabled Payment Data Transfer (PDT)
Enabled IPN message reception
Set IPN URL ('paypal/ipn')
The redirect to Auto Return URL works fine and I receive the payment data in success page, but the IPN won't process for reasons beyond me. A quick look at the IPN history on my PayPal profile shows that the messages are being sent, but I don't receive them at my end.
Here is my current IPN listener: Paypal/ipn
public function ipn() {
//Build the data to post back to Paypal
$postback = 'cmd=_notify-validate';
// go through each of the posted vars and add them to the postback variable
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$postback .= "&$key=$value";
}
// build the header string to post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: www.sandbox.paypal.com\r\n";//or www.sandbox.paypal.com
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($postback) . "\r\n\r\n";
// Send to paypal or the sandbox depending on whether you're live or developing
// comment out one of the following lines
$fp = fsockopen ('www.sandbox.paypal.com', 443, $errno, $errstr, 30);//open the connection
//$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);
// or use port 443 for an SSL connection
//$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
if ( ! $fp ) {
// HTTP ERROR Failed to connect
$message = 'HTTP ERROR Failed to connect!';
$this->email_me($message);
} else { // if we've connected OK
fputs ($fp, $header . $postback); //post the data back
while ( ! feof($fp) ) {
$response = fgets ($fp, 1024);
if (strcmp (trim($response), "VERIFIED") == 0) { //It's verified
//read the payment details and the account holder
$payment_status = $_POST['payment_status'];
$receiver_email = urldecode($_POST['receiver_email']);
// further checks
if( ($payment_status == 'Completed') && ($receiver_email == $this->business_email) ) {
$message = 'IPN verified successfully!';
$this->email_me($message);
// Insert the transaction data in the database
$this->product_model->insert_transaction_details($_POST);
} else {
$message = 'Payment could not be verified!';
$this->email_me($message);
}
} else {
$message = 'IPN invalid!';
$this->email_me($message);
}
}
}
}
Can someone point me in the right direction please?
Also, is there anyway I can check the IPN response ("VERIFIED" or "INVALID") on chrome debugger or in my PayPal Sandbox dashboard? I can see delivery status in my dashboard but it doesn't say 'Verified' or 'Invalid' anywhere.
I found the solution! I wrote the IPN handler inside a controller that allows access to users who are logged in as admin. Apparently, the IPN method was denying access to PayPal to verify the transaction. I figured this out and wrote the IPN method in a different controller and everything worked perfectly.
I also changed my IPN handler to this code (although the original might still work... i didn't try it):
class Paypal_ipn extends MY_Controller {
public function __construct() {
parent::__construct();
$this->load->model('product_model');
$this->sandbox = $this->config->item('sandbox');
$this->paypal_host = $this->config->item('paypal_host');
$this->paypal_url = $this->config->item('paypal_url');
$this->business_email = $this->config->item('business');
}
public function ipn() {
// 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 = file_get_contents('php://input');
$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($this->paypal_url);
$headers = array(
'POST /cgi-bin/webscr HTTP/1.1',
'Host: ' . $this->paypal_host,
'Content-Type: application/x-www-form-urlencoded; charset=utf-8',
'Content-Length: ' . strlen($req),
'User-Agent: PayPal-IPN-VerificationScript',
'Connection: Close'
);
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, $headers);
if( !($res = curl_exec($ch)) ) {
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'];
$payment_status = $_POST['payment_status'];
$payment_amount = $_POST['mc_gross'];
$payment_currency = $_POST['mc_currency'];
$txn_id = $_POST['txn_id'];
$receiver_email = urldecode($_POST['receiver_email']);
$payer_email = $_POST['payer_email'];
$school_id = $_POST['custom'];
// further checks
if($payment_status == 'Completed') {
$message = 'IPN verified successfully!';
$this->email_developer($message);
// Insert the transaction data in the database
$this->product_model->insert_transaction_details($_POST);
} else {
$message = 'Payment could not be verified!';
$this->email_developer($message);
}
} else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
$message = 'IPN Invalid!';
$this->email_developer($message);
}
}
}
For those that might experience my predicament, ensure you also do the following:
If you enabled Cross Site Request Forgery (CSRF), ensure the IPN listener/handler is whitelisted, else IPN message will fail (Error 403 in PayPal IPN history).
To be sure your IPN listener is working well, run it as a URL and see the response. If there is any error, it won't work. For response, trying echoing "Verified" or "Invalid".
Use the PayPal IPN Simulator to test the process. Include a procedure that will submit information to the database upon success.
I hope it helps someone.
use php://input instead of $_POST
reson described here in details : PHP "php://input" vs $_POST
also paypal has documentation for implementing IPN Listener and its in php as well
Paypal tutorial
I am implementing ipnlistner inside my project. I set the ipn url inside my paypal account. But i am not getting all the transaction ipn responses to that url. But when i am checking ipn history in my account it displays that all the ipn has been sent. For example yesterday it is showing all 112 ipn had sent. but i am getting only 7 in my db. Here is my code for ipn listner. I am inserting all the data i am getting in the db at the first line only.
<?php
namespace App\Http\Controllers;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Response;
class PaypalIPN extends Controller {
private $use_sandbox = null;
const VALID = 'VERIFIED';
const INVALID = 'INVALID';
public function useSandbox() {
$this->use_sandbox = env( 'USE_SANDBOX' );
}
public function getPaypalUri() {
if ( $this->use_sandbox ) {
return env( 'SANDBOX_VERIFY_URI' );
} else {
return env( 'VERIFY_URI' );
}
}
public function verifyIPN() {
try {
DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( $_POST, true ) ] );
if ( ! count( $_POST ) ) {
throw new \Exception( "Missing POST Data" );
}
$raw_post_data = file_get_contents( 'php://input' );
$raw_post_array = explode( '&', $raw_post_data );
$myPost = array();
foreach ( $raw_post_array as $keyval ) {
$keyval = explode( '=', $keyval );
if ( count( $keyval ) == 2 ) {
// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
if ( $keyval[0] === 'payment_date' ) {
if ( substr_count( $keyval[1], '+' ) === 1 ) {
$keyval[1] = str_replace( '+', '%2B', $keyval[1] );
}
}
$myPost[ $keyval[0] ] = urldecode( $keyval[1] );
}
}
// Build the body of the verification post request, adding the _notify-validate command.
$req = 'cmd=_notify-validate';
$get_magic_quotes_exists = false;
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";
}
// Use the sandbox endpoint during testing.
$this->useSandbox();
// Post the data back to PayPal, using curl. Throw exceptions if errors occur.
$ch = curl_init( $this->getPaypalUri() );
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_SSLVERSION, 6 );
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_CONNECTTIMEOUT, 30 );
curl_setopt( $ch, CURLOPT_HTTPHEADER, array( 'Connection: Close' ) );
$res = curl_exec( $ch );
if ( ! ( $res ) ) {
$errno = curl_errno( $ch );
$errstr = curl_error( $ch );
curl_close( $ch );
throw new \Exception( "cURL error: [$errno] $errstr" );
}
$info = curl_getinfo( $ch );
$http_code = $info['http_code'];
if ( $http_code != 200 ) {
throw new \Exception( "PayPal responded with http code $http_code" );
}
curl_close( $ch );
// Check if PayPal verifies the IPN data, and if so, return true.
if ( $res == self::VALID ) {
DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( $res, true ) ] );
} else {
DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( $res, true ) ] );
}
// Reply with an empty 200 response to indicate to paypal the IPN was received correctly.
header( "HTTP/1.1 200 OK" );
}catch (\Exception $e){
DB::table( 'ipn_response' )->insert( [ 'data' => json_encode( ["Exception"=>$e->getMessage()]) ] );
}
}
}
I am veryfying IPN on this url
https://ipnpb.paypal.com/cgi-bin/webscr
and my ipn url is
https://www.myproject.com/api/verify-ipn
Note: previously i created some paypal buttons on this account, i am not getting the ipn responses for that button payments
Please help or guide what to do for this..
Please create below function in your controller and at your Paypal account add into IPN URL and check every hit of IPN.
<?php
function paymentIpnlistener(){
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
// If testing on Sandbox use:
$header .= "Host: www.sandbox.paypal.com:443\r\n";
//$header .= "Host: ipnpb.paypal.com:443\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
if (strpos('ssl://www.sandbox.paypal.com', 'sandbox') !== FALSE && function_exists('openssl_open')) {
$fp = fsockopen('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
}
else{
// The old "normal" way of validating an IPN.
$fp = fsockopen('ssl://www.sandbox.paypal.com', 80, $errno, $errstr, 30);
}
// If testing on Sandbox use:
//$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
//$fp = fsockopen ('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30);
// assign posted variables to local variables
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$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'];
if (!$fp) {
// HTTP ERROR
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0) {
// check 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
// Add your live email address
$mail_From = "From: me#mybiz.com";
// Add your live email address
$mail_To = "raghbendra.nayak#systematixindia.com";
$mail_Subject = "VERIFIED IPN";
$mail_Body = $req;
foreach ($_POST as $key => $value){
$emailtext .= $key . " = " .$value ."\n\n";
}
mail($mail_To, $mail_Subject, $emailtext . "\n\n" . $mail_Body, $mail_From);
}
else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
$mail_From = "From: me#mybiz.com";
$mail_To = "raghbendra.nayak#systematixindia.com";
$mail_Subject = "INVALID IPN";
$mail_Body = $req;
foreach ($_POST as $key => $value){
$emailtext .= $key . " = " .$value ."\n\n";
}
mail($mail_To, $mail_Subject, $emailtext . "\n\n" . $mail_Body, $mail_From);
}
} // while end
fclose ($fp);
}
}
?>
Above function will send email to you for every time whenever the IPN listener triggered. Simply if its working then you can manage it as per your requirement. Try and let me know.
It sounds like there may be an error in your code. Further debugging would be necessary. When I run into something like this I usually check the error logs to see what's going on.
PayPal will keep trying to send requests until it receives a 200 OK HTTP status response with no content in the body. If PayPal is showing successful receipt by the endpoint, then the place where the database is failing to enter data is probably just before your header function is being called.
My next move to debug would be to try to figure out if my database inserts were failing due to some kind of data integrity error/warning.
It might be helpful to catch DB errors and trigger some kind of non-passing response so PayPal resends until you've figured out the scripting problem.
Also:
Try to add additional logging using something like Monolog or even just error_log to get to the bottom of where your script is terminating or what is not working as expected.
I should note that I'm implementing PayPal IPN right now too and this library was difficult to use. I spun out my own with a subscriber pattern. It's not ready to go public yet but it was largely to separate out logic from verification for better testing and readability.
First of all, write a log as the first row of the web service, so you know when you get a request.
Then, somethimes you don't have all request parameters set, so maybe your sql go in error when parameters are not found.
In my paypal form, for example, I send the invoice_number. Then, when I get a request from IPN, I'll check for invoice_number to link payment with invoice.
But I get payment on the same paypal account also from other source, so in the other case invoice_number is not set.
You may write a log with all parameter found in the IPN call so you can check what is missing.
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 10 years ago.
I want to set things up so that when a user pays me through PayPal, a PHP script will be run. How can I set up this kind of notification?
It's called an IPN.
You just need to go into your settings, enable the IPN, tell it what file will be handling the call back and then write your php file.
Here's how I handle paypal ipns with php
<?php
if($_POST){
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);
if (!$fp) {
// HTTP ERROR
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0) {
// check the payment_status is Completed
// assign posted variables to local variables - NOT NECESSARY..
$receiver_email = mysql_real_escape_string($_POST['receiver_email']);
$payer_email = mysql_real_escape_string($_POST['payer_email']);
$business_id = mysql_real_escape_string($_POST['item_number']);
$payment_gross = mysql_real_escape_string($_POST['mc_gross']);
$item_name = mysql_real_escape_string($_POST['item_name']);
$currency = mysql_real_escape_string($_POST['mc_currency']);
$payment_date = mysql_real_escape_string($_POST['payment_date']);
$payment_status = mysql_real_escape_string($_POST['payment_status']);
$address_street = mysql_real_escape_string($_POST['address_street']);
$first_name = mysql_real_escape_string($_POST['first_name']);
$last_name = mysql_real_escape_string($_POST['last_name']);
$address_street = mysql_real_escape_string($_POST['address_street']);
$address_city = mysql_real_escape_string($_POST['address_city']);
$address_state = mysql_real_escape_string($_POST['address_state']);
$address_country_code = mysql_real_escape_string($_POST['address_country_code']);
$address_zip = mysql_real_escape_string($_POST['address_zip']);
$quantity = mysql_real_escape_string($_POST['quantity']);
$payment_fee = mysql_real_escape_string($_POST['mc_fee']);
$address_name = mysql_real_escape_string($_POST['address_name']);
$payment_type = mysql_real_escape_string($_POST['payment_type']);
$payer_business_name = mysql_real_escape_string($_POST['payer_business_name']);
$item_number = mysql_real_escape_string($_POST['item_number']);
$transaction_subject = mysql_real_escape_string($_POST['transaction_subject']);
$parent_txn_id = mysql_real_escape_string($_POST['parent_txn_id']);
$txn_id = mysql_real_escape_string($_POST['txn_id']);
$custom = mysql_real_escape_string($_POST['custom']);
//insert into payments table
$result = mysql_query("INSERT INTO payments (payment_date, payment_type, receiver_email, payer_email, gross_amt, quantity, currency, payment_status, paypal_fee, transaction_subject, item_name, item_number, first_name, last_name, payer_business_name, address_name, address_street,address_city,address_state,address_zip,address_country_code,parent_txn_id, txn_id, custom) VALUES ('$payment_date','$payment_type', '$receiver_email', '$payer_email', '$payment_gross','$quantity','$currency','$payment_status','$payment_fee','$transaction_subject','$item_name','$item_number','$first_name','$last_name','$payer_business_name','$address_name','$address_street','$address_city','$address_state','$address_zip','$address_country_code','$parent_txn_id','$txn_id','$custom')") or die(mysql_error());
//end of if invalid
}
else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
}
}
fclose ($fp);
}
//end of post
}
?>
PayPal offers the Instant Payment Notification system which you have to set up through your account.
Here is the link to their guide to get you started.
https://cms.paypal.com/cms_content/US/en_US/files/developer/IPNGuide.pdf
Also they offer a template for your IPN listener. Here is the one in PHP:
<?php
// read the post from PayPal system and add 'cmd'
$req = 'cmd=' . urlencode('_notify-validate');
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.paypal.com/cgi-bin/webscr');
curl_setopt($ch, CURLOPT_HEADER, 0);
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_HTTPHEADER, array('Host: www.paypal.com'));
$res = curl_exec($ch);
curl_close($ch);
// assign posted variables to local variables
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$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'];
if (strcmp ($res, "VERIFIED") == 0) {
// check 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
}
else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
}
?>
As mentioned in one of the comments in an answer below, I tried following this tutorial. So now I have the following:
The ipn.php file:
<?php
$ipn_post_data = $_POST;
$url = 'https://www.sandbox.paypal.com/cgi-bin/webscr';
// Set up request to PayPal
$request = curl_init();
curl_setopt_array($request, array
(
CURLOPT_URL => $url,
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => http_build_query(array('cmd' => '_notify-validate') + $ipn_post_data),
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);
if($status == 200 && $response == 'VERIFIED')
{
$subject = "valid";
$message = "good";
}
else
{
$subject = "invalid";
$message = "bad";
}
$to = "oshirowanen#mail.com";
$from = "me#desktop.com";
$header = 'MIME-Version: 1.0' . "\r\n";
$header .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n";
$header .= 'To: Oshirowanen <oshirowanen#mail.com>' . "\r\n";
$header .= 'From: Me <me#desktop.com>' . "\r\n";
mail($to,$subject,$message,$header);
?>
The received email:
Subject "invalid"
Message "bad"
Edit:
Now that I can see the array you've outputted, try replacing this to get rid of the PHP array error:
foreach ($_POST as $key => $value) {
if (!is_array($value)) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
else if (is_array($value)) {
$paymentArray = explode(' ', $value[0]);
$paymentCurrency = urlencode(stripslashes($paymentArray[0]));
$paymentGross = urlencode(stripslashes($paymentArray[1]));
$req .= '&mc_currency=' . $paymentCurrency . '&mc_gross=' . $paymentGross;
}
}
Here is the edited code in full:
// read the post from PayPal system and add 'cmd'
$req = 'cmd=' . urlencode('_notify-validate');
foreach ($_POST as $key => $value) {
if (!is_array($value)) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
else if (is_array($value)) {
$paymentArray = explode(' ', $value[0]);
$paymentCurrency = urlencode(stripslashes($paymentArray[0]);
$paymentGross = urlencode(stripslashes($paymentArray[1]);
$req .= '&mc_currency=' . $paymentCurrency . '&mc_gross=' . $paymentGross;
}
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.paypal.com/cgi-bin/webscr');
curl_setopt($ch, CURLOPT_HEADER, 0);
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_HTTPHEADER, array('Host: www.paypal.com'));
$res = curl_exec($ch);
curl_close($ch);
// assign posted variables to local variables
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$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'];
if (strcmp ($res, "VERIFIED") == 0) {
// check 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
}
else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
}
Check this out!
Edit: Check out the PayPal troubleshooting tips:
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/e_howto_admin_IPNTesting
The problem is that you don't check the HTTP response code, so you are intepreting the "Invalid Host header" as the PayPal response, whilst it's the web server response (for the status code 400).
If you look at the PayPal documentation, there is a PHP example which is very similar to your code, since it uses the "fsockopen", "fputs" and "fgets" functions to communicate with the PayPal server.
But if you look carefully at the remark after the "fsockopen" call, you can read:
// Process validation from PayPal
// TODO: This sample does not test the HTTP response code. All
// HTTP response codes must be handled or you should use an HTTP
// library, such as cUrl
And this is exacty your problem: you don't check that the HTTP response code is 200 (OK), before parsing the response body.
Also, using the "strtolower" function is not correct, since the real response from the PayPal server is always uppercase, as shown in the above cited example.
Even if the PayPal example uses the "fsockopen" approach, I think it should be much better to use the PHP cURL library to implement your IPN listener.
Have also a look at the following answers:
PHP cURL PayPal Sandbox
cURL or fsockopen for paypal ipn
However, if you really want to use the "fsockopen" function, you should always specify the "Host" header field in the POST request, as shown in the following snippet of code (taken from the PHP manual):
<?php
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: www.example.com\r\n";
$out .= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>
UPDATE
Here is a simple function for recursive stripslashes/urlencoding:
<html>
<body>
<pre>
<?
$post = Array (
"transaction" => Array("USD 20.00"),
"payment_request_date" => "Sun Aug '05 08:49:20 PDT 2012",
"return_url" => "http://000.000.000.000/success.php"
);
echo "before myUrlencode...\n";
print_r($post);
function myUrlencode($post) {
foreach ($post as $key => $val) {
if (is_array($val)) {
$post[$key] = myUrlencode($val);
} else {
$post[$key] = urlencode(stripslashes($val));
}
}
return($post);
}
echo "\nafter myUrlencode...\n";
print_r(myUrlencode($post));
?>
</pre>
</body>
</html>
Got it working using the basic sample code 4b,
Cleared $ipnNotificationUrl = ""; from the basic sample code as I had a value in there which I added myself,
Created a seller account instead of a business pro account in sandbox,
Set the seller account to enable the ipn url,
Used the following PHP 5.2 sample code for the ipn listener
Added the 2 lines into the listener, as described here, the 2 lines can be seen below:
Downloaded the cacert.pem certificate to my server from here and put it in the same directory as the ipn listener:
The 2 lines mentioned in point 6:
CURLOPT_SSL_VERIFYPEER => TRUE,
CURLOPT_CAINFO => 'cacert.pem',
I have no idea why the sandbox business pro account does not let me set an ipn url, but the seller account does.
These links may resolve your problem,
Paypal: Invalid IPN problem
http://www.webmasterworld.com/ecommerce/4292847.htm
Paypal sandbox IPN return INVALID
I am not sure what is exactly wrong right now with your code, but I was strugling wuth the same while ago and my fixes was to add HOST in the header and host have to be www.paypal.com. I used fsockopen method and work fine now.
In Curl I had a problem before with ssl. And solution was to put those lines:
curl_setopt($curl, CURLOPT_COOKIEJAR, dirname(__FILE__) . "/cookies.txt");
curl_setopt($curl, CURLOPT_COOKIEFILE, dirname(__FILE__) . "/cookies.txt");
where of course file cookies.txt have to exists.
and more over I had to run one connection to page to get session data and later send post data.
Below is a header what is working fine for me with fsockopen method
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: www.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
It's a problem with the + character, it often get wrongly fetched so I made that workaround, and it worked for me.
payment_data = Sat Jun 04 2016 15:11:16 GMT+0200 (CEST)
foreach ($_POST as $key => $value) {
if($key !== "payment_date"){
$req .= '&' . $key . '=' . rawurlencode(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
}else{
$req .= '&' . $key . '=' . rawurlencode(str_replace(array('GMT '),array('GMT+'),$value));
}}
Here's how to avoid these errors...
foreach ($_POST as $key => $value) {
if ($key=='transaction')
foreach ($value as $key2=>$value2) {
$value['transaction'][$key2] = urlencode(stripslashes($value2));
}
else {
$value = urlencode(stripslashes($value));
}
$req .= "&$key=$value";
}
Hours of hair pulling until I saw Izudin's answer. He's right..The + in the date wasn't being transferred. Just to test, I removed it from the pre-populated field in the simulator and got a Verified at last.
I finally found an updated (August 5, 2016) working answer to this query. You can use this code as your final IPN for Sandbox or Live. With the following consideration:
Be sure to place your IPN listener to ->My selling tools -> instant payment notifications Section.
Do not use IPN Simulator in sandbox, it will always return INVALID.
Create and Use an actual Sandbox Button, but DO NOT put your IPN listener to RETURN PAGE that says "Take customers to this URL when they finish checkout".
That's all of it. I hope this will help.
And here is the working code:
<?php
$post_data = file_get_contents('php://input');
$post_array = explode('&', $post_data);
$dataFromPayPal = array();
foreach ($post_array as $keyval) {
$keyval = explode ('=', $keyval);
if (count($keyval) == 2)
$dataFromPayPal[$keyval[0]] = urldecode($keyval[1]);
}
$req = 'cmd=_notify-validate';
if(function_exists('get_magic_quotes_gpc')) {
$get_magic_quotes_exists = true;
}
foreach ($dataFromPayPal 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";
}
$ch = curl_init('https://www.sandbox.paypal.com/cgi-bin/webscr');
//use https://www.sandbox.paypal.com/cgi-bin/webscr in case you are testing this on a PayPal Sanbox environment
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_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
if( !($res = curl_exec($ch)) ) {
curl_close($ch);
exit;
}
curl_close($ch);
if (strcmp ($res, "INVALID") == 0) {
echo "INVALID";
}
else if (strcmp ($res, "VERIFIED") == 0) {
echo "VALID";
}
?>
I am using this code on my site, for paypal checkout
<?php
require('../includes/globals/config.php');
require('myCart.inc.php');
$mycart = new myCart;
$ppemail = $mycart->getPaypalEmail();
$paypal_email = $ppemail['email'];
$paypal_currency = 'USD';
//$shipping = 1.00;
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach($_POST as $key => $value){
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen('www.sandbox.paypal.com', 80, $errno, $errstr, 30);
// assign posted variables to local variables
$item_name = $_POST['item_name'];
$item_number = $_POST['item_number'];
$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'];
if (!$fp){
// HTTP ERROR
} else {
fputs($fp, $header . $req);
while(!feof($fp)) {
$res = fgets($fp, 1024);
if (strcmp($res,"VERIFIED") == 0){
//blah blah
}
else if (strcmp($res, "INVALID") == 0){
// log for manual investigation
}
}
fclose($fp);
}
?>
The problem is, after the transaction stuff, and I get redirected to a thank you page, the $_POST data doesn't appear on the form inputs, I meant, I extracted the contents of the $_POST data coming from the paypal transaction and placed the tidbits of data on each form fields..but when I clean browser cache/private data/session/cookies and do the same procedure, but this time with the
echo "<pre>",print_r($_POST),"</pre>";
the $_POST content gets printed on the page and the tidbits of $_POST are being displayed also inside the form fields..this happens when I use the print_r. Is there a workaround for this? so that the tidbits of data will immediately appear on the form fields without having to invoke the print_r() func and print the array of data somewhere on the page ?