Receiving PayPal payments

Business / Money / Online money

Receiving PayPal payments

PayPal


PayPal logo center on the Internet

Notation of acceptance

PayPal

Payment systems. PayPal

Andrey Demenev

Introduction

When creating commercial sites, the question arises: how Accept payments? One of the most popular payment systems in the world is PayPal. Selection This system is often determined by the high reliability, ease of account opening and use. To open an account, it is enough to have a credit card and/or an account with an American bank. One of the main shortcomings is often called a very tough security policy. But practice shows that with strict adherence to the rules for using the system, problems are rare. I'm not going to describe all the pros and cons. The purpose of this article is to show how to organize payment processing to ensure reliability and security.

Organization of the actual payment is not difficult. In this article, I will Pay more attention to the automatic payment verification process with Using IPN (Instant Payment Notification). The article is based on Own experience, official PayPal documentation and materials An independent forum for PayPal developers.

Types of payments

PayPal supports several types of payments:
  • payment for goods in the cart provided by   PayPal. In this case, all basket support operations are   Yourself PayPal
  • "one-click" shopping, without adding items to the shopping cart. This method is also used for payment   Goods in a basket formed without using PayPal
  • recurring billing, subscription
The last two methods will be considered in the article. Also I do not I consider the method in which the basket is formed on our site, and Then the whole contents of the basket are transferred to PayPal.

Payment Process

The Payment Process is very simple: a POST form is created with A set of hidden fields containing information about the product (identifier, name, price), and a form submission button. It should be noted that all prices must be transmitted with two signs after the point. If the cost of the goods is $10, then the price should be transferred as "10.00". After sending The buyer goes to the site paypal.com, where he completes the payment process. These forms must be sent to http://www.paypal.com/cgi-bin/webscr.

Purchase "in one click"

Code of the simplest form:
<form method="post" action= "http://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 Required. Must have meaning "_xclick"
business Required parameter - email   Seller
item_numberThe product ID. This value will not be shown to the user, but will be passed to your script when the transaction is confirmed. If you use PayPal to pay for goods from the shopping cart, you can send the shopping cart identifier in this field
item_nameThe name of the product to be displayed to the buyer
no_shippingDo not request an address for delivery. "1" - do not request the address, "0" - request
returnURL where the buyer will be redirected after a successful payment.     If this parameter is not passed, the buyer remains to the site PayPal
rmThis parameter specifies how the information about the successfully completed transaction will be transmitted to the script specified in the return parameter. "1" - no parameters will be transmitted. "2" - the POST method will be used. "0" - the GET method will be used. Default "0".    
cancel_returnThe URL where the buyer will be redirected when the payment is canceled.     If this parameter is not passed, the buyer remains to the site PayPal
notify_urlThe URL to which PayPal will provide information about the transaction (IPN).     If you do not transfer this parameter, the value specified in your account settings will be used. If this is not defined in the account settings, IPN will not be used
customThe value of this field is not involved in the purchasing process, it will simply be passed to your script when the transaction is confirmed
invoiceUsed to transfer the account number. The parameter is optional, but if you pass it, it must be unique for each transaction
amountAmount to be paid. If this parameter is not passed, the buyer will be given the opportunity to enter the amount independently (used for donations)
currency_code Currency code. Possible values are: "USD","EUR","GBP","YEN","CAD". Default "USD"

The table lists only the most commonly used parameters. For a complete list, see the documentation (links at the end of the article).

Subscription

PayPal provides an opportunity to organize a subscription: from the client's account a certain amount will be periodically transferred to your account, and the customer has the opportunity at any time to cancel the subscription. You can set the frequency and cost of the subscription, as well as the trial period, so that the client can be sure of the quality of the services you provide. The trial period can be either paid or free.

Form example:

<form action="http://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>

