PayPal

PayPal

Introduction

While creating commercial web-sites there appears a problem how to receive payments. PayPal is one of the most famous payment systems in the world. The main advantages of that system are high reliability, simplicity of using and account creating. If you want to open an account you must have a credit card or account in the American bank. Strict privacy policy is considered to be the main disadvantage. But according to the practice if you follow all the rules problems can appear rarely.

Types of payment

PayPal has several types of payment:

  • Payment in the Shopping Cart. All the support operations PayPal takes upon itself
  • Purchase “by one click” without adding the good to the Shopping Cart. That method can be also adjusted for paying in the Shopping Cart created without using PayPal.
  • Recurring billing or subscription

The paying process

The paying process is very simple. You have to create a POST-form with the set of hidden fields, which include information about good (identifier, name, and price) and form sending button. Price must consist of two signs after a dot. For example: “10.00”. After the form has been sent the customer goes to the paypal.com and finishes the paying process. The form data should be sent to https://www.paypal.com/cgi-bin/webscr.

Purchase “by one click”

The code of the simple form:

<form method="post" action= "https://www.paypal.com/cgi-bin/webscr">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="business" value="my@email.com">
<input type="hidden" name="item_name" value="Item name">
<input type="hidden" name="item_number" value="1234">
<input type="hidden" name="amount" value="19.95">
<input type="hidden" name="no_shipping" value="1">
<input type="submit" value="Buy Now">
</form>

Description of the main parameters.

Parameter Description
cmd Obligatory parameter. Should have value "_xclick"
business parameter of the e-mail seller
item_number Commodity identifier. That value won’t be shown customer. It will be transmitted to your script after the confirming the transaction.
item_name Commodity name that will be shown to the customer.
no_shipping Don’t ask shipping address. “1” – don’t ask address, “0” – ask address
return URL, buyer will be directed to. If that parameter isn’t set, buyer will leave at the PayPal site.
rm That parameter defines the result information of the transaction. "1" – none parameters won’t be transmitted. "2" – POST method will be used. "0" – GET method will be used. By default "0".
cancel_return URL, buyer will be directed to if the payment has been canceled. If that parameter isn’t set, buyer will leave at the PayPal site.
notify_url URL, where PayPal transmits the information about transaction (IPN). If that parameter isn’t set value in the account settings will be used.
custom Value of that field doesn’t take part in the paying process. It will be transmitted to your script after transaction confirmation
invoice Is used for account number transmitting. That parameter isn’t obligatory but if you transmit it, it should be unique for every transaction.
amount The payment sum. If that parameter isn’t set buyer can enter the sum himself (used for donations)
currency_code Currency code. Possible meanings: "USD","EUR","GBP","YEN","CAD". By default "USD"

Subscription

PayPal gives the opportunity to organize the subscription. Money will be periodically transmitted from the client’s account to yours one. Client can cancel the subscription anytime. You can also set the subscription periodicity and cost. Also you can set trial period, thus client can make sure of the quality of your services. Trial period can be either chargeable or free.

Form example:

