Paypal IPN - How does it work? - php

I need a brief explanation on how Paypal IPN works. Not anything in details just the basic stuff.
How should I set my HTML variables and how should I get the data to verify the payment from Paypal? This is all I need to know and I can't find a quick explanation on this one somewhere.
If possible, show me some code lines too, would definitely help.
Thanks.

IPN is a message service that PayPal uses to send notifications about specific events, such as:
Instant payments, including Express Checkout, direct credit card
payments and authorizations (transaction payments that are authorized
but have not yet been collected)
eCheck payments and associated status, such as pending, completed, or
denied, and payments pending for other reasons, such as those being
reviewed for potential fraud
Recurring payment and subscription actions
Chargebacks, disputes, reversals, and refunds associated with a
transaction
In many cases, the action that triggers an IPN event is a user-action on your website. However, other actions can trigger IPNs. For example, your site's back-office process might invoke a PayPal API that refunds a payment, or a customer might notify PayPal of a disputed charge.
You receive and process IPN messages with a listener (sometimes called a handler). This listener is basically a web page or web application that you create on your server that is always active and had code that allows it to accept and verify IPN messages sent from PayPal, and then invoke backend services on your server, based on the information from the IPN message. The web application waits for IPNs and (typically) passes them to an administrative process that responds appropriately. PayPal provides sample code that can be modified to implement a listener that handles the IPN sent from messages PayPal. For details, see Implementing an IPN listener.
For detailed information and help please visit: PayPal Instant Payment Notification Guide
Hope this helps.

Code
I've used the c# equivalent of this many times (and the PHP version looks quite similar).
https://www.x.com/developers/PayPal/documentation-tools/code-sample/216623
<?php
//reading raw POST data from input stream. reading pot data from $_POST may cause serialization issues since POST data may contain arrays
$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_exits = true;
}
foreach ($myPost as $key => $value)
{
if($get_magic_quotes_exits == true && get_magic_quotes_gpc() == 1)
{
$value = urlencode(stripslashes($value));
}
else
{
$value = urlencode($value);
}
$req .= "&$key=$value";
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://www.paypal.com/cgi-bin/webscr');
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'));
// In wamp like environment where the root authority certificate doesn't comes in the bundle, you need
// to 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');
$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
}
?>
Overview
Basically, PayPal contacts you and you respond; this allows you to validate that it was PayPal invoking your IPN handler and not a malicious party. After that validation step, you can proceed with processing the results. As I'm sure you know, an IPN call is made after a payment occurs (and also can be configured for other events in the payment lifecycle). You can use IPN to update a system status (e.g. unlocking a purchased product).
Other Stuff
The last development URL I used for PayPal was https://www.sandbox.paypal.com/cgi-bin/webscr (probably still valid)
The IPN page/handler needs to be publicly available for PayPal to invoke.
You'll need to configure IPN notifications in the PayPal developer UI (which mainly involves giving them the URL to your IPN page)
You can send custom information to PayPal with the original transaction that PayPal will send back to the IPN handler. I believe it is passed in a field called "custom".

I found very useful (and easy to use) this PHP class:

Related

Can't refund order using Paypal API

