[PayPal IPN] Automatic Shopping System

MayoMayn

BestDev
Oct 18, 2016
1,423
683
Since I've seen so many retarded retros around here, not using any kind of IPN, so that their users have to wait several hours to get what they've paid for, I've decided to release the class and coding I use for my CMS.

Demo:
(I assume that you're using RevCMS, if not change values in the coding so it matches your CMS).

First off, go create a folder called paypal and a file called class.paypal.php inside that folder.
There's a construct function so that if people use some sort of config table or anything related to that, they can easily change their settings inside this class.
Your class.paypal.php should contain this:
PHP:
class PayPalIPN
{
    /**
     * @var bool $use_sandbox     Indicates if the sandbox endpoint is used.
     */
    private $use_sandbox = true;
    /**
     * @var bool $use_local_certs Indicates if the local certificates are used.
     */
    private $use_local_certs = true;
    /**
     * @var string $DIR           Indicates the file directory of this class.
     */
    private $DIR = dirname(__FILE__) . DIRECTORY_SEPARATOR . "app/management/";
    /** Production Postback URL */
    const VERIFY_URI = 'https://ipnpb.paypal.com/cgi-bin/webscr';
    /** Sandbox Postback URL */
    const SANDBOX_VERIFY_URI = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
    /** Response from PayPal indicating validation was successful */
    const VALID = 'VERIFIED';
    /** Response from PayPal indicating validation failed */
    const INVALID = 'INVALID';
 
    /*public function __construct($config) {
        $this->use_sandbox = ($config->paypal('production') ? false : true);

        $this->use_local_certs = ($config->paypal('local_certs') ? true : false);
    }*/

    public function putLog($title='LOG',$data)
    {

        $cur_log = file_get_contents($this->DIR . "logs/paypal.log");
        file_put_contents($this->DIR . "logs/paypal.log", $cur_log . "[PAYPAL {$title} - " . gmdate("D, d M Y H:i:s") . "]\r\n" . "{$data}\r\n\r\n");

    }
    /**
     * Determine endpoint to post the verification data to.
     * @return string
     */
    public function getPaypalUri()
    {
        if($this->use_sandbox) {
            return self::SANDBOX_VERIFY_URI;
        } else {
            return self::VERIFY_URI;
        }

    }
    /**
     * Verification Function
     * Sends the incoming post data back to PayPal using the cURL library.
     *
     * @return bool
     * @throws Exception
     */
    public function verifyIPN()
    {
 
        if(!count($_POST)) {
            return false;
            exit();
        }
        $raw_post_data = file_get_contents('php://input');
        $raw_post_array = explode('&', $raw_post_data);
        $data = [];
        foreach($raw_post_array as $keyval) {
            $keyval = explode('=', $keyval);
            if(count($keyval) == 2) {
                // Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
                if($keyval[0] === 'payment_date') {
                    if(substr_count($keyval[1], '+') === 1) {
                        $keyval[1] = str_replace('+', '%2B', $keyval[1]);
                    }
                }
                $data[$keyval[0]] = urldecode($keyval[1]);
            }
        }
        // Build the body of the verification post request, adding the _notify-validate command.
        $req = 'cmd=_notify-validate';
        foreach($data as $key => $value) {
            if(function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() == 1) {
                $value = urlencode(stripslashes($value));
            } else {
                $value = urlencode($value);
            }
            $req .= "&{$key}={$value}";
        }
        // Post the data back to PayPal, using curl. Throw exceptions if errors occur.
        $ch = curl_init($this->getPaypalUri());
        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);
        // This is often required if the server is missing a global cert bundle, or is using an outdated one.
        if($this->use_local_certs) {
            curl_setopt($ch, CURLOPT_CAINFO, $this->DIR . "paypal/cacert.pem");
        }
        curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HTTPHEADER, ['Connection: Close']);
        $res = curl_exec($ch);
        if(!($res)) {
            $errno = curl_errno($ch);
            $errstr = curl_error($ch);
            curl_close($ch);
            $this->putLog("ERROR", "cURL error: [{$errno}] {$errstr}");
        }
        $info = curl_getinfo($ch);
        $http_code = $info['http_code'];
        if($http_code != 200) {
            $this->putLog("ERROR", "PayPal responded with http code {$http_code}");
        }
        curl_close($ch);
        // Check if PayPal verifies the IPN data, and if so, return true.
        if($res == self::VALID) {
            return true;
        } else {
            return false;
        }

    }

}

