4

I have this simple page where people can buy a rank for $24.99. However, they must enter their username and their favNum. These 2 user inputs get validated by the is_favnum_valid() and is_username_valid() functions. If any of the inputs are not valid, then the page is reloaded again with an error message showing. However, if the input is valid, then we redirect the customer to PayPal, where they pay via PayPal.

My question is, on line 47 (the if ($errorMsg == "") if statement block). I build the link to redirect the customer to. Is that safe? If its not safe, what should I do to redirect the customer so they can pay for the item. It is important for me somehow save the username and favNum into the database if the customer has paid for the item successfully.

My Concern is that it is exposing my listener URL http://example.com/ipn.php, its exposing all the settings I am using to make the order, people can change the custom field and the fact people can just make up their own links/orders up and try and purchase items by just visiting this link:

https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&item_name=Premium rank&item_number=10&amount=24.99&currency_code=USD&business=test@gmail.com&no_shipping=1&custom=coolusername:5&no_note=0&return=http://example.com/success.php&cancel_return=http://example.com/cancel.phpl&notify_url=http://example.com/ipn.php

I also use the custom field in this PayPal request to store the favNum and username. So when my PayPal listener hears that the payment was successful, it will add the transaction details into my database, as well as the username and favNum PayPal sends you in the custom field when you are listening.


shop.php

<?php

$errorMsg = "";

// Checks if the username is valid.
// A valid username only contains letters
// @return boolean - true of username is valid otherwise false
function is_username_valid($username) {
    if (ctype_alpha($username)) {
        return true;
    }

    return false;
}

// Checks if the favNum is valid.
// A valid favNum must be an integer
// @return boolean - true of favNum is valid otherwise false
function is_favnum_valid($favNum) {
    if (ctype_digit($favNum)) {
        return true;
    }

    return false;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    // Check if username and favNum is set
    if (!isset($_POST["username"]) || !isset($_POST["favNum"])) {
        $errorMsg = "Please enter a username and your Favorite number<br>";
    } else {
        // Check if username is not valid
        if (!is_username_valid($_POST["username"])) {
            $errorMsg .= "Username must be letters only<br>";
        }

        // Check if favNum is not valid
        if (!is_favnum_valid($_POST["favNum"])) {
            $errorMsg .= "Favorite number must be an integer<br>";
        }
    }

    // If there has been no errors with the user's input
    // redirect to paypal page so user can pay
    if ($errorMsg == "") {

        $query = array();
        $query['cmd'] = '_xclick';
        $query['item_name'] = 'Premium Rank';
        $query['item_number'] = 2;
        $query['amount'] = '24.99';
        $query['currency_code'] = 'USD';
        $query['business'] = 'test@gmail.com';
        $query['no_shipping'] = '1';
        $query['custom'] = $_POST["username"] . ":" . $_POST["favNum"];
        $query['no_note'] = 0;
        $query['return'] = 'http://example.com/return.php';
        $query['cancel_return'] = 'http://example.com/cancel.php';
        $query['notify_url'] = 'http://example.com/ipn.php';

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

    }



}

?>

<!DOCTYPE html>
<html>
<body>

<p>Buy premium rank for $24.95</p>

<?php

if ($errorMsg != "") {
    echo ('<p style="color: red;">Errors:<br>' . $errorMsg . '</p>');
}

?>

<form method="POST" action="shop.php">
  Enter Your Username:<br>
  <input type="text" name="username" placeholder="Letters only">
  <br>
  Enter Your Favorite Number:<br>
  <input type="text" name="favNum" placeholder="Integer only">
  <br><br>
  <input type="submit" value="Buy">
</form> 

</body>
</html>
Mary
  • 127
  • 1
  • 6
  • One way is to display a form that contains a single button and a collection of hidden fields which contain the actual order information. This form uses the `post` method instead of `get`. See step 3 for an example - https://code.tutsplus.com/tutorials/creating-a-paypal-payment-form--net-6 – waterloomatt Aug 12 '19 at 19:01
  • @waterloomatt I don't think you understood the question? If I had them as hidden fields, people can still see my paypal IPN url and all my order settings – Mary Aug 13 '19 at 11:15
  • There shouldn't be anything sensitive in the form body. This is probably beyond the scope of your question, but the IPN *should* be publicly accessible since this is where PayPal sends back the status of the payment. If you're concerned amount tampering then you need to do validation when the IPN response is returned to you. Here's an example workflow - https://stackoverflow.com/a/19729378/296555. I have to scratch my head why PayPal didn't implement a hash check like other processors do. – waterloomatt Aug 13 '19 at 11:45
  • To answer your question directly, post via a link or posting via a form with hidden fields is equally (in)secure. The latter will only slow down the most rudimentary attempts to post invalid payments. Some people implement an automatic redirect but again, this only stops the little old lady whose buying new sewing needles. You need to validate the response from PayPal when the IPN response comes back. The major caveat here is that the payment has already been processed by the time the response comes back so you'll need to handle that. – waterloomatt Aug 13 '19 at 11:51
  • Final note - as an alternative to express checkout, you might want to look into `encrypted buttons` as a way to secure your checkout process. Here's a decent SO question/answer - https://stackoverflow.com/q/6322247/296555 – waterloomatt Aug 13 '19 at 11:59
  • @waterloomatt thank you for your answer. Do you have any good tutorials for the IPN listener. I think I need to make the listener really secure with lots of if statements checking the details of the paypal transaction. Then I won't have to care about the form – Mary Aug 13 '19 at 22:04
  • @waterloomatt Also I have a question. How did this person https://stackoverflow.com/questions/6322247/dynamic-paypal-button-generation-isnt-it-very-insecure not put a `notify_url` field in their form. How will paypal know where to send the information of the transaction – Mary Aug 13 '19 at 22:10
  • @waterloomatt Also third question, where can I find more form fields like `$query['no_note'] = 0;` Is there a place where I can see what parameters I can set and what the parameters actually mean? – Mary Aug 13 '19 at 22:20
  • @waterloomatt I took a look at the encrypted button link you sent. Does this mean on every page load I generate a new encryption or can I just use the same encryption every time so I don't need to keep calling paypal over and over again – Mary Aug 13 '19 at 22:24

0 Answers0