DDescription of the main parameters

                                                                                 
Parameter Description
cmd Required. Must have meaning "_xclick-subscriptions"
business The binding parameter is the seller's email
item_number Product identifier. This value will not be shown to the user, but will be passed to your script when the transaction is confirmed.
item_name The name of the goods to be shown to the buyer
no_shipping Do not request an address for delivery. "1" - do not request the address, "0" - request
return URL where the buyer will be redirected after a successful payment.      If this parameter is not passed, the buyer will remain to the site PayPal
rm This parameter specifies how the information about the successfully completed transaction will be transmitted to the script specified in the return parameter. "1" - no parameters will be transmitted. "2" - the POST method will be used. "0" - the GET method will be used. The default is "0".
cancel_return URL, where the buyer will be redirected when canceling the payment.      If this option is not passed, the buyer remains to the site PayPal
notify_url The URL to which PayPal will provide information about the transaction (IPN).      If you do not pass this parameter, the value specified in your account settings will be used. If this is not specified in the account settings, IPN will not be used
custom The value of this field does not participate in the purchase process, it will simply be passed to your script when the transaction is confirmed
invoice Used to transfer the account number. The parameter is optional, but if you pass it, it must be unique for each transaction
a1 The cost of the first trial period. It can be "0", in this case the trial period is free. If you do not provide a trial period, do not pass this parameter
p1 Duration of the first trial period. If you do not provide a trial period, do not pass this parameter
t1 Unit of measurement of the duration of the first trial period.      Possible values are: "D" -day, "W" -weeks, "M" -months, "Y" -years.      If you do not provide a trial period, do not pass this parameter
a2 The cost of the second trial period. If you do not provide a trial period, do not pass this parameter
p2 Duration of the second trial period. If you do not provide a trial period, do not pass this parameter
t2 Unit of measurement of the duration of the second trial period.     Possible values ​​are: "D" -day, "W" -weeks, "M" -months, "Y" -years.     If you do not provide a trial period, do not pass this parameter
a3The cost of the main subscription cycle. Required parameter
p3Duration of the main subscription cycle. Required parameter
t3The unit of the duration of the main subscription cycle. Required.     Possible values ​​are: "D" -day, "W" -weeks, "M" -months, "Y" -years.    
srcRecurring payments. "1" - payments will be repeated periodically.     If the parameter is not passed, the payment will be made once.
sraIf you pass "1" in this parameter, and the transfer attempt fails (for example, if there are not enough funds in the buyer's account), then up to 2 more attempts will be made. After 3 unsuccessful attempts, the subscription will be automatically canceled. If you do not pass this parameter, subscription will be automatically canceled after the first failed attempt.
srtNumber of subscription cycles. If this parameter is passed, subscription will be canceled after the specified number of cycles. If you do not pass this parameter, subscription will remain in effect until canceled by the buyer (or automatically if the transaction fails)
modifyPossibility of modification. Possible       Values:
"0" - the form is intended only for creating a new subscription
"1" - the form is intended only for changing the parameters of the existing subscription
"2" - the form is intended both for creating a new subscription, so And to change the existing parameters
The default is "0"
usr_manageAutomatic generation of user name and password. Pass "1" to PayPal to automatically generate a username and password.
currency_code Currency code. Possible values are "USD", "EUR", "GBP", "YEN", "CAD".      Default "USD"

IPN

IPN (Instant Payment Notification) - This is a PayPal technology that allows Automate the process of processing payments. The essence of it is, That on the server of the seller the special script is created, and at occurrence Events related to the merchant account (such as payment, cancellation Payment, creating or canceling a subscription, etc.) the PayPal server sends This IPN script is a POST request with information about the transaction. Script in your The queue sends a request to the PayPal server to verify the transaction.

So, the buyer has completed the payment. With a little delay (Up to several seconds), the PayPal server sends the IPN to the script specified in Account settings or passed in the notify_url parameter. A well-written IPN script is the key to security Payments. If you've heard of cases of fraudulent sellers using PayPal, You can be sure: either they did not use IPN at all, or they have "Leaky" IPN script

First of all, the script should make sure that it really was Called by the PayPal server. To do this, it must generate a POST request to www.paypal.com/cgi-bin/webscr, Passing all received variables without changing with the addition of the cmd parameter with a value of _notify-validate. In response, there will be returned, or VERIFIED if the transaction was successfully verified, or INVALID in case of an error. If you answer INVALID The script should finish.

Then you should check the payee, since the potential an attacker can change the form to payment was credited to his account. The payee is determined by variables business and receiver_email. The need for two variables is explained by the fact that PayPal allows register several email addresses for one account. Email, specified when creating the account, is the primary email. The value of receiver_email is always primary email. If payment was sent to an additional email, it is sent through business.
 If business and/or receiver_email are not Contains the expected value, the script immediately exits.

Now you need to check the amount and currency of the payment. Such verification is necessary, since It is not difficult for a potential attacker to change the amount in the form, in the case of a subscription, you should check all the subscription parameters (availability, duration And the cost of trial periods, duration and cost of the main a subscription cycle, etc.).

IPN for the same transaction can be sent more than once. For example, if a payment has been delayed for any reason, the first IPN will be sent immediately after the payment. After the payment is made, completed or canceled, a second IPN will be sent. If your IPN script did not return HTTP status 200, PayPal will retry sending IPN after some time. The first repeat will be after 10 seconds, then if necessary after 20, then through 40, 80, etc. (up to 24 hours). If within 4 days the expected a response from your script will not be received, attempts will be terminated. It's possible use in order to not lose transaction data in case of an error in your IPN script. For example, if the script failed to connect to the database in which it stores transaction data, it can return HTTP status 500, and IPN will be repeated later. Repeated IPN will also be sent if the IPN script does not contact the PayPal server for transaction verification.

As can be seen from the description of the parameters return, rm and notify_url, IPN can be passed to two scripts specified in parameters return and notify_url. Between them 2 differences:

  1. IPN for return will be sent only once, immediately after payment. notify_url can be called several times (see the previous paragraph).
  2. The output of the return script will be displayed to the user. Please note, that if the output contains references, then they must be absolute. The output of the script notify_url to the user's browser is not displayed.

In the obtained POST variables contain information about the transaction. The most commonly used variables:

                                       
Parameter Description
txn_id Unique Transaction Number
payment_date Payment date in format "18:30:30 Jan 1, 2000 PST"
payer_email email of buyer
business email of vendor
payer_id Unique buyer ID. PayPal account participants are identified by email address, however, given that it is possible to change email, it is better to identify the buyer to use payer_id
item_number Product ID
item_nameProduct Name
txn_typeThe type of transaction. Possible Values:
" web_accept" - payment was made using the "Buy Now" button
" cart" - payment was made using the built-in PayPal
" send_money" - payment was made using the "Send money" function
" reversal" - the money was returned to the buyer on his initiative
payment_statusThe payment status. Possible Values:
" Completed" - the transaction was completed successfully, the money was transferred to the seller's account. In the case of txn_type="reversal" That the money was returned to the buyer's account
" Pending" - the payment is delayed. The reason for the delay is in the variable pending_reason. After the payment is made, Completed or canceled, PayPal will send another notification.
" Failed" - the payment failed. This condition is possible only if payment was made from a bank account
" Denied" - the payment was canceled by the seller. This condition occurs when the seller cancels the payment, state Which was Pending
" Refunded" - the money was returned to the buyer. This condition occurs when the seller cancels the payment, state Which was Completed
pending_reasonThe reason for the delay in payment. Possible Values:
" echeck" - payment was made by electronic check
" multi_currency" - the payment was made in a currency that is not specified in the settings of the seller's account. The payment will be completed after the seller confirms the transaction
" intl" - the seller is not a resident of USA. The payment will be completed after the seller confirms the transaction
" verify" - the merchant account is in the "unverified" state. Payment will be completed after confirmation Personality of the seller.
" address" - in the settings of the seller's account indicated that the buyer must specify the address for delivery, But the buyer did not specify the address. The payment will be completed after the seller confirms the transaction
" upgrade" - the payment was made from a credit card, while the seller's account has the status "Personal". To complete the payment, the seller must update the account to "Business" or "Premier"
" unilateral" - the seller's email is not registered in the system.
" other" is another reason. The seller should contact the customer service for a reason.
payment_type Type of payment. Possible Values:
"echeck" - Payment was made by an electronic check
"instant" - Payment was made from a credit card, bank account or using funds on the PayPal account of the buyer
mc_gross Summ of payment
mc_fee The amount of commission. The amount credited to the seller's account is determined as mc_gross–mc_fee
mc_currency Currency
first_name
last_name
address_street Street
address_city City
address_state State
address_zip ZIP
address_country Country
verify_sign Digital signature. Use PayPal when checking a transaction

Additional variables used with the subscription

Parameter Description
txn_type The type of transaction. Possible values are:
"subscr_signup" - subscription
"subscr_cancel" - unsubscribe
"subscr_failed" - payment attempt failed
"subscr_payment" - the payment attempt completed successfully
"subscr_eot" - he end of the subscription cycle
"subscr_modify" - change subscription settings
subscr_date Subscription or cancellation date
subscr_effective The effective date for changes to subscription parameters.      Transmitted only iftxn_type=subscr_modify
period1 Duration of the first trial period. "4 D" - 4 days, "2 W" - 2 weeks, "1 M" - 1 month...
period2 Duration of the second trial period.
period3 Duration of the main subscription cycle.
mc_amount1 The cost of the first trial period.
mc_amount2 Cost of the second trial period.
mc_amount3 The cost of the main subscription cycle.
mc_currency Currency
recurring Indicator of recurring payments. Reflects the value of the src parameter transmitted via the subscription form
reattempt Reflects the value of the sra parameter passed through the subscription form
recur_times Reflects the value of the srt parameter passed through the subscription form
retry_at In case of a failed payment attempt (txn_type=subscr_failed), contains the date of the next attempt.
username Autogenerated username
password Autogenerated password
subscr_id Unique Subscriber ID

The following table shows which variables are passed for different IPNs on a subscription

  Transaction Type
Variable Subscription Unsubscribing Change subscription Payment Unsuccessful payment End of subscription
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

Examples of scripts

I will give two examples of scripts using PayPal IPN. I put Its goal is not to bring ready scripts that you can use By the method of copy/paste, but merely to illustrate the general principles.

In the first example, the buyer pays for goods in the "basket". After payment, the site administrator receives an email notification of admission Order, and the contents of the basket are entered in the database for monitoring Order. The second example shows how you can organize Subscription to the content.

Payment for goods in the "basket"

I will not describe the actual implementation of the "basket" here. I will only note that in our case, the sessions for storing the contents of the basket We are not able to restore the session data in our IPN script. For definiteness, I believe that $_ COOKIE ['cart_id'] contains The identifier of the basket by which we distinguish the basket of one user From another. Let the contents of the recycle bin are stored in the MySQL database, in the Table with the following structure:

CREATE TABLE cart (
  cart_id     int(11),
  item_id     int(11), 
  price decimal(8,2),
  quantity    mediumint(6)
);
After payment, the buyer's basket must be cleared, and entries in the order table are entered. General information about orders will be stored in the table of orders
CREATE TABLE orders (
  order_id    int(11)  auto_increment,
  txn_id      varchar(20),	
  order_date  datetime,		
  order_total decimal(8,2),	
  email varchar(50),		
  first_name  varchar(50),		
  last_name   varchar(50),	
  street      varchar(50),		
  city  varchar(50),		
  state varchar(50),		
  zip   varchar(15),	
  country     varchar(50)	
  PRIMARY KEY (id)
);
We will store the order details in the order_details table
CREATE TABLE order_details (
  order_id    int(11),
  item_id     int(11),
  price decimal(8,2),
  quantity    mediumint(6)
);

Code script that displays the order form (checkout)

<?php
  
// checkout.php
  
  
$paypalemail  "my@email.com";     // email of vendor
  
$currency     "USD";              // currency
  
$cart_id=intval($_COOKIE['cart_id']);

  
/*
    Here code that connects to the database data  
       content baskets
  */

  
$r=mysql_query("SELECT sum(price*quantity) FROM cart WHERE cart_id=".$cart_id);
  list (
$total)=mysqli_fetch_row($r);
  
mysqli_free_result($r);
  
$total=number_format($total,2);
  echo <<<FORM
<form method="post" action= "http://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;
?>

Passing $cart_id in the item_number field allows us to restore the contents of the recycle bin in our IPN script. If the buyer cancels the payment, it will be redirected to myhost.com/payment_cancel.html. If he makes a payment, he will be redirected to myhost.com/payment_success.php, where we check whether the payment was made, update the database and thank you for the purchase.

Script code payment_success.php

<?php
  
// payment_success.php
  
$paypalemail "my@email.com";     // email of sailer
  
$adminemail  "admin@email.com";  // email  of admin
  
$currency    "USD";              // currency

  /********
  Request transaction transaction
  ********/
  
$postdata="";
  foreach (
$_POST as $key=>$value$postdata.=$key."=".urlencode($value)."&";
  
$postdata .= "cmd=_notify-validate"
  
$curl curl_init("http://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 ..."); 

  
/********
  check your recipient payment  transaction type, and we exit if not our account
  in $ paypalemail -  primary email, therefore check receiver_email
  ********/
  
if ($_POST['receiver_email'] != $paypalemail 
    
|| $_POST["txn_type"] != "web_accept")
      die(
"You should not be here ...");

  
/*
    here code that connects to the database data  
  */ 

  /********  
    make sure in that this transaction is not  
    was processed earlier  
  ********/
  
$r mysql_query("SELECT order_id FROM orders WHERE txn_id='".$_POST["txn_id"]."'");
  list(
$duplicate) = mysqli_fetch_row($r);
  
mysqli_free_result($r);
  if (
$duplicate) die ("I feel like I met you before ..."); 
  
/********
    Check payment amount  
  ********/ 
  
$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) = mysqli_fetch_row($r);
  
mysqli_free_result($r);
  if (!
$nitems// You failed recover contents Recycle Bin
  
{
    
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);
  }
   
/********
     Checks completed form order
  ********/
  
$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']);
  }
  
