Receiving PayPal payments
PayPal logo center on the Internet
Payment systems. PayPal
Andrey DemenevIntroduction
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
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 tohttp://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_number | Â Â Â ÂThe 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_name | Â Â Â ÂThe name of the product to be displayed 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 remains 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. Default "0".
   |
cancel_return | Â Â Â ÂThe 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_url | Â Â Â ÂThe 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 |
custom | Â Â Â ÂThe value of this field is not involved in the purchasing 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 |
amount | Â Â Â ÂAmount 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" |
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 |
a3 | Â Â Â ÂThe cost of the main subscription cycle. Required parameter |
p3 | Â Â Â ÂDuration of the main subscription cycle. Required parameter |
t3 |    ÂThe unit of the duration of the main subscription cycle. Required.     Possible values ​​are: "D" -day, "W" -weeks, "M" -months, "Y" -years.    |
src | Â Â Â ÂRecurring payments. "1" - payments will be repeated periodically. Â Â Â Â If the parameter is not passed, the payment will be made once. |
sra | Â Â Â ÂIf 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. |
srt | Â Â Â ÂNumber 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) |
modify | Â Â Â ÂPossibility 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_manage | Â Â Â ÂAutomatic 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:
- IPN for
return
will be sent only once, immediately after payment.notify_url
can be called several times (see the previous paragraph). - 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 scriptnotify_url
to the user's browser is not displayed.
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_name | Â Â Â ÂProduct Name |
txn_type | Â Â Â ÂThe 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_status | Â Â Â ÂThe 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_reason | Â Â Â ÂThe 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 |
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 |
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;
?>
$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 ($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 ...");
/********
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 */
?>
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.
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;
?>
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 ($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;
/********
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. Usepayer_id
- Getting
txn_type=web_accept
ortxn_type=subscr_payment
does not mean that you received Payment. Always checkpayment_status=completed
. The only thing Exception: you have a non-US account andpending_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.