PayPal Express Checkout with PHP

Paypal is the most popular and convenient way to get paid. If you are selling some products in your website, you should definitely use Paypal payment gateway, why? because it's free and there are over 350 million Paypal users all over the world who would happily purchase your products. In this tutorial let us find-out how we can use Paypal Express Checkout in websites to sell some products instantly.
To test your PayPal store in a local server, you need to signup for PayPal Developer account and use their sandbox. See creating PayPal Sandbox account. To sell your products in real world, you need to signup for real Paypal account and obtain sellers information needed for the configuration file. As you can see we have four PHP files in this tutorial index.php, config.php, process.php and paypal.class.php, but if you observe mainly two files index.php and process.php, you should be pretty clear how everything works.

Configuration

config.php PHP file is used to store seller's Paypal API information. Use your sandbox or live PayPal API details to replace variables in config.php file.
PHP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
<?php $PayPalMode = 'sandbox'; // sandbox or live $PayPalApiUsername = '[email protected]'; //PayPal API Username $PayPalApiPassword = '123456'; //Paypal API password $PayPalApiSignature = 'ZWxwchnCsdQg5PxAUjcH6OPuZK3sPcPH'; //Paypal API Signature $PayPalCurrencyCode = 'USD'; //Paypal Currency Code $PayPalReturnURL = 'http://yoursite.com/paypal/process.php'; //Point to process.php page $PayPalCancelURL = 'http://yoursite.com/paypal/cancel_url.php'; //Cancel URL if user clicks cancel ?>

Product Page

index.php is initial page where your buyers get to see your products, it doesn't matter how you are planning to present your products, it could be complex ajax driven page, single product or just list of few products, you just need to list your products similar to example below. Each product item contains a form, and each form contains hidden input variables item name, item number, item quantity and price. A buyer is able to see product details and select quantity before buying the product. When buyer decides to buy, the selected product data gets posted to process.php.
Please Note: Do not include price in hidden field as shown in example below, as the value can be easily replaced using any developer tool. In practical world, you should check existence of product and fetch actual price from database using product ID in process page, so that people can not manipulate the price.
HTML
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
<?php include_once("config.php"); ?> <h2 align="center">Test Products</h2> <table class="procut_item" border="0" cellpadding="4"> <tr> <td width="70%"><h4>Product 1</h4>(product description)</td> <td width="30%"> <form method="post" action="process.php"> <input type="hidden" name="itemname" value="Product 1" /> <input type="hidden" name="itemnumber" value="10000" /> <input type="hidden" name="itemdesc" value="product description." /> <input type="hidden" name="itemprice" value="225.00" /> Quantity : <select name="itemQty"><option value="1">1</option><option value="2">2</option><option value="3">3</option></select> <input class="dw_button" type="submit" name="submitbutt" value="Buy (225.00 <?php echo $PayPalCurrencyCode; ?>)" /> </form> </td> </tr> </table>

PHP Class

The downloadbale sample files also include a PHP class called paypal.class.php to interact with PayPal. paypal.class.php is nothing but collection of functions, PPHttpPost() PHP function is used to carry out API operations in progress.php file, such as sending HTTP POST Request or getting response from the server. You can add your own additional functions (if needed) in this class file and execute like this $paypal->functionname().

Processing PayPal Payment

The most important segment of our PayPal Express Checkout system is the process.php file. Here the buyer's product data is received and processed further for the payment. We will execute mainly three Paypal methods here SetExpressCheckout, DoExpressCheckout & GetExpressCheckoutDetails. But before we proceed further, we need to collect product details such as product id, its name and price for process page. As explained earlier, you should only fetch product price from database rather than hidden input fields for security reasons, once we have the price, we can calculate and include tax details, shipping cost and discounts, which we need to set as parameters for SetExpressCheckout and DoExpressCheckout. There are other various parameters you can pass to SetExpressCheckout and DoExpressCheckout to control the outcome. I advice you to read official PayPal documentation to know more about these parameters.

SetExpressCheckOut
When buyer sends product details by clicking buy button, process.php needs to obtain PayPal token with SetExpressCheckOut method using Seller's PayPal API credentials.