mysqli_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"]);
  
/* 
    inform, that order is accepted, we thank you for for purchase and 
    we offer buy anything else */ 
?>

Using the return parameter is convenient because Allows immediately after payment to give the result of verification to the user. However, such a check does not give 100% certainty that the money was Really credited to our account. For example, if the buyer pays Electronic check (e-check), money will be credited only after processing Check by the bank, and the enrollment is not guaranteed. notify-url It is not deprived of this shortcoming, since it allows one to trace the moment of the actual Receipts of money. In the following example, I'll show you how to handle Repeated IPN on the example of a subscription to the content.

Subscription to content

The PayPal subscription feature is very convenient, however it has a significant Lack. The matter is that if the subscription has a trial period, then The user can cancel the subscription until the trial period is over, and Subscribe again, thus obtaining another trial period, and so to infinity. Two outputs are possible. The first, the simplest, is not Use trial periods. The second is to provide only limited Access during the trial period.   Also keep in mind that there is no automatic return mechanism The user can cancel the subscription in the middle of the cycle.

The IPN script for the subscription must handle several kinds of IPN. it Often causes difficulties. I will describe in more detail the different types of IPN ( txn_type)

  • subscr_signup subscription created. This IPN notifies only About subscription, but not about payment.
  • subscr_payment payment was made either during the trial period (If it is not free), or for the main subscription cycle.
  • subscr_failed the payment attempt failed. This IPN is sent only as a notification, usually no action is taken Do not need to.
  • subscr_cancel subscription is canceled by the buyer, seller Or automatically (if payment can not be made). When receiving This IPN should not close the user's access to the resource, since Subscription can be canceled in the middle of the cycle for which you already paid.
  • subscr_eot end of subscription. When you receive this IPN You must prevent the user from accessing the resource.
  • subscr_modify Change subscription settings.
