Multi Items Payment with PayPal REST API (PHP)

To simplify things I created a simple Payment System with PayPal REST API in my previous post. But today I want to extend that tutorial making it bit complex and allowing customers to either pay with PayPal payment method or credit cards directly. Yes PayPal allows people to pay for your products directly using credit cards, which requires absolutely no user sign-in or sign-ups with PayPal. pay-with-credit-card-or-pay-pal Before we begin get your Client Id and Client Secret from developer.paypal.com account for sandbox testing. Or to sell your products live, you will need Client Id and Client Secret from Paypal.com In my previous post, I had created four files, but here I've add two more file called payment_option.php and config.php. payment_option.php file allows customer to choose Payment method, which is either PayPal or Credit Card, and you can guess what config.php is all about.
  • config.php – Stores information needed by the application.
  • Index.php – (Products page) contains list of products for sale.
  • Payment_option.php – Allows customer to choose payment method
  • Order_process.php – Processes PayPal payment.
  • Functions.inc.php – Helper script, contains functions needed for processing.
  • Payment_cancel.html – PayPal redirects user to this page in-case of cancellation.

Products Page

Let's talk about product page (Index.php) a bit, as you can see in PHP code below, I have listed the products stored in database, each item contains a quantity field and a checkbox. User selects the item he wants to buy, selects the quantity and these information will be posted to payment_option.php.
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
<?php //select product information from database $results = $mysqli->query("SELECT id, product_code, product_name, product_price, image_name FROM my_products"); //wrap with form tag print '<form action="payment_option.php" method="post">'; print '<ul>'; //list products from database while($row = $results->fetch_object()) { print '<li>'; print '<img src="images/' . $row->image_name . '" width="220" height="220">'; print '<div class="item-name">' . $row->product_name . '</div>'; print '<div class="btn-wrap">'; print 'Qty:'; print '<select name="'.$row->product_code.'_qty">'; print '<option>1</option>'; print '<option>2</option>'; print '<option>3</option>'; print '<option>4</option>'; print '<option>5</option>'; print '<option>8</option>'; print '<option>10</option>'; print '</select> '; print '<input type="checkbox" name="item_id[]" value="' . $row->id . '" />'; print '</div>'; print '</li>'; } print '</ul>'; print '<div align="center">'; print '<input type="submit" value="Buy Selected Items" />'; print '</div>'; print '</form>'; ?>

Payment Method Selection

When a user is redirected to this page along with item ids and quantity, we need to make sure selected items exist in database, then we can fetch prices and product codes from database and construct a collection of items array in $_SESSION["items"], which we will use in order_process.php.
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
<?php session_start(); //start the session include_once "config.php"; //continue if item_ids are set in post if(isset($_POST["item_id"])){ //make sure ids are numbers foreach ($_POST["item_id"] as $itm_id) { if (!(is_numeric($itm_id))) { die("Product id passed must be integers!"); } } //convert item ids into string using implode(), seperated by comma. $item_codes = implode(',', $_POST["item_id"]); //select products using given ids $results = $mysqli->query("SELECT product_code, product_name, product_price FROM my_products WHERE id IN (".$item_codes.")"); if($results->num_rows){ $item_total = 0; while($row = $results->fetch_object()){ //item quantity $item_quantity = ( isset( $_POST[$row->product_code."_qty"] ) && is_numeric( $_POST[$row->product_code."_qty"] ) ) ? $_POST[$row->product_code."_qty"] : 1; //add item array to $items variable, it will be used in order_process.php $items[] = array( 'name' => $row->product_name, 'quantity' => $item_quantity, 'price' => $row->product_price, 'sku' => $row->product_code, 'currency' => PP_CURRENCY ); //calculate total price of all items $item_total = $item_total + ($row->product_price * $item_quantity); } }else{ die("Selected item(s) not found in database!"); } //Set session variables of items and total price for later use. $_SESSION["items"] = $items; $_SESSION["items_total"] = $item_total; }else{ die("Please select atlease 1 product"); } ?>
If everything looks good, it's time to display Payment Option page to user. As you can see I have two HTML forms here, first one is for PayPal Payment method, and second HTML form is for credit card Payment method, it contains all the fields required for credit card information.
HTML
  • 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
