PayPal Standard Payment integracja w PHP

W tym tutorialu pokażę prostą integrację płatności PayPal w php. PayPal posiada swój Sandbox, w którym deweloperzy mogą testować swoje aplikacje tak jakby działały w normalnym środowisku, przy użyciu wirtualnych transakcji i pieniędzy. Gdy proces płatności PayPal działa poprawnie w środowisku Sandbox, możesz ustawić system PayPal na rzeczywiste środowisko. Najpierw musimy utworzyć konto PayPal w sandboxie. Oczywiście wcześniej musimy mieć zwykłe konto na PayPalu. Jeśli go nie mamy musimy się zarejestrować. W celu stworzenia konta na sandboxie należy wejść na stronę PayPal Developer i zalogować się do swojego konta PayPal.

Logowanie do PayPal Developer Po zalogowaniu kliknij na Dashboard w menu nawigacyjnym. Następnie wybierz z menu po lewej Accounts w sekcji Sandbox. Powinieneś widzieć listę kont z dwoma przykładowymi kontami (możesz je usunąć, bo będziemy tworzyć własne). Możesz utworzyć wiele kont biznesowych (Business) i klienckich (Personal) klikając na przycisk Create Account. Utwórz dwa konta - jedno biznesowe (Business), a drugie klienta (Personal). Kliknij adres e-mail konta, którym chcesz zarządzać, pojawi się link Profil. Jeśli na niego klikniesz pojawią się detale konta umieszczone w kilku zakładkach. Na przykład hasło, dane fikcyjnych kart kredytowych itp. Sandbox - testowe konta Teraz utworzymy bazę danych, w której będziemy przechowywać dane dotyczące produktów i transakcji. W tabeli products będziemy trzymać nasze produkty, a w tabeli payments informacje na temat dokonanych transakcji.
CREATE TABLE `products` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `price` float(10,2) NOT NULL,
 `status` tinyint(1) NOT NULL DEFAULT '1',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;