In most cases, it is sufficient to implement the processing subscr_signup, subscr_cancel, subscr_payment and subscr_eot.

Let there is a certain resource to which we want to provide paid access. Payment is $10 per month, a week of free limited access is provided.

Subscriber data is stored in a table subscribers

CREATE TABLE subscribers (
  subscr_id    varchar(20)  #Unique subscription identifier
  subscr_date  datetime,    #date of subscription
  payer_id     varchar(20), #Buyer ID
  email  varchar(30), #Buyer email
  username     varchar(20), #user name
  passhash     varchar(32), #Password hash
  limited      tinyint(1)  #Restricted flag
  PRIMARY KEY (subscr_id)
);
The script that verifies the user name and password will need to use the limited field to determine whether the user should be granted full or restricted access.

Code of the script that displays the subscription form

<?php
  
// subscribe.php
  
$paypalemail  "my@email.com";     // email of vendor
  
$currency     "USD";              // Currency
  
$price        "10.00";            // cost of the subscribe
  
$trial        1;                  // Length of trial period
  
$trialunit    "W";                // 1 week

  
echo <<<FORM
<form method="post" action= "http://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;
?>

We will provide the username and password generator with PayPal (usr_manage=1). After the subscription, the script ipn.php will get IPN (txn_type=subscr_signup). If the status of the subscription changes, additional IPNs will be sent, we will only process subscr_payment and subscr_eot.