<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_xclick-subscriptions">
<input type="hidden" name="business" value=  "my@email.com">
<input type="hidden" name="item_name" value="Baseball Hat Monthly">
<input type="hidden" name="item_number" value="123">
<input type="hidden" name="no_shipping" value="1">
<input type="hidden" name="a1" value="0">
<input type="hidden" name="p1" value="1">
<input type="hidden" name="t1" value="W">
<input type="hidden" name="a2" value="5.00">
<input type="hidden" name="p2" value="2">
<input type="hidden" name="t2" value="M">
<input type="hidden" name="a3" value="50.00">
<input type="hidden" name="p3" value="1">
<input type="hidden" name="t3" value="Y">
<input type="hidden" name="src" value="1">
<input type="hidden" name="sra" value="1">
<input type="hidden" name="srt" value="5">
<input type="submit" value="Subscribe">
</form>
Parameter Description
cmd Obligatory parameter. It must have "_xclick-subscriptions" value
business Obligatory parameter of the e-mail seller
item_number Commodity identifier. That value won’t be shown the customer but it will be transferred to your script if transaction has been confirmed.
item_name Commodity name that will be shown the customer
no_shipping Don’t ask shipping address. “1” – don’t ask address, “0” – ask address
return URL, customer will go to after the successful payment. If that parameter isn’t set, buyer will leave at the PayPal site.
rm That parameter defines the result information of the transaction. "1" – none parameters won’t be transmitted. "2" – POST method will be used. "0" – GET method will be used. By default "0".
cancel_return URL, buyer will be directed to if the payment has been canceled. If that parameter isn’t set, buyer will leave at the PayPal site.
notify_url URL, where PayPal transmits the information about transaction (IPN). If that parameter isn’t set value in the account settings will be used.
custom Value of that field doesn’t take part in the paying process. It will be transmitted to your script after transaction confirmation
invoice Is used for account number transmitting. That parameter is not obligatory but if you transmit it, it should be unique for every transaction.
a1 First trial period cost. Its value can be “0”. In that case, trial period is free.
p1 First trial period time. If you don’t give the trial period don’t set that parameter
t1 First trial period time unit. Possible values are “D” – days, “W” – weeks, “M”- moths, “Y” – years. If you don’t give the trial period don’t set that parameter
a2 Second trial period cost. If you don’t give the trial period don’t set that parameter
p2 Second trial period time. If you don’t give the trial period don’t set that parameter
t2 Second trial period time unit. Possible values are “D” – days, “W” – weeks, “M”- moths, “Y” – years. If you don’t give the trial period don’t set that parameter
a3 Cost of the main subscription cycle. Obligatory parameter
p3 Time of the main subscription cycle. Obligatory parameter
t3 Unit of the time of the main subscription cycle. Obligatory parameter. Possible values are “D” – days, “W” – weeks, “M”- moths, “Y” – years.
src Periodical payments. “1” – payments will be repeated periodically. If that parameter isn’t set payment will be arranged once.
sra If that’s parameter value is “1” and the attempt of money transfer isn’t successful (for example, if there isn’t enough money) there will be 2 more attempts. After 3 unsuccessful attempts, subscription will be automatically canceled. If that parameter isn’t set subscription will be canceled after first unsuccessful attempt
srt The amount of the subscription cycles. If that parameter is set subscription will be canceled after indicated amount of the cycles.
modify Modification opportunity. Possible values are:
  • “0” – form is meant for new subscription creating
  • “1” – form is meant for changing parameters of the existent subscription
  • “2” – form is meant for new subscription creating as well as for changing parameters of the existent subscription
    • Default value is “0”
usr_manage Automatic password and username generation. Set “1” if you want PayPal to generate password and username
currency_code Currency code. Possible meanings: "USD","EUR","GBP","YEN","CAD". By default "USD"

IPN

IPN (Instant Payment Notification) is the PayPal technology that allows payment process automatizing. Special script is created on the seller server and if there appears some events related to the sellers account such as payment, payment cancellation, subscription creating or cancellation etc. PayPal server sends IPN-POST query with transaction information to that script. So, buyer has finished paying. With some delay PayPal server sends set in the account settings notify_url to the IPN script. Correctly written IPN script is a key that provides payments safety. At first script should be sure that it was stimulated by the PayPal server. It should create POST query to https://www.paypal.com/cgi-bin/webscr, having transmitted all received variables without any changing with adding the parameter cmd with _notify-validate value. It will get VERIFIED in the case of successful transaction verification or INVALID in the case of mistake. If script gets INVALID answer it should finish working.

Then the recipient of that payment should be checked because malefactor can change form, and in that case payment will be transmitted to his account. The recipient is characterized by business and receiver_email variables. The necessity of two variables can be explained that PayPal allows registering several e-mail addresses for one account. E-mail mentioned during account creation is primary email. Receiver_email is always primary email. If payment has been sent to the extra email, it is transmitted via business. If business and receiver_email don’t have an expected value, script finishes working.