<div class="payment_method"> <div class="select_method"> Select a Payment Method. <form action="order_process.php" method="post" > <select id="payment_method"> <option value="paypal">PayPal</option> <option value="credit_card">Credit Card</option> </select> <input type="hidden" name="payment_method" value="paypal" /> <input type="submit" value="Go"> </form> </div> <form action="order_process.php" method="post" id="credit_cart_pay" class="credit_card_info" style="display:none" novalidate> <label><span>First Name :</span><input type="text" name="credit_card_first_name" required="true" /></label> <label><span>Last Name :</span><input type="text" name="credit_card_last_name" required="true" /></label> <label><span>Card Type :</span><select name="credit_card_type"> <option value="visa">Visa</option> <option value="mastercard">Mastercard</option> </select> </label> <label><span>Credit Card Number :</span><input type="text" name="credit_card_number" required="true" /></label> <label><span>Expire Month :</span><input type="text" name="credit_card_expire_month" required="true" /></label> <label><span>Expire Year :</span><input type="text" name="credit_card_expire_year" required="true" /></label> <label><span>cvv2 :</span> <input type="text" name="credit_card_cvv2" required="true" /></label> <input type="hidden" name="payment_method" value="credit_card" /> <div align="center" id="button_wrp"><input type="submit" value="Pay with Credit Card"></div> </form> </div>
You may be wondering, why show two forms to customer? he will get confused! Well the solution is pretty simple, we will just use jQuery here, and we hide/show credit card form when customer selects a payment method. You might want to use some credit card validation script here to make things more solid.
JQUERY
  • 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