Once we successfully receive PayPal token, we need to set some session variable (itemprice, totalamount, itemName, itemNo, itemQTY) for later use. And then we redirect buyer to PayPal order summary page, where buyer pays in secure PayPal environment, but remember PayPal doesn't transfer money yet.

DoExpressCheckoutPayment
After payment, buyer is redirected back to process.php page with Paypal token and PayerID values. We again need to send these values back to PayPal using DoExpressCheckoutPayment method, where PayPal verifies these values, only then the money is transferred to Seller's account.

GetExpressCheckoutDetails
Obtains information about a specific order using SetExpressCheckOut token, on successful payment you might want to save buyer details such as address, payment Information etc. in database, please see Mysqli basic usage. Each comment line in PHP code below explains how process.php deals with data received from buyer and PayPal.
PHP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
<?php session_start(); include_once("config.php"); include_once("paypal.class.php"); $paypalmode = ($PayPalMode=='sandbox') ? '.sandbox' : ''; if($_POST) //Post Data received from product list page. { //Mainly we need 4 variables from product page Item Name, Item Price, Item Number and Item Quantity. //Please Note : People can manipulate hidden field amounts in form, //In practical world you must fetch actual price from database using item id. Eg: //$ItemPrice = $mysqli->query("SELECT item_price FROM products WHERE id = Product_Number"); $ItemName = $_POST["itemname"]; //Item Name $ItemPrice = $_POST["itemprice"]; //Item Price $ItemNumber = $_POST["itemnumber"]; //Item Number $ItemDesc = $_POST["itemdesc"]; //Item description $ItemQty = $_POST["itemQty"]; // Item Quantity $ItemTotalPrice = ($ItemPrice*$ItemQty); //(Item Price x Quantity = Total) Get total amount of product; //Other important variables like tax, shipping cost $TotalTaxAmount = 2.58; //Sum of tax for all items in this order. $HandalingCost = 2.00; //Handling cost for this order. $InsuranceCost = 1.00; //shipping insurance cost for this order. $ShippinDiscount = -3.00; //Shipping discount for this order. Specify this as negative number. $ShippinCost = 3.00; //Although you may change the value later, try to pass in a shipping amount that is reasonably accurate. //Grand total including all tax, insurance, shipping cost and discount $GrandTotal = ($ItemTotalPrice + $TotalTaxAmount + $HandalingCost + $InsuranceCost + $ShippinCost + $ShippinDiscount); //Parameters for SetExpressCheckout, which will be sent to PayPal $padata = '&METHOD=SetExpressCheckout'. '&RETURNURL='.urlencode($PayPalReturnURL ). '&CANCELURL='.urlencode($PayPalCancelURL). '&PAYMENTREQUEST_0_PAYMENTACTION='.urlencode("SALE"). '&L_PAYMENTREQUEST_0_NAME0='.urlencode($ItemName). '&L_PAYMENTREQUEST_0_NUMBER0='.urlencode($ItemNumber). '&L_PAYMENTREQUEST_0_DESC0='.urlencode($ItemDesc). '&L_PAYMENTREQUEST_0_AMT0='.urlencode($ItemPrice). '&L_PAYMENTREQUEST_0_QTY0='. urlencode($ItemQty). /* //Additional products (L_PAYMENTREQUEST_0_NAME0 becomes L_PAYMENTREQUEST_0_NAME1 and so on) '&L_PAYMENTREQUEST_0_NAME1='.urlencode($ItemName2). '&L_PAYMENTREQUEST_0_NUMBER1='.urlencode($ItemNumber2). '&L_PAYMENTREQUEST_0_DESC1='.urlencode($ItemDesc2). '&L_PAYMENTREQUEST_0_AMT1='.urlencode($ItemPrice2). '&L_PAYMENTREQUEST_0_QTY1='. urlencode($ItemQty2). */ /* //Override the buyer's shipping address stored on PayPal, The buyer cannot edit the overridden address. '&ADDROVERRIDE=1'. '&PAYMENTREQUEST_0_SHIPTONAME=J Smith'. '&PAYMENTREQUEST_0_SHIPTOSTREET=1 Main St'. '&PAYMENTREQUEST_0_SHIPTOCITY=San Jose'. '&PAYMENTREQUEST_0_SHIPTOSTATE=CA'. '&PAYMENTREQUEST_0_SHIPTOCOUNTRYCODE=US'. '&PAYMENTREQUEST_0_SHIPTOZIP=95131'. '&PAYMENTREQUEST_0_SHIPTOPHONENUM=408-967-4444'. */ '&NOSHIPPING=0'. //set 1 to hide buyer's shipping address, in-case products that do not require shipping '&PAYMENTREQUEST_0_ITEMAMT='.urlencode($ItemTotalPrice). '&PAYMENTREQUEST_0_TAXAMT='.urlencode($TotalTaxAmount). '&PAYMENTREQUEST_0_SHIPPINGAMT='.urlencode($ShippinCost). '&PAYMENTREQUEST_0_HANDLINGAMT='.urlencode($HandalingCost). '&PAYMENTREQUEST_0_SHIPDISCAMT='.urlencode($ShippinDiscount). '&PAYMENTREQUEST_0_INSURANCEAMT='.urlencode($InsuranceCost). '&PAYMENTREQUEST_0_AMT='.urlencode($GrandTotal). '&PAYMENTREQUEST_0_CURRENCYCODE='.urlencode($PayPalCurrencyCode). '&LOCALECODE=GB'. //PayPal pages to match the language on your website. '&LOGOIMG=https://www.sanwebe.com/wp-content/themes/sanwebe/img/logo.png'. //site logo '&CARTBORDERCOLOR=FFFFFF'. //border color of cart '&ALLOWNOTE=1'; ############# set session variable we need later for "DoExpressCheckoutPayment" ####### $_SESSION['ItemName'] = $ItemName; //Item Name $_SESSION['ItemPrice'] = $ItemPrice; //Item Price $_SESSION['ItemNumber'] = $ItemNumber; //Item Number $_SESSION['ItemDesc'] = $ItemDesc; //Item description $_SESSION['ItemQty'] = $ItemQty; // Item Quantity $_SESSION['ItemTotalPrice'] = $ItemTotalPrice; //total amount of product; $_SESSION['TotalTaxAmount'] = $TotalTaxAmount; //Sum of tax for all items in this order. $_SESSION['HandalingCost'] = $HandalingCost; //Handling cost for this order. $_SESSION['InsuranceCost'] = $InsuranceCost; //shipping insurance cost for this order. $_SESSION['ShippinDiscount'] = $ShippinDiscount; //Shipping discount for this order. Specify this as negative number. $_SESSION['ShippinCost'] = $ShippinCost; //Although you may change the value later, try to pass in a shipping amount that is reasonably accurate. $_SESSION['GrandTotal'] = $GrandTotal; //We need to execute the "SetExpressCheckOut" method to obtain paypal token $paypal= new MyPayPal(); $httpParsedResponseAr = $paypal->PPHttpPost('SetExpressCheckout', $padata, $PayPalApiUsername, $PayPalApiPassword, $PayPalApiSignature, $PayPalMode); //Respond according to message we receive from Paypal if("SUCCESS" == strtoupper($httpParsedResponseAr["ACK"]) || "SUCCESSWITHWARNING" == strtoupper($httpParsedResponseAr["ACK"])) { //Redirect user to PayPal store with Token received. $paypalurl ='https://www'.$paypalmode.'.paypal.com/cgi-bin/webscr?cmd=_express-checkout&token='.$httpParsedResponseAr["TOKEN"].''; header('Location: '.$paypalurl); }else{ //Show error message echo '<div style="color:red"><b>Error : </b>'.urldecode($httpParsedResponseAr["L_LONGMESSAGE0"]).'</div>'; echo '<pre>'; print_r($httpParsedResponseAr); echo '</pre>'; } } //Paypal redirects back to this page using ReturnURL, We should receive TOKEN and Payer ID if(isset($_GET["token"]) && isset($_GET["PayerID"])) { //we will be using these two variables to execute the "DoExpressCheckoutPayment" //Note: we haven't received any payment yet. $token = $_GET["token"]; $payer_id = $_GET["PayerID"]; //get session variables $ItemName = $_SESSION['ItemName']; //Item Name $ItemPrice = $_SESSION['ItemPrice'] ; //Item Price $ItemNumber = $_SESSION['ItemNumber']; //Item Number $ItemDesc = $_SESSION['ItemDesc']; //Item Number $ItemQty = $_SESSION['ItemQty']; // Item Quantity $ItemTotalPrice = $_SESSION['ItemTotalPrice']; //total amount of product; $TotalTaxAmount = $_SESSION['TotalTaxAmount'] ; //Sum of tax for all items in this order. $HandalingCost = $_SESSION['HandalingCost']; //Handling cost for this order. $InsuranceCost = $_SESSION['InsuranceCost']; //shipping insurance cost for this order. $ShippinDiscount = $_SESSION['ShippinDiscount']; //Shipping discount for this order. Specify this as negative number. $ShippinCost = $_SESSION['ShippinCost']; //Although you may change the value later, try to pass in a shipping amount that is reasonably accurate. $GrandTotal = $_SESSION['GrandTotal']; $padata = '&TOKEN='.urlencode($token). '&PAYERID='.urlencode($payer_id). '&PAYMENTREQUEST_0_PAYMENTACTION='.urlencode("SALE"). //set item info here, otherwise we won't see product details later '&L_PAYMENTREQUEST_0_NAME0='.urlencode($ItemName). '&L_PAYMENTREQUEST_0_NUMBER0='.urlencode($ItemNumber). '&L_PAYMENTREQUEST_0_DESC0='.urlencode($ItemDesc). '&L_PAYMENTREQUEST_0_AMT0='.urlencode($ItemPrice). '&L_PAYMENTREQUEST_0_QTY0='. urlencode($ItemQty). /* //Additional products (L_PAYMENTREQUEST_0_NAME0 becomes L_PAYMENTREQUEST_0_NAME1 and so on) '&L_PAYMENTREQUEST_0_NAME1='.urlencode($ItemName2). '&L_PAYMENTREQUEST_0_NUMBER1='.urlencode($ItemNumber2). '&L_PAYMENTREQUEST_0_DESC1=Description text'. '&L_PAYMENTREQUEST_0_AMT1='.urlencode($ItemPrice2). '&L_PAYMENTREQUEST_0_QTY1='. urlencode($ItemQty2). */ '&PAYMENTREQUEST_0_ITEMAMT='.urlencode($ItemTotalPrice). '&PAYMENTREQUEST_0_TAXAMT='.urlencode($TotalTaxAmount). '&PAYMENTREQUEST_0_SHIPPINGAMT='.urlencode($ShippinCost). '&PAYMENTREQUEST_0_HANDLINGAMT='.urlencode($HandalingCost). '&PAYMENTREQUEST_0_SHIPDISCAMT='.urlencode($ShippinDiscount). '&PAYMENTREQUEST_0_INSURANCEAMT='.urlencode($InsuranceCost). '&PAYMENTREQUEST_0_AMT='.urlencode($GrandTotal). '&PAYMENTREQUEST_0_CURRENCYCODE='.urlencode($PayPalCurrencyCode); //We need to execute the "DoExpressCheckoutPayment" at this point to Receive payment from user. $paypal= new MyPayPal(); $httpParsedResponseAr = $paypal->PPHttpPost('DoExpressCheckoutPayment', $padata, $PayPalApiUsername, $PayPalApiPassword, $PayPalApiSignature, $PayPalMode); //Check if everything went ok.. if("SUCCESS" == strtoupper($httpParsedResponseAr["ACK"]) || "SUCCESSWITHWARNING" == strtoupper($httpParsedResponseAr["ACK"])) { echo '<h2>Success</h2>'; echo 'Your Transaction ID : '.urldecode($httpParsedResponseAr["PAYMENTINFO_0_TRANSACTIONID"]); /* //Sometimes Payment are kept pending even when transaction is complete. //hence we need to notify user about it and ask him manually approve the transiction */ if('Completed' == $httpParsedResponseAr["PAYMENTINFO_0_PAYMENTSTATUS"]) { echo '<div style="color:green">Payment Received! Your product will be sent to you very soon!</div>'; } elseif('Pending' == $httpParsedResponseAr["PAYMENTINFO_0_PAYMENTSTATUS"]) { echo '<div style="color:red">Transaction Complete, but payment is still pending! '. 'You need to manually authorize this payment in your <a target="_new" href="http://www.paypal.com">Paypal Account</a></div>'; } // we can retrive transection details using either GetTransactionDetails or GetExpressCheckoutDetails // GetTransactionDetails requires a Transaction ID, and GetExpressCheckoutDetails requires Token returned by SetExpressCheckOut $padata = '&TOKEN='.urlencode($token); $paypal= new MyPayPal(); $httpParsedResponseAr = $paypal->PPHttpPost('GetExpressCheckoutDetails', $padata, $PayPalApiUsername, $PayPalApiPassword, $PayPalApiSignature, $PayPalMode); if("SUCCESS" == strtoupper($httpParsedResponseAr["ACK"]) || "SUCCESSWITHWARNING" == strtoupper($httpParsedResponseAr["ACK"])) { echo '<br /><b>Stuff to store in database :</b><br /><pre>'; /* #### SAVE BUYER INFORMATION IN DATABASE ### //see (https://www.sanwebe.com/2013/03/basic-php-mysqli-usage) for mysqli usage $buyerName = $httpParsedResponseAr["FIRSTNAME"].' '.$httpParsedResponseAr["LASTNAME"]; $buyerEmail = $httpParsedResponseAr["EMAIL"]; //Open a new connection to the MySQL server $mysqli = new mysqli('host','username','password','database_name'); //Output any connection error if ($mysqli->connect_error) { die('Error : ('. $mysqli->connect_errno .') '. $mysqli->connect_error); } $insert_row = $mysqli->query("INSERT INTO BuyerTable (BuyerName,BuyerEmail,TransactionID,ItemName,ItemNumber, ItemAmount,ItemQTY) VALUES ('$buyerName','$buyerEmail','$transactionID','$ItemName',$ItemNumber, $ItemTotalPrice,$ItemQTY)"); if($insert_row){ print 'Success! ID of last inserted record is : ' .$mysqli->insert_id .'<br />'; }else{ die('Error : ('. $mysqli->errno .') '. $mysqli->error); } */ echo '<pre>'; print_r($httpParsedResponseAr); echo '</pre>'; } else { echo '<div style="color:red"><b>GetTransactionDetails failed:</b>'.urldecode($httpParsedResponseAr["L_LONGMESSAGE0"]).'</div>'; echo '<pre>'; print_r($httpParsedResponseAr); echo '</pre>'; } }else{ echo '<div style="color:red"><b>Error : </b>'.urldecode($httpParsedResponseAr["L_LONGMESSAGE0"]).'</div>'; echo '<pre>'; print_r($httpParsedResponseAr); echo '</pre>'; } } ?>