CREATE TABLE `payments` (
 `payment_id` int(11) NOT NULL AUTO_INCREMENT,
 `item_number` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `txn_id` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `payment_gross` float(10,2) NOT NULL,
 `currency_code` varchar(5) COLLATE utf8_unicode_ci NOT NULL,
 `payment_status` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 PRIMARY KEY (`payment_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Stwórzmy teraz pliki php. Najpierw plik konfiguracyjny, który będziemy załączać w innych plikach. Będą w nim dane dostępowe do bazy danych i połączenie do bazy. Dane te musisz ustawić takie jak do Twojej bazy.
<?php
//Database credentials
$host = 'domena.pl';
$username = 'username';
$pass = 'password';
$db = 'db_name';

//Connect with the database
$db = new mysqli($host, $username, $pass, $db);

//Display error if failed to connect
if ($db->connect_errno) {
    printf("Connect failed: %s\n", $db->connect_error);
    exit();
}
?>
Teraz w pliku index.php wyświetlimy wszystkie produkty z bazy danych. Przy każdym z nich będzie przycisk kup za pomocą PayPal. Dla każdego produktu będzie odpowiedni formularz z ukrytymi polami, w których będziemy przekazywać dane do PayPal.
<?php
session_start();
//Include DB configuration file
include 'config.php';

//Set useful variables for paypal form
$paypalURL = 'https://www.sandbox.paypal.com/cgi-bin/webscr'; //Test PayPal API URL
$paypalID = 'Your merchant sandbox e-mail'; //Business Email

?>
<?php
    //Fetch products from the database
    $results = $db->query("SELECT * FROM products");
    while($row = $results->fetch_assoc()){
?>
    Nazwa: <?php echo $row['name']; ?>
    Cena: <?php echo $row['price']; ?>
    <form action="<?php echo $paypalURL; ?>" method="post">
        <!-- Identify your business so that you can collect the payments. -->
        <input type="hidden" name="business" value="<?php echo $paypalID; ?>">
        
        <!-- Specify a Buy Now button. -->
        <input type="hidden" name="cmd" value="_xclick">
        
        <!-- Specify details about the item that buyers will purchase. -->
        <input type="hidden" name="item_name" value="<?php echo $row['name']; ?>">
        <input type="hidden" name="item_number" value="<?php echo $row['id']; ?>">
        <input type="hidden" name="amount" value="<?php echo $row['price']; ?>">
        <input type="hidden" name="currency_code" value="USD">
        
        <!-- Specify URLs -->
        <input type='hidden' name='cancel_return' value='url_to_file/cancel.php'>
        <input type='hidden' name='return' value='url_to_file/success.php'>
        
        <!-- Display the payment button. -->
        <input type="image" name="submit" border="0"
        src="https://www.paypalobjects.com/pl_PL/i/btn/btn_buynow_LG.gif" alt="PayPal - The safer, easier way to pay online">
        <img alt="" border="0" width="1" height="1" src="https://www.paypalobjects.com/pl_PL/i/scr/pixel.gif" >
    </form>
<?php } ?>
Należy zmienić adres url w inputach return i cancel_return na własne. Upewnij się, że skonfigurowałeś Auto Return for Website Payments na koncie firmowym PayPal. W przeciwnym razie nie otrzymasz informacji o transakcjach z serwisu PayPal w pliku success.php (stworzymy go za chwilę). Jeśli jeszcze tego nie zrobiłeś zaloguj się na firmowe (business) konto w sandboxie. Następnie przejdź do strony Profile > Profile and Settings. Potem wybierz My selling preferences z menu po lewej. Sandbox - Profile My selling preferences Kliknij teraz Update przy opcji Website Preferences. Pojawi się poniższy ekran. Sandbox - Profile Website preferences Ustaw Auto Return na On oraz w pole tekstowe Return url wpisz url do pliku success.php na serwerze. Ponadto ustaw Payment Data Transfer na On. Plik success.php wygląda następująco:
<?php
session_start();
include 'dbConfig.php';

if(isset($_SESSION['info'])) {
	echo $_SESSION['info'];
	unset($_SESSION['info']);
	exit;
}
//Get payment information from PayPal
$item_number = $_GET['item_number']; 
$txn_id = $_GET['tx'];
$payment_gross = $_GET['amt'];
$currency_code = $_GET['cc'];
$payment_status = $_GET['st'];

//Get product price from database
$productResult = $db->query("SELECT price FROM products WHERE id = ".$item_number);
$productRow = $productResult->fetch_assoc();
$productPrice = $productRow['price'];

if(!empty($txn_id) && $payment_gross == $productPrice) {
    //Check if payment data exists with the same TXN ID.
    $prevPaymentResult = $db->query("SELECT payment_id FROM payments WHERE txn_id = '".$txn_id."'");

    if($prevPaymentResult->num_rows > 0){
        $paymentRow = $prevPaymentResult->fetch_assoc();
        $last_insert_id = $paymentRow['payment_id'];
    }else{
        //Insert tansaction data into the database
        $insert = $db->query("INSERT INTO payments(item_number,txn_id,payment_gross,currency_code,payment_status) VALUES('".$item_number."','".$txn_id."','".$payment_gross."','".$currency_code."','".$payment_status."')");
        $last_insert_id = $db->insert_id;
    }
		$info = '<h1>Your payment has been successful. TXN ID: '.$txn_id.' Payment gross: '.$payment_gross.' Product price: '.$productPrice.'</h1>';
} else { 	
	$info = '<h1>Your payment has failed.</h1>'; 
} 
	$_SESSION['info'] = $info;
header('Location: success.php');
 
?>
W pliku cancel.php wpiszmy tylko informację, że transakcja została przerwana.
Your PayPal transaction has been canceled
Aby lepiej zabezpieczyć Standard PayPal Payment, do potwierdzenia transakcji należy użyć PayPal Instant Payment Notification (IPN). Wykonaj poniższe czynności, aby skonfigurować IPN. Najpierw należy włączyć IPN na biznesowym koncie PayPal. Po zalogowaniu przejdź do strony Profile > Profile and Settings. Następnie na My selling preferences z menu po lewej. Kliknij teraz Update przy opcji Instant payment notifications. Pojawi się poniższy ekran. Sandbox - Profile Instant payment notifications Włącz IPN, a w polu tekstowym wpisz adres url do pliku ipn.php. W pliku index.php w formularzu trzeba teraz dodać dodatkowe ukryte pole.
<input type='hidden' name='notify_url' value='url do pliku ipn.php'>
Po włączeniu IPN, PayPal wyśle dane transakcji do adresu Notify URL (ipn.php). Umieść następujący kod w pliku ipn.php, aby sprawdzić poprawność transakcji i wstawić informacje o płatności do bazy danych.
<?php 
//Include DB configuration file
include 'config.php';

/*
 * 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
 */
$paypalURL = "https://www.sandbox.paypal.com/cgi-bin/webscr";
$ch = curl_init($paypalURL);
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_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);

// Set TCP timeout to 30 seconds
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close', 'User-Agent: company-name'));
$res = curl_exec($ch);
/*
 * Inspect IPN validation result and act accordingly
 * Split response headers and payload, a better way for strcmp
 */ 
$tokens = explode("\r\n\r\n", trim($res));
$res = trim(end($tokens));

if (strcmp($res, "VERIFIED") == 0 || strcasecmp($res, "VERIFIED") == 0) {
    
    //Payment data
    $item_number = $_POST['item_number'];
    $txn_id = $_POST['txn_id'];
    $payment_gross = $_POST['mc_gross'];
    $currency_code = $_POST['mc_currency'];
    $payment_status = $_POST['payment_status'];
    
    //Check if payment data exists with the same TXN ID.
    $prevPayment = $db->query("SELECT payment_id FROM payments WHERE txn_id = '".$txn_id."'");
    if($prevPayment->num_rows > 0)
	{
        exit();
    } else
	{
        //Insert tansaction data into the database
        $insert = $db->query("INSERT INTO payments(item_number,txn_id,payment_gross,currency_code,payment_status) VALUES('".$item_number."','".$txn_id."','".$payment_gross."','".$currency_code."','".$payment_status."')");
    }
}
?>