Now we have to check the sum and currency of the payment. Such checking is very important because malefactor can change the sum in the form without any problems. Also, you have to check all the parameters of the subscription (presence, duration and cost of the trial periods; duration and cost of the main subscription cycle etc.)

IPN for the same transaction can be sent more than once. For example, if the payment has delayed, first IPN will be sent straight away after the payment. After payment fulfilling or cancellation there will be sent second IPN. If your IPN script hasn’t returned the HTTP status 200, PayPal will send IPN after a time. First sending will be in 10 minutes, then if necessary in 20 minutes, then in 40, 80 and so on. If the expected answer isn’t got in 4 days attempts will be stopped.

As we can see from return, rm and notify_url parameters description, IPN can be transferred to two scripts indicated in return and notify_url parameters. There are two differences:

  • IPN for return will be sent only once after payment. notify_url can be called several times.
  • User will see return script output. notify_url isn’t outputted to the user’s browser.

Information about transaction is situated in the POST variables. The most used variables:

Parameter Description
txn_id The unique transaction number
payment_date Payment date in the format "18:30:30 Jan 1, 2000 PST"
payer_email Buyer’s e-mail
business Seller's e-mail
payer_id The unique buyer identifier. PayPal users are identified by e-mail address. But there is an opportunity to change the e-mail, that’s why it is better to use payer_id
item_number Commodity identifier
item_name Commodity name
txn_type Transaction type. Possible values:

"web_accept" – paid by using "Buy Now" button

"cart" – paid by using PayPal shopping Cart

"send_money" - paid by using “Send money" function

"reversal" – money was returned to the customer

payment_status Payment status. Possible values:

"Completed" – transaction is successful and money has been transmitted. txn_type="reversal" means that money was returned.

"Pending" – payment is delayed. The reason of delay is in the pending_reason variable.

"Failed" – payment failed. It can happen if it has been paid from the bank account.

"Denied" - payment has been denied by the seller.

"Refunded" – money has been returned

pending_reason Pending reason. Possible values:

"echeck" – the bill was paid by the echeck

"multi_currency" – the bill was paid in currency that isn’t indicated in the seller’s account settings.

"intl" – seller doesn’t live in USA. The payment will be fulfilled after seller’s confirmation.

"verify" – seller’s account has "unverified" status.

"upgrade" – bill was paid by the credit card and sellers account has "Personal" status.

"unilateral" – seller’s e-mail isn’t registered in the system

"other" – another reason. Seller has to contact with support service.

payment_type Payment type. Possible values:

"echeck" – paid by echeck

"instant" – paid by credit card, from the bank account or by using money on the PayPal account of the user

mc_gross Payment sum
mc_fee Fee sum
mc_currency Currency
first_name Customer’s first name
last_name Customer’s last name
address_street Street
address_city City
address_state State
address_zip Zip code
address_country Country
verify_sign Sign

Additional variables used with subscription


Parameter Description
txn_type Transaction type. Possible values:

"subscr_signup" – subscription

"subscr_cancel" - subscription cancellation

"subscr_failed" – unsuccessful payment

"subscr_payment" – successful payment

"subscr_modify" – subscription modifying

subscr_date Subscription date or subscription cancellation
subscr_effective Date of the subscription parameters changing taking effect
period1 First trial period time “4 D" - 4 days, "2 W" - 2 weeks "1 M" - 1 month etc.
period2 Second trial period time
period3 Time of the main cycle of the subscription
mc_amount1 First trial period cost
mc_amount2 Second trial period cost
mc_amount3 Cost of the main cycle of the subscription
mc_currency Currency
recurring Indicator of the recurring payments
reattempt Shows sra parameter value
recur_times Shows srt parameter value
retry_at In the case of unsuccessful payment (txn_type=subscr_failed) has a date of the next attempt
username Automatically generated username
password Automatically generated password
subscr_id Subscriber’s ID

