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_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" |
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 |
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 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_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 |
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 ($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 */
?>
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 ($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. 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