Currently seeking new career opportunities in web development, particularly with Laravel, Hire Me

How to integrate PayPal into PHP

David Carr

PayPal Tutorials PHP & MySQL

In this post, I’ll explain how to use PayPal for one-off payment and how to use the Instant Payment Notification (IPN) to process payments on your website.

When testing you don’t want to actually spend any money, this is where sandbox accounts come in. Sandbox accounts lets you simulate transactions without actually spending money everything is simulated. The sandbox accounts lets you run through the entire transaction when it comes time to change to real accounts its a case of changing the account and URLs used, a quick change to make.

Sandbox  Accounts

Sign in to your PayPal account into PayPal Developer [Sandbox accounts - PayPal Developer](https://developer.paypal.com/developer/accounts/) 

Under Sandbox in the left sidebar click on Accounts, here you will see your automatically generated sandbox accounts if you don’t have any click create an account.

The default accounts are 1 personal and 1 business. These are perfect for testing transactions with PayPal.

You use the personal account to test payments to the business account. To get the password for each click on the ellipse on the far right and click on view/edit account.

PayPal Form

To send payment data to a PayPal checkout page you can use a form, there’s a lot of details on PayPal’s docs [how do-i-add-paypal-checkout-to-my-custom-shopping-cart](https://www.paypal.com/uk/smarthelp/article/how-do-i-add-paypal-checkout-to-my-custom-shopping-cart-ts1200)

The form posts to of https://www.sandbox.paypal.com/cgi-bin/webscr for sandbox accounts or for normal payments use https://www.paypal.com/cgi-bin/webscr

If you are sending multiple items then use the cart options:

<input type="hidden" name="cmd" value="_cart" />
<input type="hidden" name="upload" value="1" />

For single items only then use 

<input type=“hidden” name=“cmd” value="_xclick">

Having said this I default to using cart it can be used to single and multiple items so keep your code consistent.

To link the payment your PayPal business account enter the account email address to:

<input type="hidden" name="business" value="email@business.example.com" />

Core form data:

<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" />
<input type="hidden" name="cmd" value="_cart" />
<input type="hidden" name="upload" value="1" />
<input type="hidden" name="business" value="email@business.example.com" />

Additional options:

Set the currency:

<input type="hidden" name="currency_code" value="GBP" />
<input type="hidden" name="lc" value="UK" />
<input type="hidden" name="rm" value="2" />

Set a return url, this will be where the user is directed to after completing a payment.

The cancel url will be where the user is directed after canceling a payment.

The notify url is where Instant Payment Notifications (IPN) are sent to after payment

<input type="hidden" name="return" value="https://domain.com/thankyou" />
<input type="hidden" name="cancel_return" value="https://domain.com/cancel" />
<input type="hidden" name="notify_url" value="https://domain.com/ipn" />
<input type="hidden" name="charset" value="utf-8" />

Adding items

Each item will have a number on the end for example imte_name_1 is the first item another item would have item_name_2 and so on:

<input type="hidden" name="item_name_1" value="PC" />
<input type="hidden" name="item_number_1" value="0045" />
<input type="hidden" name="amount_1" value="500.00" />
<input type="hidden" name="quantity_1" value="1" />

For the pay button you can use PayPal’s image:

<input style="margin-top:10px;" type="image" src="http://www.paypal.com/en_US/i/btn/x-click-but01.gif" name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
</form>

Putting it all together:

<form action="https://www.sandbox.paypal.com/cgi-bin/webscr" method="post" />
<input type="hidden" name="cmd" value="_cart" />
<input type="hidden" name="upload" value="1" />
<input type="hidden" name="business" value="email@business.example.com" />
<input type="hidden" name="currency_code" value="GBP" />
<input type="hidden" name="lc" value="UK" />
<input type="hidden" name="rm" value="2" />
<input type="hidden" name="return" value="https://domain.com/thankyou" />
<input type="hidden" name="cancel_return" value="https://domain.com/cancel" />
<input type="hidden" name="notify_url" value="https://domain.com/ipn" />
<input type="hidden" name="charset" value="utf-8" />
<input type="hidden" name="item_name_1" value="PC" />
<input type="hidden" name="item_number_1 value="0045" />
<input type="hidden" name="amount_1" value="500.00" />
<input type="hidden" name="quantity_1" value="1" />
<input style="margin-top:10px;" type="image" src="http://www.paypal.com/en_US/i/btn/x-click-but01.gif" name="submit" alt="Make payments with PayPal - it's fast, free and secure!">
</form>

Whilst this works it does open it up to abuse, anyone can inspect the form and change any of the fields such as the price before it goes to PayPal! 

What I prefer to do it send the data directly from a PHP page, this way it cannot be modified before being sent to PayPal.

Post data to PayPal

The same form data is still used with this approach only it’s not laid out in a form but instead in an array:

$fields = [
    'cmd'           => '_cart',
    'upload'        => '1',
    'business'      => 'email@business.example.com',
    'currency_code' => 'GBP',
    'lc'            => 'UK',
    'rm'            => '2',
    'return'        => 'https://domain.com/thankyou',
    'cancel_return' => 'https://domain.com/cancel',
    'notify_url'    => 'https://domain.com/ipn'
];

//add items
$fields["item_name_1"]   = 'PC';
$fields["item_number_1"] = '0045';
$fields["amount_1"]      = '500.00';
$fields["quantity_1"]    = 1;

$fields["item_name_2"]   = 'Office Chair';
$fields["item_number_2"] = '0098';
$fields["amount_2"]      = '60.00';
$fields["quantity_2"]    = 1;

// Prepare query string
$query_string = http_build_query($fields);

header('Location: https://www.sandbox.paypal.com/cgi-bin/webscr?' . $query_string);
exit();

This will send the array data to the URL as a POST request.

To send a custom item such as an user_id you can use a custom field:

$fields['custom'] = $user_id;

Or part of the initial fields array:

$fields = [
    'cmd'           => '_cart',
    'upload'        => '1',
    'business'      => 'email@business.example.com',
    'currency_code' => 'GBP',
    'lc'            => 'UK',
    'rm'            => '2',
    'return'        => 'https://domain.com/thankyou',
    'cancel_return' => 'https://domain.com/cancel',
    'notify_url'    => 'https://domain.com/ipn',
    'custom'        => $user_id
];

Remember to change the sandbox URL and business field when switching to a live account.

For Laravel users, the IPN will be POSTED to your notify_url endpoint. It will not have a CSRF token to make sure you exclude the call from CSRF token checks by going to app/Http/Middleware/VerifyCsrfToken.php and adding the endpoint to the $except array:

class VerifyCsrfToken extends Middleware
{
    /**
     * The URIs that should be excluded from CSRF verification.
     *
     * @var array
     */
    protected $except = [
        'ipn'
    ];
}

 

Handling IPN

Once payment has been made an IPN is sent to the specified notify_url.

First, specify weather to use sandbox with a Y for yes or anything else for No.

Collect the POST data

// Read 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){
        $myPost[$keyval[0]] = urldecode($keyval[1]);
    }
}

