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:
|
| 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:
| 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 ($curl, CURLOPT_HEADER, 0);
curl_setopt ($curl, CURLOPT_POST, 1);
curl_setopt ($curl, CURLOPT_POSTFIELDS, $postdata);
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($curl, CURLOPT_SSL_VERIFYHOST, 1);
$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 ($curl, CURLOPT_HEADER, 0);
curl_setopt ($curl, CURLOPT_POST, 1);
curl_setopt ($curl, CURLOPT_POSTFIELDS, $postdata);
curl_setopt ($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt ($curl, CURLOPT_SSL_VERIFYHOST, 1);
$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.