I'm integrating PayPal using PHP and cURL and managed to create orders and capture the payments via the https://api.sandbox.paypal.com/v2/checkout/orders and
https://api.sandbox.paypal.com/v2/checkout/orders/<order_id>/capture endpoints
The orders I'm trying to refund have been successfully captured and looking at the details it shows their status is "COMPLETED" and the final_capture field is true, so the order has been placed and I can see the transaction ended successfully in the merchant account
Now I'm trying to test refunds using the capture ID I get from the capture call but it always fails with an error with the following response body
{
"error":"invalid_subject",
"error_description":"Subject Authentication failed"
}
I tracked down the problem behind the Subject Authentication failed problem being a wrong Paypal-Auth-Assertion header, that I printed and double checked multiple times already and it seems to be right. Here's the code I use to make the call
// Codeigniter function to read from $_POST
$capture_id = $this->input->post('paypal_capture_id');
$auth_1 = base64_encode("{\"alg\":\"none\"}");
$auth_2 = base64_encode("{\"payer_id\":<payer_id>,\"iss\":<client_id>}");
$auth_assertion_header = $auth_1 . "." . $auth_2 . ".";
$curlSES=curl_init();
curl_setopt($curlSES,CURLOPT_URL,"https://api.sandbox.paypal.com/v2/payments/captures/$capture_id/refund");
curl_setopt($curlSES, CURLOPT_POST, true);
curl_setopt($curlSES, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Authorization: Bearer '. $access_token,
'PayPal-Auth-Assertion:' . $auth_assertion_header
));
curl_setopt($curlSES, CURLOPT_POSTFIELDS, '{}'); // empty payload means full refund
curl_setopt($curlSES, CURLOPT_RETURNTRANSFER, true);
$result=curl_exec($curlSES);
Where payer_id and client_id are filled using a merchant id account used for the sandbox environment and the client_id is the secret id for the application provided by Paypal, the $access_token has been previously generated from a function I use in other parts of the application and it works fine.
Furthermore, if I try to make the same call using Postman (and the PayPal API explorer as well) it produces a different error, that is
{
"error": "invalid_request",
"error_description": "No permissions to set target_client_id"
}
No search result for this error is really helpful so I'm lost on what I'm doing wrong there, although it doesn't seem related to the Paypal-Auth-Assertion as it falls back to the Subject Authentication failed error if I provide a wrong value on purpose.
I was facing the same issue, when trying to refund a captured order for a partner account. It seems to me u are using the string escaped json from the example.
Try using the json_encode function like below:
$header = base64_encode(json_encode(['alg' => 'none']));
$body = base64_encode(json_encode(['payer_id' => $merchantId, 'iss' => $clientId]));
$authHeader = $header . "." . $body . ".";
I was able to refund a captured order with this $authHeader.
Greetings

PayPal NVP API - Getting Express Checkout Token

I am trying to get the express checkout token for a PayPal one time purchase integration. I get this error when trying to make the cURL request:
ACK=Failure&L_ERRORCODE0=81002&L_SHORTMESSAGE0=Unspecified%20Method&L_LONGMESSAGE0=Method%20Specified%20is%20not%20Supported&L_SEVERITYCODE0=Error1
Here is my code
<?php
if(!isset($_GET['id'])) {
header("Location: index.php");
exit();
}
//Get paypal express checkout token
$ch = curl_init("https://api-3t.sandbox.paypal.com/nvp");
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
"USER: seller-v3rm_api1.test.com",
"PWD: <snip>",
"SIGNATURE: <snip>",
"METHOD: SetExpressCheckout",
"VERSION: 93",
"PAYMENTREQUEST_0_PAYMENTACTION: SALE",
"PAYMENTREQUEST_0_AMT: 25",
"PAYMENTREQUEST_0_CURRENCYCODE: USD",
"RETURNURL: http://test/buy.php",
"CANCELURL: http://test.com",
));
$token = curl_exec($ch);
echo $token;
Am i missing something?
You aren't setting up the request string correctly. I would really recommend taking a look at this PayPal PHP SDK.
First, it will eliminate your need to even mess with this sort of code because it handles it all for you. All you would need to do is open the SetExpressCheckout.php template file that it comes with and fill out the parameters, and then the same for the other calls you might use.
Also, though, if you want you could study the code to see how it's handling the cURL request. You need to build an actual NVP string (ie. &something=like&this=example). Then that is what you send to PayPal via cURL.

Verify POST data comes from PayPal