Conclusion

I hope this tutorial will help you sell your products easily using PayPal. If you are facing issue of Paypal holding fund for 21 days, please read their policy here, and if you sell huge amount of products everyday, you might want to automate things by setting up PayPal IPN listener script. Good luck! You might also be interested in reading Creating Simple Shopping Cart with PHP. and Sending Multiple Items to PayPal using Shopping cart. Download Demo
  • i am integrating this code into my paypal sandbox but i am getting mesage "Transaction Complete, but payment may still be pending if that's the case, You can manually authorize this payment in your Paypal Account" every time. what the reason?
    • I already have the code installed. But since last couple of days, I am seeing on my site the problem you are describing. Even the calculation is being wrong. I checked all the files for bugs and broken stuffs. It seems they are all fine. I came to site looking for a solution. But during the search, I noticed paypal to do this weird things and not even help the ones suffering.
  • Thanks itry your system on paypal express checkout but am having the following error ( ! ) Fatal error: Can't use function return value in write context in C:\wamp\www\ppl\paypal.class.php on line 125 Call Stack # Time Memory Function Location 1 0.0004 260600 {main}( ) ..\process.php:0 please how do i resolve the problem. thanks
  • Hi, I'm always having this problem using live credential when i put my credit card data to pay: "YOUR SESSION HAS EXPIRED, LOGIN AGAIN" Any ideas? thank you so much for your kindness
New question is currently disabled!