Read the post from PayPal and add CMD

$req = 'cmd=_notify-validate';
foreach ($myPost as $key => $value) {
    $value = urlencode($value);
    $req .= "&$key=$value";
}

Set the paypal URL

if (USE_SANDBOX == 'Y') {
    $paypal_url = "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr";
} else {
    $paypal_url = "https://ipnpb.paypal.com/cgi-bin/webscr";
}

Next to confirm the payment a request is sent back to PayPal

$ch = curl_init($paypal_url);
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_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
$res = curl_exec($ch);

If there is an error connecting throw an exception

if (curl_errno($ch) != 0) {
    
    throw new \Exception("Can't connect to PayPal to validate IPN message: ".curl_error($ch)); 
    
    return true;
}

A successful payment will have VERIFIED in the PayPal payload so check for that:

if (preg_match("/VERIFIED/", $res)) {
//payment complete, process order & send an email etc
}

Putting it all together:

define("USE_SANDBOX", 'Y');

// Read 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){
        $myPost[$keyval[0]] = urldecode($keyval[1]);
    }
}
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($myPost as $key => $value) {
    $value = urlencode($value);
    $req .= "&$key=$value";
}
if (USE_SANDBOX == 'Y') {
    $paypal_url = "https://ipnpb.sandbox.paypal.com/cgi-bin/webscr";
} else {
    $paypal_url = "https://ipnpb.paypal.com/cgi-bin/webscr";
}
$ch = curl_init($paypal_url);
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_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
$res = curl_exec($ch);

if (curl_errno($ch) != 0) {
    
    throw new \Exception("Can't connect to PayPal to validate IPN message: ".curl_error($ch)); 
    
    return true;
} else {

    if (preg_match("/VERIFIED/", $res)) {
        //payment complete, you should send en email here.
    } else {
        //IPN not verified, you should send en email here.
    }
}
curl_close($ch);

 

Laravel Modules Your Logo Your Logo Your Logo

Become a sponsor

Help support the blog so that I can continue creating new content!

Sponsor

My Latest Book

Modular Laravel Book - Laravel: The Modular way

Learn how to build modular applications with Laravel Find out more

Subscribe to my newsletter

Subscribe and get my books and product announcements.

Learn Laravel with Laracasts

Faster Laravel Hosting

© 2006 - 2024 DC Blog. All code MIT license. All rights reserved.