Code Script ipn.php

<?php
  
// ipn.php
  
$paypalemail "my@email.com";     // email of vendor
  
$adminemail  "admin@email.com";  // email  of admin
  
$currency    "USD";              // currency
  
$price       10.00;              // cost of subscribtion
  
$trial       "1 W";              // Length of trial period
  
$cycle       "1 M";              // Main cycle time

  /********
  We request a transaction confirmation
  ********/
  
$postdata="";
  foreach (
$_POST as $key=>$value$postdata.=$key."=".urlencode($value)."&"
  
$postdata.="cmd=_notify-validate";
  
$curl curl_init("http://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; 

  
/********
  Check the payee and the transaction type, and exit, 
  If not our account or IPN does not require processing
  in $paypalemail - Our primary email, so we check receiver_email
  ********/
  
if ($_POST['receiver_email'] != $paypalemail 
      
|| $_POST["txn_type"] != "subscr_signup"
      
|| $_POST["txn_type"] != "subscr_eot"
      
|| $_POST["txn_type"] != "subscr_payment")
        exit;


  
/* 
    Here is the code that connects to the database 
  */ 

  /******** 
    subscription ? 
  ********/ 
  
if ($_POST["txn_type"] == "subscr_signup")
  {
    
$r mysql_query("SELECT payer_id FROM subscribers WHERE payer_id='".$_POST["payer_id"]."'"); 
    list(
$duplicate) = mysqli_fetch_row($r);
    
mysqli_free_result($r);
    if (
$duplicate) exit; // repeated IPN - Ignore 
    
if (isset($_POST["p2"]) 
        || 
$_POST["mc_currency"] != $currency 
        
|| $_POST["mc_amount3"] != $price 
        
|| $_POST["period1"] != $trial 
        
|| $_POST["period3"] != $cycle) exit; // Incorrect subscription settings 
    
$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"
); 
    
/* 
      Here you can send an email with a username and password, 
      I do not do this, since the password is generated by PayPal 
      And is available to the subscriber on the PayPal website in the Account Control Panel 
    */ 
  

  
/******** 
    Ðpaymentnbsp;? 
  ********/ 
  
elseif ($_POST["txn_type"] == "subscr_payment"
  {
    if (
$_POST["mc_currency"] != $currency 
    
|| ($_POST["payment_status"] != "completed" && $_POST["pending_reason"] != "intl")
    || 
$_POST["mc_gross"] != $price) exit;
    
// After the first payment we give full access 
    
mysql_query("UPDATE subscribers SET  limited=0 WHERE subscr_id='".$_POST["subscr_id"]."'");
  } 
  
/******** 
    Ending of subscriptions ? 
  ********/ 
  
elseif ($_POST["txn_type"] == "subscr_eot"
  { 
    
mysql_query("DELETE FROM subscribers WHERE subscr_id='".$_POST["subscr_id"]."'");
  }
?> 

Conclusion

In conclusion, a few tips

  • Never trust the data received by the IPN script before receiving VERIFIED response from PayPal. Store the data about the processed transactions, and After receiving the VERIFIED response, verify that this Transaction before.
  • or all possible IPNs, determine what data you expect get. Any discrepancy is suspicious.
  • Do not use payer_email - email can be changed. Use payer_id
  • Getting txn_type=web_accept or txn_type=subscr_payment does not mean that you received Payment. Always check payment_status=completed. The only thing Exception: you have a non-US account and pending_reason=intl
  • Limit the size of the POST request sent to the IPN script at the level Several kilobytes
    #httpd.conf
    <files my_ipn_script.php>
    php_admin_value post_max_size 10K
    </files>
  • Any system can fail, PayPal is no exception. If the IPN script received suspicious data, you should write to the log and Notify administrator. It is also useful to bring out a form, The user can send an error message.

About PayPal