How to verify POST data comes from PayPal ?
I am working on a store that sells some products. The payment gateway is PayPal.
Initially I set up a custom PayPal form and used the IPN responses to validate the data that is sent to me from PayPal.
Now my client has bought PayPal Advance Payment that uses PayPal PayFlow. The responses are not sent anymore through IPN (or are they?) instead they are returned by SILENT POST, basically when a transaction is perfomed on their end it is sent to a link of my choice and I process data through that link.
How do I validate the source of the POST data, so I know it is coming from PayPal and not a bad intentions user. I can not find any documentation on this. Also I want the same think when a users clicks "Cancel" button on paypal page and it is redirected to cancelation page on my website. I want that POST data source also verified.
Any thoughts on this ?
First solution than I found, also some PayPal support guy has mentioned something similar but he could not offer details as he said he is not an expert.
Basically you have to run a TRANSACTION of type INQUIRY with the received PNREF from SILENT POST, if the response returns ORIGRESULT equal to 0 then the transaction exists in PayPal database under your account.
I also send the SECURE TOKEN ID and SECURE TOKEN with the inquiry, I do not know if it helps or not, I saw this as a solution somewhere and I tried sending just TOKEN and TOKEN ID without ORIGID and sometimes it worked sometimes not.
On the developer reference from PayPal is specified that on a TRXTYPE=I (INQUIRY TRANSACTION) the ORIGID must be specified.
Code below:
//GET POST DATA FROM SILENT POST
$data = $_POST;
$result = $data['RESULT'];
$pnref = $data['PNREF'];
$secure_token = $data['SECURETOKEN'];
$secure_token_id = $data['SECURETOKENID'];
$fee_amount = $data['AMT'];
if(!isset($result) || $result != '0'){
//DO TRANSACTION FAILED OPERATIONS
exit;
}
//CHECK IF PNREF ID EXISTS ON PAYPAL
$post_data = "PARTNER=yourpartnerid"
."&VENDOR=your_vendor"
."&USER=your_user"
."&PWD=your_password"
."&SECURETOKENID=" . $secure_token_id
."&SECURETOKEN=" . $secure_token
."&ORIGID=" . $pnref
."&TRXTYPE=I"
."&VERBOSITY=HIGH";
$ch = curl_init('https://payflowpro.paypal.com');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
$resp = curl_exec($ch);
$inquiry = array();
parse_str($resp, $inquiry);
//IF ORIGRESULT is 0 then PNREF/transaction exists.
if($inquiry['ORIGRESULT'] == '0' && $inquiry['RESPMSG'] == 'Approved'){ $validated = true; }
else{ $validated = false; }
if($result != 0 || $amount != 'your_correct_fee' || $validated == false){
// DO TRANSACTION NOT VALID OR HAS FAILED OPERATIONS
exit;
}
//DO TRANSACTION SUCCESSFULL OPERATIONS
The response from a INQUIRY looks this way:
RESULT=0&PNREF=ETHPC0BBF5FB&TRANSSTATE=8&ORIGRESULT=0&ORIGPNREF=ELFPB0E766F5&RESPMSG=Approved&AVSADDR=N&AVSZIP=N&ORIGPPREF=8GT035513B296200N&CORRELATIONID=97306f6456378&SETTLE_DATE=2014-07-09 14:11:36&TRANSTIME=2014-07-09 14:11:36&FIRSTNAME=John&LASTNAME=doe&AMT=0.0
Another way of doing it is checking the IP from which the SILENT POST is coming.
I noticed all SILENT POST data comes from 173.0.81.65
$ip_address = $_SERVER['REMOTE_ADDR'];
if($ip_address != '173.0.81.65'){ exit; }
#Andrew Angel
SILENT POST and NotifyURL is not the same thing.
From the NotifyURL you can use IPN and data verification it is done very differently there because you receive different kind of post data string back.
I talked with PayPal support and they said NotifyURL and IPN is not available for PayPal Advanced Payments only for PayPal Payments Pro.
Indeed they both do the same function, but they are different things and source validation is done differently.
Hope this helps someone, waiting on opinions.

Paypal : Get a response of "txn_type= recurring payment " instead of "txn_type= recurring_payment_profile_created" for the first Time

I used to implement paypal recurring payment into my web application.where I got a response from paypal through Instant payment Notification to my (paypalipn.php) page as follows,
$payment_status = completed;
$txn_type = recurring_payment;
$paypal_txn_id = id comes here;
$parent_txn_id = id comes here;
$recurring_payment_id = id comes here;
This response I got for first time but It is expected that for first time as,
$payment_status = completed;
$txn_type = recurring_payment_profile_created;
$paypal_txn_id = id comes here;
$parent_txn_id = id comes here;
$recurring_payment_id = id comes here;
so any one point me what error may be occured or what may be the reason for this ........
Both events are typically fired when a new recurring payments profile is created. One is for the creation of the recurring payments profile, and the other is for the first payment processed against that recurring payment profile. Technically, recurring_payment_profile_created should come before recurring_payment; however, IPN is asynchronous, so the IPNs could be delivered out of order.