<script type="text/javascript"> $(document).ready(function() { //Payment method selection by user $( "#payment_method" ).change(function() { if($(this).val()=="credit_card"){ $( ".credit_card_info").fadeIn(); $(this).parent().find('input[type="submit"]').hide(); }else{ $( ".credit_card_info").fadeOut(); $(this).parent().find('input[type="submit"]').show(); } }); $("#credit_cart_pay").submit(function( event ){ $("#credit_cart_pay input[required=true]").each(function(){ $(this).css('border-color',''); if(!$.trim($(this).val())){ //if this field is empty $(this).css('border-color','red'); //change border color to red event.preventDefault(); } }); $("#button_wrp").html("Please wait.."); }); }); </script>

Order Processes

Order process is actually pretty straight forward if you examine the code carefully. I have really tried hard to make it look less complicated, I just hope it doesn't take you too much time to understand the whole process. Here's the sample flow of order_process.php.
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
//we have user payment method and session items if(isset($_POST["payment_method"]) && isset($_SESSION["items"])){ try{ if($_POST["payment_method"] == "paypal"){ //redirect to paypal with items and prices } if($_POST["payment_method"] == "credit_card"){ //pay using credit card, no paypal redirection required //set $_SESSION["results"] and redirect user back to this page } }catch(PPConnectionException $ex){ //show errors } } if(isset($_GET["token"]) && isset($_GET["PayerID"]) && isset($_SESSION["payment_id"])){ //if PayPal payment method was used, paypal redirects back to this page with PayerID and Payment ID //execute payment using PayerID and Payment ID //set $_SESSION["results"] and redirect user back to this page } if(isset($_SESSION["results"])) { //If results session was set in actions above, display results set in session. }
And here's the whole code, just take your time to read each comment line to understand it more clearly.
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
<?php session_start(); //start session include_once "config.php"; include_once "vendor/autoload.php"; //include PayPal SDK include_once "functions.inc.php"; //our PayPal functions if(isset($_POST["payment_method"]) && isset($_SESSION["items"])){ //card details for credit card payemnt if($_POST["payment_method"] == "credit_card"){ $cc_first_name = (isset($_POST["credit_card_first_name"]))? $_POST["credit_card_first_name"] : die("First Name Empty"); $cc_last_name = (isset($_POST["credit_card_last_name"]))? $_POST["credit_card_last_name"] : die("Last Name Empty"); $cc_card_type = (isset($_POST["credit_card_type"]))? $_POST["credit_card_type"] : die("Credit Card type Empty"); $cc_card_number = (isset($_POST["credit_card_number"]))? $_POST["credit_card_number"] : die("Credit Card Number Empty"); $cc_card_month = (isset($_POST["credit_card_expire_month"]))? $_POST["credit_card_expire_month"] : die("Expire Month Empty"); $cc_card_year = (isset($_POST["credit_card_expire_year"]))? $_POST["credit_card_expire_year"] : die("Expire Year Empty"); $cc_card_cvv2 = (isset($_POST["credit_card_cvv2"]))? $_POST["credit_card_cvv2"] : die("CVV month empty"); } //set array of items details from session, single or multiple $items = $_SESSION["items"]; //get total amount from session. $total_amount = $_SESSION["items_total"]; // try a payment request try{ ######## if payment method is PayPal ############## if($_POST["payment_method"] == "paypal"){ //if payment method was PayPal, we need to redirect user to PayPal approval URL $result = create_paypal_payment($total_amount, PP_CURRENCY, '', $items, RETURN_URL, CANCEL_URL); if($result->state == "created" && $result->payer->payment_method == "paypal"){ $_SESSION["payment_id"] = $result->id; //set payment id for later use, we need this to execute payment unset($_SESSION["items"]); //unset item session, not required anymore. unset($_SESSION["items_total"]); //unset items_total session, not required anymore. header("location: ". $result->links[1]->href); //after success redirect user to approval URL exit(); } } ######## if payment method is Credit Card ############## if($_POST["payment_method"] == "credit_card"){ $credit_card = array( 'type'=> $cc_card_type, 'number' => $cc_card_number, 'expire_month'=>$cc_card_month, 'expire_year'=>$cc_card_year, 'cvv2'=>$cc_card_cvv2, 'first_name'=>$cc_first_name, 'last_name'=>$cc_last_name ); //pay directly using credit card information. $result = pay_direct_with_credit_card($credit_card, PP_CURRENCY , $total_amount, $items, '') ; //If credit card payment is succesful, get results if($result->state == "approved" && $result->payer->payment_method == "credit_card"){ unset($_SESSION["items"]); //unset item session, not required anymore. unset($_SESSION["items_total"]); //unset items_total session, not required anymore. //get transaction details $transaction_id = $result->transactions[0]->related_resources[0]->sale->id; $transaction_time = $result->transactions[0]->related_resources[0]->sale->create_time; $transaction_currency = $result->transactions[0]->related_resources[0]->sale->amount->currency; $transaction_amount = $result->transactions[0]->related_resources[0]->sale->amount->total; $transaction_method = $result->payer->payment_method; $transaction_state = $result->transactions[0]->related_resources[0]->sale->state; //get payer details $payer_first_name = $result->payer->payer_info->first_name; $payer_last_name = $result->payer->payer_info->last_name; $payer_email = $result->payer->payer_info->email; $payer_id = $result->payer->payer_info->payer_id; //get shipping details $shipping_recipient = $result->transactions[0]->item_list->shipping_address->recipient_name; $shipping_line1 = $result->transactions[0]->item_list->shipping_address->line1; $shipping_line2 = $result->transactions[0]->item_list->shipping_address->line2; $shipping_city = $result->transactions[0]->item_list->shipping_address->city; $shipping_state = $result->transactions[0]->item_list->shipping_address->state; $shipping_postal_code = $result->transactions[0]->item_list->shipping_address->postal_code; $shipping_country_code = $result->transactions[0]->item_list->shipping_address->country_code; //insert into database $insert_row = $mysqli->query("INSERT INTO my_orders (transaction_id, transaction_currency, transaction_amount, transaction_method, transaction_state) VALUES ('$transaction_id', '$transaction_currency', '$transaction_amount', '$transaction_method', '$transaction_state')"); //set $_SESSION["results"] session, print_r($result); to see what is returned $_SESSION["results"] = array( 'transaction_id' => $transaction_id, 'transaction_time' => $transaction_time, 'transaction_currency' => $transaction_currency, 'transaction_amount' => $transaction_amount, 'transaction_method' => $transaction_method, 'transaction_state' => $transaction_state ); header("location: ". RETURN_URL); //$_SESSION["results"] is set, redirect back to order_process.php exit(); } } }catch(PPConnectionException $ex) { echo parseApiError($ex->getData()); } catch (Exception $ex) { echo $ex->getMessage(); } } ### If Payment method was PayPal, user is redirected back to this page with token and Payer ID ### if(isset($_GET["token"]) && isset($_GET["PayerID"]) && isset($_SESSION["payment_id"])){ try{ $result = execute_payment($_SESSION["payment_id"], $_GET["PayerID"]); //call execute payment function. if($result->state == "approved"){ //if state = approved continue.. //SUCESS unset($_SESSION["payment_id"]); //unset payment_id, it is no longer needed //get transaction details $transaction_id = $result->transactions[0]->related_resources[0]->sale->id; $transaction_time = $result->transactions[0]->related_resources[0]->sale->create_time; $transaction_currency = $result->transactions[0]->related_resources[0]->sale->amount->currency; $transaction_amount = $result->transactions[0]->related_resources[0]->sale->amount->total; $transaction_method = $result->payer->payment_method; $transaction_state = $result->transactions[0]->related_resources[0]->sale->state; //get payer details $payer_first_name = $result->payer->payer_info->first_name; $payer_last_name = $result->payer->payer_info->last_name; $payer_email = $result->payer->payer_info->email; $payer_id = $result->payer->payer_info->payer_id; //get shipping details $shipping_recipient = $result->transactions[0]->item_list->shipping_address->recipient_name; $shipping_line1 = $result->transactions[0]->item_list->shipping_address->line1; $shipping_line2 = $result->transactions[0]->item_list->shipping_address->line2; $shipping_city = $result->transactions[0]->item_list->shipping_address->city; $shipping_state = $result->transactions[0]->item_list->shipping_address->state; $shipping_postal_code = $result->transactions[0]->item_list->shipping_address->postal_code; $shipping_country_code = $result->transactions[0]->item_list->shipping_address->country_code; //insert into database $insert_row = $mysqli->query("INSERT INTO my_orders (transaction_id, transaction_currency, transaction_amount, transaction_method, transaction_state) VALUES ('$transaction_id', '$transaction_currency', '$transaction_amount', '$transaction_method', '$transaction_state')"); //Set session for later use, print_r($result); to see what is returned $_SESSION["results"] = array( 'transaction_id' => $transaction_id, 'transaction_time' => $transaction_time, 'transaction_currency' => $transaction_currency, 'transaction_amount' => $transaction_amount, 'transaction_method' => $transaction_method, 'transaction_state' => $transaction_state ); header("location: ". RETURN_URL); //$_SESSION["results"] is set, redirect back to order_process.php exit(); } }catch(PPConnectionException $ex) { $ex->getData(); } catch (Exception $ex) { echo $ex->getMessage(); } } ### Display order confirmation if $_SESSION["results"] is set #### if(isset($_SESSION["results"])) { $html = '<!DOCTYPE HTML>'; $html .= '<html>'; $html .= '<head>'; $html .= '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'; $html .= '<title>Order Confirm Details</title>'; $html .= '<style type="text/css">'; $html .= '.transaction_info {margin:0px auto; background:#F2FCFF;; max-width: 750px; color:#555;}'; $html .= '.transaction_info thead {background: #BCE4FA;font-weight: bold;}'; $html .= '.transaction_info thead tr th {border-bottom: 1px solid #ddd;}'; $html .= '</style>'; $html .= '</head>'; $html .= '<body>'; $html .='<div align="center"><h2>Payment Success</h2></div>'; $html .='<div align="center">Please note down your transaction ID, it will be required for further communication!</div>'; $html .= '<table border="0" cellpadding="10" cellspacing="0" class="transaction_info">'; $html .= '<thead><tr><td>Transaction ID</td><td>Date</td><td>Currency</td><td>Amount</td><td>Method</td><td>State</td></tr></thead>'; $html .= '<tbody><tr>'; $html .= '<td>'.$_SESSION["results"]["transaction_id"].'</td>'; $html .= '<td>'.$_SESSION["results"]["transaction_time"].'</td>'; $html .= '<td>'.$_SESSION["results"]["transaction_currency"].'</td>'; $html .= '<td>'.$_SESSION["results"]["transaction_amount"].'</td>'; $html .= '<td>'.$_SESSION["results"]["transaction_method"].'</td>'; $html .= '<td>'.$_SESSION["results"]["transaction_state"].'</td>'; $html .= '</tr>'; $html .= '<tr><td colspan="6"><div align="center"><a href="index.php">Back to Products Page</a></div></td></tr>'; $html .= '</tbody>'; $html .= '</table>'; $html .= '</body>'; $html .= '</html>'; echo $html; unset($_SESSION["results"]); } ?>
It may not be necessary, but don't forget to look into functions.inc.php to understand how it uses PayPal SDK to interact to PayPal. I would love to discuss them here, but it will be out of the scope of this tutorial! You can checkout PayPal REST API Reference for the same.
Download Demo
  • I try the sample code in my localhost. I found the error error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure Pls, kindly advise. What I need to do to fix the error.
  • hello.. I got this error when I run your script Got Http response code 401 when accessing https://api.sandbox.paypal.com/v1/oauth2/token. please help me . thanks
    • Hello, I have the same problem, i changed the way to get the token, i add a value 'signature' in "config.php" and "functions.inc.php" and i got the same error message. If someone can help us, thank you !
  • After successful transaction, does is the order table updated base on item_number or item_name? What I am trying to know is if , we get a new for for each item in transaction.
  • Hi when I did the demo of this page (http://www.sanwebe.com/2014/09/multi-items-payment-with-paypal-rest-api-php), After coming back from Paypal, got this error: Method PayPal\Api\Sale::getTransactionFee() does not exist
    • Download the latest SDK from https://github.com/paypal/PayPal-PHP-SDK/releases/tag/v1.3.0 then replace the "vendor" folder content with the new files Example: /paypal /composer autoload.php
New question is currently disabled!