Then after this go and run this SQL query to add the required table:
Code:
DROP TABLE IF EXISTS `cms_transactions`;
CREATE TABLE `cms_transactions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `refunded` enum('0','1') DEFAULT '0',
  `gateway` enum('stripe','paypal','braintree') DEFAULT NULL,
  `transaction_id` varchar(25) NOT NULL,
  `amount` char(7) NOT NULL,
  `package` int(1) NOT NULL,
  `email` varchar(255) DEFAULT NULL,
  `nonce` varchar(255) NOT NULL,
  `purchase_date` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

Then on your shop page, go add this at the very top of your .php file:
PHP:
/**
 * Set cache headers to prevent the user from doing a duplicate of form submission
 */
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

//header("HTTP/1.1 200 OK");

require dirname(__FILE__) . DIRECTORY_SEPARATOR . "app/management/paypal/class.paypal.php";

use PayPalIPN;

$ipn = new PayPalIPN();

$orderPlaced = true;

// Set the response array
$response = ['status' => 'false', 'title' => 'Payment Error', 'message' => ''];

$verified = $ipn->verifyIPN();
if($verified) {
    $username = $users->getUsername($uid);

    // Payment has been verified, so get details
    $payer_email  = $_POST['payer_email'];
    $verify_sign  = $_POST['verify_sign'];
    $txn_id       = $_POST['txn_id'];
    $payment_date = $_POST['payment_date'];
    $package      = $_POST['item_number'];
    $amount       = $_POST['payment_gross'];
    $item_name    = $_POST['item_name'];

    // Make sure the transaction id has not already been used
    $stmt = $db->query("SELECT `id` FROM `cms_transactions` WHERE `transaction_id` = :tid LIMIT 1");
    $stmt->bindParam(':tid', $txn_id, $db->PARAM_STR);
    $stmt->execute();

    if($stmt->rowCount() < 1) {
        // If the transaction id has not been used, match the package id against the one in db.
        $stmt = $db->query("SELECT `diamonds` FROM `cms_diamond_packages` WHERE `id` = :id AND `price` = :a LIMIT 1");
        $stmt->bindParam(':id', $package, $db->PARAM_INT);
        $stmt->bindParam(':a', $amount, $db->PARAM_STR);
        $stmt->execute();
 
        if($stmt->rowCount() > 0) {
            $diamonds = $stmt->fetchColumn();
    
            // Payment has been successfully placed, insert it into the transactions table.
            $stmt = $db->query("INSERT INTO `cms_transactions` (`user_id`,`gateway`,`transaction_id`,`amount`,`package`,`email`,`nonce`,`purchase_date`) VALUES (:uid,:g,:tid,:a,:p,:e,:n,:d)");
            $data = [
                ':uid' => $_SESSION['user']['id'],
                ':g'   => 'paypal',
                ':tid' => $txn_id,
                ':a'   => $amount,
                ':p'   => $package,
                ':e'   => $payer_email,
                ':n'   => $verify_sign,
                ':d'   => NULL
            ];
            $stmt->execute($data);
    
            // Give user paid currencies or VIP
            $users->updateUser($uid, 'diamonds', ($users->getUserInfo($uid, 'diamonds') + $diamonds));
            if(!$users->hasBadge($uid, 'SDB01')) // If user doesn't have badge give it to them
                $users->giveUserBadge($uid, 'SDB01');
 
            // Set response to success
            $response['status']  = "true";
            $response['title']   = "Payment Success";
            $response['message'] = 'You have successfully bought '.$diamonds.' <img src="'.$url.'/static/img/diamonds.png" />.<br /> You can buy all sorts of things with diamonds. Visit our other shops to buy credits, duckets, rares, boxes, VIP etc.';
    
            // Put log as success
            $ipn->putLog("SUCCESS", json_encode($_POST, true));
            $ipn->putLog("SUCCESS", "User {$username} has successfully bought package {$package} containing {$diamonds} with a price of {$amount} using payer email {$payer_email} on {$payment_date} with ID: {$txn_id}");
        } else {
            // Put log as the user trying to exploit the system by changing price for a package
            $response['message'] = "Package ID doesn't match its pricetag: {$verified}";
            $ipn->putLog("ERROR", "User {$username} tried to spoof {$package} with price {$amount} using transaction id {$txn_id} with payer email {$payer_email} on {$payment_date}.");
        }
    } else {
        // User tried to duplicate this package, set the response and put the error log
        $response['message'] = "You have already bought a package using this Transaction ID once <b>{$txn_id}</b>.";
        $ipn->putLog("ERROR", "User {$username} tried to duplicate package {$package} using transaction id {$txn_id} with payer email {$payer_email} on {$payment_date}.");
    }
} else {
    $orderPlaced = false;
}

So, when someone clicks on a buy button, they should be redirected to PayPal with the right values, that is where this comes in handy:
PHP:
<?php echo '<form action="'.$ipn->getPaypalUri().'" method="post" accept-charset="utf8">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="lc" value="US">
<input type="hidden" name="currency" value="currency they're paying in">
<input type="hidden" name="item_name" value="title of what they're buying">
<input type="hidden" name="business" value="Your PayPal Business Email">
<input type="hidden" name="amount" value="price of what they're buying">
<input type="hidden" name="item_number" value="package id of what they're buying">
<input type="hidden" name="return" value="enter url here that returns to your shop page">
<input type="hidden" name="cancel_url" value="enter url here that returns to your shop page">
<input type="hidden" name="rm" value="2">
<input type="hidden" name="quantity" value="1">
<button style="margin:3px 3px 3px;float:left;padding:.375rem 0.9rem;" type="submit" class="btn btn-green" name="submit">
     <big>_diamonds_ <img src="'.$url.'/static/img/diamonds.png" /></big>
     <br />
     <small>_price_ '.$currency.'</small>
</button>
</form>'; ?>

To check if a process has been made, you can print to the user the response details as shown below:
PHP:
<?php if($orderPlaced): ?>
<script type="text/javascript">
window.alert("Title: <?php echo $response['title']; ?>, Response: <?php echo $response['message']; ?>");
</script>
<?php endif; ?>

Getting the cacert.pem copy the data from here:
Aint gonna be too specific about this, so if you got any questions write a comment.

* There will be added a feature, so that every times a user visits client, it checks for current existing transactions made by that user, to be sure that the user haven't refunded the payment, and if they have, it will automatically ban them.
 
Last edited:

MayoMayn

BestDev
Oct 18, 2016
1,423
683


Regardless, this will step help the newbies
Wouldn't really help lol, what's the point of linking PayPal's class, when I just modified it? There's not really any less or more explanation on that one.
Wrong link to give the rookies.
 

MayoMayn

BestDev
Oct 18, 2016
1,423
683
Good Job, this will help out a lot of the new hotels.
(or people who dont know how to google).
If half the people on this forum knew how to Google, their hotels wouldn't have so many flaws and exploits lol.
Instead of just believing what everybody tells you, they should research and find out for themselves instead of just being a bunch of naive fools.

Sent from my SM-G928F using Tapatalk
 

MasterJiq

Member
Jul 8, 2016
385
23
I've converted it to my own cms. Now I get this error when I am on Paypal
We cannot process this transaction because there is a problem with the PayPal email address supplied by the seller. Please contact the seller to resolve the problem. If this payment is for an eBay listing, you can contact the seller via the "Ask Seller a Question" link on the listing page. When you have the correct email address, payment can be made at .

Your purchase couldn't be completed
There's a problem with the merchant's PayPal account. Please try again later.
 

MayoMayn

BestDev
Oct 18, 2016
1,423
683
I've converted it to my own cms. Now I get this error when I am on Paypal
Post your code. Did you configure your PayPal account settings?

EDIT:
If you look REALLY closely, it is RIGHT IN FRONT OF YOUR EYES.
We cannot process this transaction because there is a problem with the PayPal email address supplied by the seller.
 
Last edited:

MasterJiq

Member
Jul 8, 2016
385
23
Post your code. Did you configure your PayPal account settings?

EDIT:
If you look REALLY closely, it is RIGHT IN FRONT OF YOUR EYES.
We cannot process this transaction because there is a problem with the PayPal email address supplied by the seller.
I don't find any email actually, just find " payer_mail " ?
Code:
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

//header("HTTP/1.1 200 OK");

use PayPalIPN;

$ipn = new PayPalIPN();

$orderPlaced = true;

// Set the response array
$response = ['status' => 'false', 'title' => 'Payment Error', 'message' => ''];

$verified = $ipn->verifyIPN();
if($verified) {
    $username = Users::$Session->Name;

    // Payment has been verified, so get details
    $payer_email  = $_POST['payer_email'];
    $verify_sign  = $_POST['verify_sign'];
    $txn_id       = $_POST['txn_id'];
    $payment_date = $_POST['payment_date'];
    $package      = $_POST['item_number'];
    $amount       = $_POST['payment_gross'];
    $item_name    = $_POST['item_name'];

    // Make sure the transaction id has not already been used
    $stmt = CMS::$MySql->Query("SELECT `id` FROM `cms_transactions` WHERE `transaction_id` = :tid LIMIT 1");
    $stmt->bindParam(':tid', $txn_id, $db->PARAM_STR);
    $stmt->execute();

    if($stmt->rowCount() < 1) {
        // If the transaction id has not been used, match the package id against the one in db.
        $stmt = CMS::$MySql->Query("SELECT `diamonds` FROM `cms_diamond_packages` WHERE `id` = :id AND `price` = :a LIMIT 1");
        $stmt->bindParam(':id', $package, $db->PARAM_INT);
        $stmt->bindParam(':a', $amount, $db->PARAM_STR);
        $stmt->execute();
 
        if($stmt->rowCount() > 0) {
            $diamonds = $stmt->fetchColumn();
    
            // Payment has been successfully placed, insert it into the transactions table.
            $stmt = CMS::$MySql->Query("INSERT INTO `cms_transactions` (`user_id`,`gateway`,`transaction_id`,`amount`,`package`,`email`,`nonce`,`purchase_date`) VALUES (:uid,:g,:tid,:a,:p,:e,:n,:d)");
            $data = [
                ':uid' => Users::$Session->Data['id'],
                ':g'   => 'paypal',
                ':tid' => $txn_id,
                ':a'   => $amount,
                ':p'   => $package,
                ':e'   => $payer_email,
                ':n'   => $verify_sign,
                ':d'   => NULL
            ];
            $stmt->execute($data);
    
            // Give user paid currencies or VIP
            $users->updateUser($uid, 'diamonds', ($users->getUserInfo($uid, 'diamonds') + $diamonds));
            if(!$users->hasBadge($uid, 'SDB01')) // If user doesn't have badge give it to them
                $users->giveUserBadge($uid, 'SDB01');
 
            // Set response to success
            $response['status']  = "true";
            $response['title']   = "Payment Success";
            $response['message'] = 'You have successfully bought '.$diamonds.' <img src="'.$url.'/static/img/diamonds.png" />.<br /> You can buy all sorts of things with diamonds. Visit our other shops to buy credits, duckets, rares, boxes, VIP etc.';
    
            // Put log as success
            $ipn->putLog("SUCCESS", json_encode($_POST, true));
            $ipn->putLog("SUCCESS", "User {$username} has successfully bought package {$package} containing {$diamonds} with a price of {$amount} using payer email {$payer_email} on {$payment_date} with ID: {$txn_id}");
        } else {
            // Put log as the user trying to exploit the system by changing price for a package
            $response['message'] = "Package ID doesn't match its pricetag: {$verified}";
            $ipn->putLog("ERROR", "User {$username} tried to spoof {$package} with price {$amount} using transaction id {$txn_id} with payer email {$payer_email} on {$payment_date}.");
        }
    } else {
        // User tried to duplicate this package, set the response and put the error log
        $response['message'] = "You have already bought a package using this Transaction ID once <b>{$txn_id}</b>.";
        $ipn->putLog("ERROR", "User {$username} tried to duplicate package {$package} using transaction id {$txn_id} with payer email {$payer_email} on {$payment_date}.");
    }
} else {
    $orderPlaced = false;
}
 ?>
 

MayoMayn

BestDev
Oct 18, 2016
1,423
683
I don't find any email actually, just find " payer_mail " ?
Code:
<?php
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
header("Cache-Control: no-store, no-cache, must-revalidate");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");

//header("HTTP/1.1 200 OK");

use PayPalIPN;

$ipn = new PayPalIPN();

$orderPlaced = true;

// Set the response array
$response = ['status' => 'false', 'title' => 'Payment Error', 'message' => ''];

$verified = $ipn->verifyIPN();
if($verified) {
    $username = Users::$Session->Name;

    // Payment has been verified, so get details
    $payer_email  = $_POST['payer_email'];
    $verify_sign  = $_POST['verify_sign'];
    $txn_id       = $_POST['txn_id'];
    $payment_date = $_POST['payment_date'];
    $package      = $_POST['item_number'];
    $amount       = $_POST['payment_gross'];
    $item_name    = $_POST['item_name'];

    // Make sure the transaction id has not already been used
    $stmt = CMS::$MySql->Query("SELECT `id` FROM `cms_transactions` WHERE `transaction_id` = :tid LIMIT 1");
    $stmt->bindParam(':tid', $txn_id, $db->PARAM_STR);
    $stmt->execute();

    if($stmt->rowCount() < 1) {
        // If the transaction id has not been used, match the package id against the one in db.
        $stmt = CMS::$MySql->Query("SELECT `diamonds` FROM `cms_diamond_packages` WHERE `id` = :id AND `price` = :a LIMIT 1");
        $stmt->bindParam(':id', $package, $db->PARAM_INT);
        $stmt->bindParam(':a', $amount, $db->PARAM_STR);
        $stmt->execute();
 
        if($stmt->rowCount() > 0) {
            $diamonds = $stmt->fetchColumn();
    
            // Payment has been successfully placed, insert it into the transactions table.
            $stmt = CMS::$MySql->Query("INSERT INTO `cms_transactions` (`user_id`,`gateway`,`transaction_id`,`amount`,`package`,`email`,`nonce`,`purchase_date`) VALUES (:uid,:g,:tid,:a,:p,:e,:n,:d)");
            $data = [
                ':uid' => Users::$Session->Data['id'],
                ':g'   => 'paypal',
                ':tid' => $txn_id,
                ':a'   => $amount,
                ':p'   => $package,
                ':e'   => $payer_email,
                ':n'   => $verify_sign,
                ':d'   => NULL
            ];
            $stmt->execute($data);
    
            // Give user paid currencies or VIP
            $users->updateUser($uid, 'diamonds', ($users->getUserInfo($uid, 'diamonds') + $diamonds));
            if(!$users->hasBadge($uid, 'SDB01')) // If user doesn't have badge give it to them
                $users->giveUserBadge($uid, 'SDB01');
 
            // Set response to success
            $response['status']  = "true";
            $response['title']   = "Payment Success";
            $response['message'] = 'You have successfully bought '.$diamonds.' <img src="'.$url.'/static/img/diamonds.png" />.<br /> You can buy all sorts of things with diamonds. Visit our other shops to buy credits, duckets, rares, boxes, VIP etc.';
    
            // Put log as success
            $ipn->putLog("SUCCESS", json_encode($_POST, true));
            $ipn->putLog("SUCCESS", "User {$username} has successfully bought package {$package} containing {$diamonds} with a price of {$amount} using payer email {$payer_email} on {$payment_date} with ID: {$txn_id}");
        } else {
            // Put log as the user trying to exploit the system by changing price for a package
            $response['message'] = "Package ID doesn't match its pricetag: {$verified}";
            $ipn->putLog("ERROR", "User {$username} tried to spoof {$package} with price {$amount} using transaction id {$txn_id} with payer email {$payer_email} on {$payment_date}.");
        }
    } else {
        // User tried to duplicate this package, set the response and put the error log
        $response['message'] = "You have already bought a package using this Transaction ID once <b>{$txn_id}</b>.";
        $ipn->putLog("ERROR", "User {$username} tried to duplicate package {$package} using transaction id {$txn_id} with payer email {$payer_email} on {$payment_date}.");
    }
} else {
    $orderPlaced = false;
}
 ?>
You miss the redirect form button?

Sent from my SM-G928F using Tapatalk
 

MayoMayn

BestDev
Oct 18, 2016
1,423
683
What do you mean ?
I can see you havent even integrated it properly for your own system. If you look at the bottom off my thread, there'a a FORM that redirects to PayPal with the REQUIRED data for a payment.

Sent from my SM-G928F using Tapatalk
 

MayoMayn

BestDev
Oct 18, 2016
1,423
683
Yes, I clicked the link from you give and get the current error. lol
Have you read the error? Its not really my issue, when its something I cannot fix. Are you using sandbox or production mode? Because your production email doesnt work as your sandbox email. You should read their documentation, its not really that hard to setup.

Give me a link to your shopping page.

Sent from my SM-G928F using Tapatalk
 

KylePr0zZ

Member
Jul 22, 2016
51
14
I use RevCMS and I have a Buy Diamonds page If I put the code at the very top of it and refresh the page it goes white :/
 

KylePr0zZ

Member
Jul 22, 2016
51
14
Yeah obviously, because it doesn't work unless you edit it to work with your CMS.
I wrote that at the very top of the thread :)
It was very misleading lol, it says if you use RevCMS it will be okay but if you use another you will have to do it for your cms
I currently use RevCMS!
 

Sledmore

Chaturbate Livestreamer
Staff member
FindRetros Moderator
Jul 24, 2010
5,194
3,901
Very good contribution. It's a shock to see most people still not using an IPN.

Props to you for pointing them into the right direction.
 

Users who are viewing this thread

Top