Following table shows what variables are transferred for different IPN:


 
Transaction type
Variable
Subscription
Subscription cancellation
Subscription modifying
Payment
Unsuccessful payment
Subscription completion
business X X X X X X
receiver_email X X X X X X
item_name X X X X X X
item_number X X X X X X
invoice X X X X X X
custom X X X X X X
payment_status       X    
pending_reason       X    
payment_date       X    
txn_id       X    
txn_type subscr_signup subscr_cancel subscr_modify subscr_payment subscr_failed subscr_eot
mc_gross       X    
mc_fee       X    
mc_currency X X X X X X
first_name X X X X X X
last_name X X X X X X
address_street X X X X    
address_city X X X X    
address_state X X X X    
address_zip X X X X    
address_country X X X X    
payer_email X X X X X X
payer_id X X X X X X
payment_type       X    
subscr_date X X X      
subscr_effective     X      
period1 X X X      
period2 X X X      
period3 X X X      
mc_amount1 X X X      
mc_amount2 X X X      
mc_amount3 X X X      
recurring X X X      
reattempt X X X      
recur_times X X X      
retry_at         X  
username X X X X X X
password X X X X X X
subscr_id X X X X X X

Scripts examples

I’ll show two examples of scripts which use PayPal IPN. I’m not going to give scripts that can be used by means copy/paste. I want to show the main principles.

In the first example buyer pays the items situated in the Shopping Cart. After the payment administrator receives the e-mail about the order, shopping cart content is entered to the database. Second example shows how to organize the content subscription.


Payment by the Shopping cart.

Here we won’t talk about creating a shopping cart. I just say that here we don’t need the sessions for shopping cart content storing. $_COOKIE['cart_id'] includes shopping cart identifier that allows us to distinguish user’s carts from each other. Shopping cart content is stored in the MySQL database in the table with the following structure:

CREATE TABLE cart (
  cart_id     int(11),      # cart ID
  item_id     int(11),      # item ID
  price       decimal(8,2), # Item price
  quantity    mediumint(6)  # quantity
);

After the payment, shopping cart should be emptied. Order information will be stored in the “orders” table:

CREATE TABLE orders (
  order_id    int(11)  auto_increment, # order ID
  txn_id      varchar(20),             # transaction number
  order_date  datetime,                # order date
  order_total decimal(8,2),            # general sum
  email       varchar(50),             # buyer"s e-mail 
  first_name  varchar(50),             # buyer"s first name
  last_name   varchar(50),             # buyer"s last name
  street      varchar(50),             # buyer"s address
  city        varchar(50),             # city
  state       varchar(50),             # state
  zip         varchar(15),             # zip
  country     varchar(50)              # country
  PRIMARY KEY (id)
);

Order details will be stored in the “order_details” table:

CREATE TABLE order_details (
  order_id    int(11),      # order_id
  item_id     int(11),      # item_id
  price       decimal(8,2), # price
  quantity    mediumint(6)  # quantity
);

Here is a checkout script:

<?php 
  
// checkout.php 
   
  
$paypalemail  "my@email.com";     // seller’s e-mail  
  
$currency     "USD";              // currency
  
$cart_id=intval($_COOKIE["cart_id"]); 
  
$r=mysql_query("SELECT sum(price*quantity) FROM cart WHERE cart_id=".$cart_id); 
  list (
$total)=mysql_fetch_row($r); 
  
mysql_free_result($r); 
  
$total=number_format($total,2); 
  echo <<<
FORM 
<form method="post" action"https://www.paypal.com/cgi-bin/webscr"
<
input type="hidden" name="cmd" value="_xclick"
<
input type="hidden" name="business" value="$paypalemail"
<
input type="hidden" name="item_name" value="Shopping cart"
<
input type="hidden" name="item_number" value="$cart_id"
<
input type="hidden" name="amount" value="$total"
<
input type="hidden" name="no_shipping" value="0"
<
input type="hidden" name="return" value="http://myhost.com/payment_success.php"
<
input type="hidden" name="rm" value="2"
<
input type="hidden" name="cancel_return" value="http://myhost.com/payment_cancel.html"
<
input type="hidden" name="no_shipping" value="0"
<
input type="hidden" name="currency_code" value="$currency"
<
input type="submit" value="Checkout">  
</
FORM
FORM
?>

