PayPal subscription payments and recurring IPN handling
I need advice on PayPal subscription payments IPN handling. I have written an IPN handler/listener based on PayPal code samples. The listener copies the IPN message back to PayPal preceded by cmd=_notify-validate. I can setup a subscription with no problems, ie, user enters their details and this, together with their order information is passed to PayPal where they log in to their account and agree subscription. On successful response from PayPal the order is confirmed and my database updated. The problem I am having is the recurring payment notification. I have set up the subscriptions to occur daily via the PayPal Sandbox and each time PayPal advises the customer payment pending the customer logs in to their PayPal account and accepts payment, which results in another IPN confirming payment complete. I am posting back the IPN messages preceded by the validate request and receiving a null response from PayPal Sandbox. I am expecting to receive “VERIFIED” or “INVALID” as per PayPal documentation? However, the PayPal response to the returned message is “” or null? The IPN validation code looks like this and uses “https://www.sandbox.paypal.com/cgi-bin/webscr” as the URL:
$url_parsed=parse_url($this->paypal_url);
// generate the post string from the _POST vars and load the _POST vars into an array
$post_string = "cmd=_notify-validate"; // start IPN response with validate command
foreach ($_POST as $field=>$value) {
$post_string .= '&';
$this->ipn_data["$field"] = $value;
$post_string .= $field.'='.urlencode(stripslashes($value));
}
// open the connection to PayPal
$fp = fsockopen($url_parsed[host],443,$err_num,$err_str,30);
if(!$fp) {
// could not open the connection. If logging is on, log the error message
$this->last_error = "fsockopen error no. $errnum: $errstr";
$this->log_ipn_results(false);
return false;
} else {
// Post the data back to PayPal
fputs($fp, "POST $url_parsed[path] HTTPS/1.1rn");
fputs($fp, "Host: $url_parsed[host]rn");
fputs($fp, "Content-type: application/x-www-form-urlencodedrn");
fputs($fp, "Content-length: ".strlen($post_string)."rn");
fputs($fp, "Connection: closernrn");
fputs($fp, $post_string . "rnrn");
// loop through the response from the server and append to variable
while(!feof($fp)) {
$this->ipn_response .= fgets($fp, 1024);
}
fclose($fp); // close connection
/* PayPal sends a single word back, which is VERIFIED if the message originated with PayPal
or INVALID if there is any discrepancy with what was originally sent */
if (strcmp ("INVALID", $this->ipn_response) != 0) {
// The above is a work around to address null response! For now!
// Valid IPN transaction.
$this->log_ipn_results(true);
return true;
} else {
// Invalid IPN transaction. Check the log for details.
$this->last_error = 'IPN Validation Failed.';
$this->log_ipn_results(false);
return false;
}
I have tested the timeout and believe the process is well within the time limit of 30 seconds, and confirmed the structure of the $post_string replicates the original message with cmd at the start. The only other issue I can think of is the return posting of the IPN vars is sent from a page secured by a SSL certificate? Regardless, unless I am missing something I do not believe that the PayPal Sandbox is actually responding hence null result? Any advice or guidance would be greatly appreciated as I am relying on multiple daily subscription payment periods to test this via Sandbox.
Don't strip slashes from the values when you post back the parameters for verification of the transaction. They must be posted back as received.
I implemented PHP Curl handler as follows:
<?php
function validate_ipn() {
// CONFIG: Enable debug mode. This means we'll log requests into 'ipn.log' in the same directory.
// Especially useful if you encounter network errors or other intermittent problems with IPN (validation).
// Set this to 0 once you go live or don't require logging.
define("DEBUG", 1);
define("LOG_FILE", "../log/ipn.log");
// Set to 0 once you're ready to go live
define("USE_SANDBOX", 1);
// Read POST data
// reading posted data 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";
}
// Post IPN data back to PayPal to validate the IPN data is genuine
// Without this step anyone can fake IPN data
if (USE_SANDBOX == true) {
$paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
} else {
$paypal_url = "https://www.paypal.com/cgi-bin/webscr";
}
$ch = curl_init($paypal_url);
if ($ch == FALSE) {
return FALSE;
}
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);
if (DEBUG == true) {
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
}
// CONFIG: Optional proxy configuration
//curl_setopt($ch, CURLOPT_PROXY, $proxy);
//curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
// Set TCP timeout to 30 seconds
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
// CONFIG: Please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set the directory path
// of the certificate as shown below. Ensure the file is readable by the webserver.
// This is mandatory for some environments.
//$cert = __DIR__ . "./cacert.pem";
//curl_setopt($ch, CURLOPT_CAINFO, $cert);
$res = curl_exec($ch);
if (curl_errno($ch) != 0) { // cURL error
if (DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Can't connect to PayPal to validate IPN message: " . curl_error($ch) . PHP_EOL, 3, LOG_FILE);
}
curl_close($ch);
exit;
} else {
// Log the entire HTTP response if debug is switched on.
if (DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "HTTP request of validation request:". curl_getinfo($ch, CURLINFO_HEADER_OUT) ." for IPN payload: $req" . PHP_EOL, 3, LOG_FILE);
error_log(date('[Y-m-d H:i e] '). "HTTP response of validation request: $res" . PHP_EOL, 3, LOG_FILE);
// Split response headers and payload
list($headers, $res) = explode("rnrn", $res, 2);
}
curl_close($ch);
}
// Inspect IPN validation result and act accordingly
if (strcmp ($res, "VERIFIED") == 0) {
if (DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Verified IPN: $req ". PHP_EOL, 3, LOG_FILE);
}
return true;
} else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
// Add business logic here which deals with invalid IPN messages
if (DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Invalid IPN: $req" . PHP_EOL, 3, LOG_FILE);
}
return false;
}
}
?>
and it worked!
链接地址: http://www.djcxy.com/p/27868.html上一篇: 贝宝定期付款网关修改订阅
下一篇: PayPal订购付款和定期IPN处理