PayPal notify_url not being called after extensive sandbox testing

I'm designing a website for a charity board game event, wherein people can watch the event live, and can donate money to a charity (Child's Play) in order to force players to play continuously for 60 hours. Because the donations are going directly to Child's Play, I need to use the notify_url setting to pass my IPN notification URL in; we've done this marathon twice before and have had no problems, but we recently ported it over to PHP.
Now, I've been testing the site extensively for the past few months using the sandbox, and everything was working perfectly. The marathon is only a couple of weeks away now, so I switched over to the actual PayPal system, fired off a test donation, and now I've got a problem: for some reason, PayPal is not hitting the notify_url at all. I've verified the URL by switching back to sandbox temporarily, and everything works fine. I've also added a few calls to error_log in the listener code to see if it's just getting caught in the code somewhere, but have found out it hasn't been hit at all.
If anyone can offer any suggestions here as to what I can do, I'd greatly appreciate it. Obviously I'm under a bit of a time crunch, so I'd really appreciate it if you could respond sooner rather than later.
EDIT: Here's the relevant code:
if (isset($_GET['paypalipn'])) {
// tell PHP to log errors to ipn_errors.log in this directory
ini_set('log_errors', true);
ini_set('error_log', dirname(__FILE__).'/ipn_errors.log');
// intantiate the IPN listener
$listener = new IpnListener();
// tell the IPN listener to use the PayPal test sandbox
$listener->use_sandbox = false;
error_log("here");
// try to process the IPN POST
try {
$listener->requirePostMethod();
$verified = $listener->processIpn();
} catch (Exception $e) {
error_log($e->getMessage());
exit(0);
}
error_log("here2");
// Process the IPN
if ($verified) {
error_log("here3");
$errmsg = ''; // stores errors from fraud checks
// Split the custom variable
$split_custom = explode("&", $_POST['custom']);
$custom_array = array();
for ($i = 0; $i<count($split_custom); $i++) {
$current_set = explode("=", $split_custom[$i]);
$custom_array[$current_set[0]] = $current_set[1];
}
error_log("here4");
if (!isset($custom_array['game'])) {
$custom_array['game'] = 0;
}
if (!isset($custom_array['player'])) {
$custom_array['player'] = 0;
}
error_log("here5");
if (!empty($errmsg)) {
// manually investigate errors from the fraud checking
$body = "IPN failed fraud checks: \n$errmsg\n\n";
$body .= $listener->getTextReport();
mail('jafawcett#gmail.com', 'IPN Fraud Warning', $body);
} else {
mail('jafawcett#gmail.com', 'Successful IPN', $listener->getTextReport());
}
error_log("donor_un: ".$custom_array['donor_un']);
process_donation($_POST['mc_gross'], $custom_array['player'], $custom_array['game'], $custom_array['donor_name'], $_POST['payer_email'], $custom_array['donor_un']);
} else {
// manually investigate the invalid IPN
mail('jafawcett#gmail.com', 'Invalid IPN', $listener->getTextReport());
}
}
For the ipn listener class I'm using the one created by Micah Carrick and available here:
https://github.com/Quixotix/PHP-PayPal-IPN
I asked my direct contact at PayPal about this and got the following response...
In regards to the IPN issue, we had an outage last night. It was fixed
at about 1-2am, the IPN’s are delayed because we had a backed up queue
of about 2.5 million IPN’s which did not send out. We are currently
working through these IPN’s. I’d expect normal functionality shortly.
You can also see plenty of people talking about this issue on Twitter.
We're having the same problem.
It worked perfectly fine for a few months now and the script remains untouched. PayPal IPN is enabled inside the PayPal account as well and our IPNs just stopped this morning.
There might be a problem over at PayPal, we're still trying to figure it out.

Categories