payment_success.php code


<?php 
  
// payment_success.php 
  
$paypalemail "my@email.com";     // seller’s e-mail 
  
$adminemail  "admin@email.com";  // administrator’s e-mail   
  
$currency    "USD";              // currency 
  
$postdata=""
  foreach (
$_POST as $key=>$value$postdata.=$key."=".urlencode($value)."&"
  
$postdata .= "cmd=_notify-validate";  
  
$curl curl_init("https://www.paypal.com/cgi-bin/webscr"); 
  
curl_setopt ($curlCURLOPT_HEADER0);  
  
curl_setopt ($curlCURLOPT_POST1); 
  
curl_setopt ($curlCURLOPT_POSTFIELDS$postdata); 
  
curl_setopt ($curlCURLOPT_SSL_VERIFYPEER0);  
  
curl_setopt ($curlCURLOPT_RETURNTRANSFER1); 
  
curl_setopt ($curlCURLOPT_SSL_VERIFYHOST1); 
  
$response curl_exec ($curl); 
  
curl_close ($curl); 
  if (
$response != "VERIFIED") die("You should not do that ...");  
  if (
$_POST["receiver_email"] != $paypalemail  
    
|| $_POST["txn_type"] != "web_accept"
      die(
"You should not be here ..."); 
  
$r mysql_query("SELECT order_id FROM orders WHERE txn_id="".$_POST["txn_id"]."""); 
  list(
$duplicate) = mysql_fetch_row($r); 
  
mysql_free_result($r); 
  if (
$duplicate) die ("I feel like I met you before ...");  
  
$cart_id intval($_POST["item_number"]); 
  
$r mysql_query"SELECT sum(price*quantity), COUNT(cart_id) FROM cart  
                      WHERE cart_id="
.$cart_id); 
  list (
$total,$nitems) = mysql_fetch_row($r); 
  
mysql_free_result($r); 
  if (!
$nitems)  
  { 
    
mail($adminemail"IPN error""Unable to restore cart contents\r\nCart ID: "
      
$cart_id."\r\nTransaction ID: ".$_POST["txn_id"]); 
    die(
"I cannot recall what you paid for ... Please contact ".$adminemail);  
  } 
  if (
$total != $_POST["mc_gross"] || $_POST["mc_currency"] != $currency
  { 
    
mail($adminemail"IPN error""Payment amount mismatch\r\nCart ID: " 
      
$cart_id."\r\nTransaction ID: ".$_POST["txn_id"]); 
    die(
"Out of money? Please contact ".$adminemail); 
  } 
     
$order_date date("Y-m-d H:i:s",strtotime ($_POST["payment_date"]));  
  
mysql_query("INSERT INTO orders SET  
    txn_id      = "".$_POST["
txn_id"]."", 
    order_date  = "
$order_date", 
    order_total = $total, 
    email       = "".$_POST["
payer_email"]."", 
    first_name  = "".mysql_escape_string($_POST["
first_name"])."", 
    last_name   = "".mysql_escape_string($_POST["
last_name"])."", 
    street      = "".mysql_escape_string($_POST["
address_street"])."",  
    city        = "".mysql_escape_string($_POST["
address_city"])."",  
    state       = "".mysql_escape_string($_POST["
address_state"])."",  
    zip         = "".mysql_escape_string($_POST["
address_zip"])."",  
    country     = "".mysql_escape_string($_POST["
address_country"]).""" ); 
  
$order_id mysql_insert_id();  
  
$r mysql_query("SELECT * FROM cart WHERE cart_id=".$cart_id); 
  while (
$row mysql_fetch_assoc($r)) 
  { 
    
mysql_query("INSERT INTO order_details SET  
      order_id = $order_id,  
      item_id = "
.$row["item_id"].", 
      price = "
.$row["price"].", 
      quantity = "
.$row["quantity"]); 
  } 
  
mysql_free_result($r);  
  
mysql_query("DELETE FROM cart WHERE cart_id=".$cart_id); 
  
mail($adminemail"New order""New order\r\nOrder ID: "$order_id."\r\nTransaction ID: " 
    
.$_POST["txn_id"]); 
?>

return parameter using is very convenient, because user can see the result of the payment straight away after billing. But such check-up doesn’t give 100% guarantee that that money has been entered to the account. For example, if customer pays by the e-check money will be entered to the account only after bank has processed that check. notify-url doesn’t have that disadvantage.


Content subscription

PayPal subscription function is very convenient but has one disadvantage. If subscription has a trial period, user can cancel the subscription until the trial period hasn’t expired and can subscribe one more time having got one more trial period. That process can be endless. We have two ways out. First way is the simplest one. Don’t use trial period at all. Second one is that you can provide restricted access during the trial period.

Subscription IPN-script must process several types of IPN. Here are the most popular IPN types (txn_type):

  • subscr_signup subscription created. That IPN informs only about subscription, not about the payment.
  • subscr_payment payment is fulfilled.
  • subscr_failed payment is unsuccessful. That IPN is sent only as a notification.
  • subscr_cancel subscription is cancelled by the customer, seller or cancelled automatically.
  • subscr_eot subscription finishing. When that IPN is received user’s access must be prohibited
  • subscr_modify subscription modifying

So, we have some chargeable recourse. The payment is 10$ per month, and there is a 7 days trial period. Information about subscribers is stored in the “subscribers” table:

CREATE TABLE subscribers (
  subscr_id    varchar(20)  # subscription ID
  subscr_date  datetime,    # subscription date
  payer_id     varchar(20), # Buyer’s ID
  email        varchar(30), # e-mail
  username     varchar(20), # username
  passhash     varchar(32), # password hash 
  limited      tinyint(1)  
  PRIMARY KEY (subscr_id)
);

Script that checks username and password should use field limited.

Subscription form output script

<?php 
  
// subscribe.php 
  
$paypalemail  "my@email.com";     // seller’s e-mail 
  
$currency     "USD";              // currency 
  
$price        "10.00";            // price 
  
$trial        1;                  // trial period time 
  
$trialunit    "W";                // 1 week

  
echo <<<FORM 
<form method="post" action"https://www.paypal.com/cgi-bin/webscr"
<
input type="hidden" name="cmd" value="_xclick-subscriptions"
<
input type="hidden" name="business" value="$paypalemail"
<
input type="hidden" name="item_name" value="Subscription"
<
input type="hidden" name="no_shipping" value="0"
<
input type="hidden" name="return" value="http://myhost.com/subscribed.html"
<
input type="hidden" name="rm" value="1"
<
input type="hidden" name="cancel_return" value="http://myhost.com/subsc_cancel.html"
<
input type="hidden" name="no_shipping" value="1"
<
input type="hidden" name="currency_code" value="$currency"
<
input type="hidden" name="notify_url" value="http://myhost.com/ipn.php"
<
input type="hidden" name="a1" value="0"
<
input type="hidden" name="p1" value="$trial"
<
input type="hidden" name="t1" value="$trialunit"
<
input type="hidden" name="a3" value="$price"
<
input type="hidden" name="p3" value="1"
<
input type="hidden" name="t3" value="M"
<
input type="hidden" name="usr_manage" value="1"
<
input type="submit" value="Subscribe"> </FORM
FORM
?>

PayPal (usr_manage=1) will generate username and password. After subscription script ipn.php will receive IPN (txn_type=subscr_signup). Additional IPN will be sent during the subscription status changing, but we will process only subscr_payment and subscr_eot.

ipn.php script:

<?php 
  
// ipn.php 
  
$paypalemail "my@email.com";     // seller’s e-mail  
  
$adminemail  "admin@email.com";  // administrator’s e-mail   
  
$currency    "USD";              // currency 
  
$price       10.00;              // price 
  
$trial       "1 W";              // trial period time 
  
$cycle       "1 M";              // main cycle time 

  
$postdata=""
  foreach (
$_POST as $key=>$value$postdata.=$key."=".urlencode($value)."&";  
  
$postdata.="cmd=_notify-validate"
  
$curl curl_init("https://www.paypal.com/cgi-bin/webscr"); 
  
curl_setopt ($curlCURLOPT_HEADER0); 
  
curl_setopt ($curlCURLOPT_POST1); 
  
curl_setopt ($curlCURLOPT_POSTFIELDS$postdata); 
  
curl_setopt ($curlCURLOPT_SSL_VERIFYPEER0); 
  
curl_setopt ($curlCURLOPT_RETURNTRANSFER1); 
  
curl_setopt ($curlCURLOPT_SSL_VERIFYHOST1); 
  
$response curl_exec ($curl); 
  
curl_close ($curl);  
  if (
$response != "VERIFIED") exit;  
  if (
$_POST["receiver_email"] != $paypalemail  
      
|| $_POST["txn_type"] != "subscr_signup" 
      
|| $_POST["txn_type"] != "subscr_eot" 
      
|| $_POST["txn_type"] != "subscr_payment"
        exit; 

  if (
$_POST["txn_type"] == "subscr_signup"
  { 
    
$r mysql_query("SELECT payer_id FROM subscribers WHERE payer_id="".$_POST["payer_id"].""");  
    list(
$duplicate) = mysql_fetch_row($r); 
    
mysql_free_result($r); 
    if (
$duplicate) exit;  
    if (isset(
$_POST["p2"])  
        || 
$_POST["mc_currency"] != $currency  
        
|| $_POST["mc_amount3"] != $price  
        
|| $_POST["period1"] != $trial  
        
|| $_POST["period3"] != $cycle) exit;  
    
$subscr_date date("Y-m-d H:i:s",strtotime ($_POST["subscr_date"])); 
    
mysql_query("INSERT INTO subscribers SET  
      subscr_id   = "".$_POST["
subscr_id"]."", 
      subscr_date = "
$subscr_date", 
      payer_id    = "".$_POST["
payer_id"]."", 
      email       = "".$_POST["
payer_email"]."", 
      username    = "".$_POST["
username"]."",  
      passhash    = "".md5($_POST["
password"])."",  
      limited = 1"
);   
  elseif (
$_POST["txn_type"] == "subscr_payment")  
  { 
    if (
$_POST["mc_currency"] != $currency  
    
|| ($_POST["payment_status"] != "completed" && $_POST["pending_reason"] != "intl"
    || 
$_POST["mc_gross"] != $price) exit; 
        
mysql_query("UPDATE subscribers SET  limited=0 WHERE subscr_id="".$_POST["subscr_id"]."""); 
  }  
  elseif (
$_POST["txn_type"] == "subscr_eot")  
  {  
    
mysql_query("DELETE FROM subscribers WHERE subscr_id="".$_POST["subscr_id"]."""); 
  } 
?> 

Conclusion

  • Don’t trust the data received by IPN script before receiving the VERIFIED answer from PayPal . Save processed transaction data.
  • Define what data you are going to receive for all IPN
  • Don’t use payer_email for buyer’s identification, e-mail can be changed. Use payer_id.
  • If you received txn_type=web_accept or txn_type=subscr_payment it doesn’t mean that you received payment. Always check payment_status=completed.
  • Restrict the POST query size up to several kilobytes

    # httpd.conf
    <files my_ipn_script.php>
    php_admin_value post_max_size 10K
    </files>

  • Every system can have errors. PayPal isn’t an exclusion. If IPN script received strange data you have to inform the administrator.

 

  • Top