При создании коммерческих сайтов встает вопрос: как принимать платежи? Одной из наиболее популярных в мире платежных систем является PayPal . Выбор этой системы часто определяется высокой надежностью, простотой открытия аккаунта и использования. Для открытия аккаунта достаточно наличия кредитной карты и/или счета в американском банке. Одним из основных недостатков часто называют очень жесткую политику безопасности. Но практика показывает, что при строгом соблюдении правил использования системы проблемы возникают редко. Я не собираюсь описывать все "за" и "против". Цель этой статьи - показать, как организовать обработку платежей для обеспечения надежности и безопасности.

Сразу оговорюсь, что PayPal не открывает аккаунты жителям России (как, впрочем, и других стран бывшего СССР), но, учитывая, что многие российские программисты создают сайты для зарубежных заказчиков, тема интеграции сайта с PayPal является актуальной.

Организация собственно оплаты не представляет тудностей. В этой статье я буду уделять больше внимания процессу автоматической проверки платежа с использованием IPN (Instant Payment Notification). Статья основана на собственном опыте, официальной документации PayPal и материалах независимого форума разработчиков PayPal.

Виды платежей

PayPal поддерживает несколько видов платежей:
оплата товаров в корзине, предоставляемой PayPal. В этом случае все операции по поддержке корзины берет на себя PayPal
покупка товара "в один клик", без добавления товара в корзину. Этот метод также применяется для оплаты товаров в корзине, сформированной без использования PayPal
периодические платежи, или подписка (recurring billing, subscription)

В статье будут рассмотрены два последних метода. Также я не рассматриваю метод, при котором корзина формируется на нашем сайте, а затем все содержимое корзины передается PayPal.
Процесс оплаты

Процесс оплаты очень прост: создается POST-форма с набором hidden полей, содержащих информацию о товаре (идентификатор, наименование, цена), и кнопку отправки формы. Следует отметить, что все цены должны передаваться с двумя знаками после точки. Если стоимость товара $10, то цена должна передаваться как "10.00". После отправки формы покупатель переходит на сайт paypal.com, где он завершает процесс оплаты. Данные формы должны отправляться на https://www.paypal.com/cgi-bin/webscr.
Покупка "в один клик"

Код простейшей формы:
<form method="post" action= "https://www.paypal.com/cgi-bin/webscr">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="business" value="my@email.com">
<input type="hidden" name="item_name" value="Item name">
<input type="hidden" name="item_number" value="1234">
<input type="hidden" name="amount" value="19.95">
<input type="hidden" name="no_shipping" value="1">
<input type="submit" value="Buy Now">
</form>

Подписка

PayPal предоставляет возможность организовать подписку: со счета клиента на ваш счет будет периодически переводиться определенная сумма, причем клиент имеет возможность в любой момент отменить подписку. Вы можете задать периодичность и стоимость подписки, а также пробный период, чтобы клиент мог убедиться в качестве предоставляемых вами услуг. Пробный период может быть как платным, так и бесплатным.

Пример формы:
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_xclick-subscriptions">
<input type="hidden" name="business" value=  "my@email.com">
<input type="hidden" name="item_name" value="Baseball Hat Monthly">
<input type="hidden" name="item_number" value="123">
<input type="hidden" name="no_shipping" value="1">
<input type="hidden" name="a1" value="0">
<input type="hidden" name="p1" value="1">
<input type="hidden" name="t1" value="W">
<input type="hidden" name="a2" value="5.00">
<input type="hidden" name="p2" value="2">
<input type="hidden" name="t2" value="M">
<input type="hidden" name="a3" value="50.00">
<input type="hidden" name="p3" value="1">
<input type="hidden" name="t3" value="Y">
<input type="hidden" name="src" value="1">
<input type="hidden" name="sra" value="1">
<input type="hidden" name="srt" value="5">
<input type="submit" value="Subscribe">
</form>

IPN

IPN (Instant Payment Notification) - это технология PayPal, позволяющая автоматизировать процесс обработки платежей. Суть ее заключается в том, что на сервере продавца создается специаальный скрипт, и при возникновении событий, имеющих отношение аккаунту продавца (таких, как платеж, отмена платежа, создание или отмена подписки, и т.д.) сервер PayPal отправляет этому скрипту IPN - POST-запрос с информацией о транзакции. Скрипт в свою очередь посылает запрос серверу PayPal для проверки транзакции.

Итак, покупатель завершил оплату. С небольшой задержкой (до нескольких секунд) сервер PayPal отправляет IPN скрипту, указанному в настройках аккаунта или переданному в параметре notify_url. Грамотно написанный IPN-скрипт является ключом к обеспечению безопасности платежей. Если вы слышали о случаях обмана продавцов, использующих PayPal, можете быть уверены: либо они вообще не использовали IPN, либо у них "дырявый" IPN-скрипт

В первую очередь скрипт должен убедиться в том, что он действительно был вызван сервером PayPal. Для этого он должен сформировать POST-запрос к https://www.paypal.com/cgi-bin/webscr, передав все полученные переменные без изменения с добавлением параметра cmd со значением _notify-validate. В ответ будет возвращено либо VERIFIED в случае успешной верификациии транзакции, либо INVALID в случае ошибки. В случае ответа INVALID скрипт должен завершить работу.

Затем следует проверить получателя платежа, так как потенциальный злоумышленник может изменить форму, чтобы платеж был зачислен на его счет. Получатель платежа определяется по переменным business и receiver_email. Необходимость двух переменных объясняется тем, что PayPal позволяет зарегистрировать для одного аккаунта несколько адресов e-mail. E-mail, указанный при создании аккаунта, является первичным (primary email). Значением receiver_email всегда является primary email. Если платеж был отправлен на дополнительный email, то он передается через business. Если business и/или receiver_email не содержит ожидаемого значения, скрипт немедленно завершает работу.

Теперь нужно проверить сумму и валюту платежа. Такая проверка необходима, так как потенциальному злоумышленнику не составляет труда изменить сумму в форме, В случае подписки, следует проверять все параметры подписки (наличие, продолжительность и стоимость пробных периодов, продолжительность и стоимость основного цикла подписки, и т.д.).

IPN для одной и той же транзакции может отправляться более одного раза. Например, если платеж по какой-либо причине был задержан, первый IPN будет передан сразу после платежа. После того, как платеж будет завершен или отменен, будет отправлен второй IPN. Если ваш IPN-скрипт не вернул HTTP статус 200, PayPal повторит отправку IPN через некоторое время. Первый повтор будет через 10 секунд, затем при необходимости через 20, потом через 40, 80 и т.д.(до 24 часов). Если в течение 4 суток ожидаемый ответ от вашего скрипта не будет получен, попытки будут прекращены. Это можно использовать для того, чтобы не потерять даные о транзакции в случае возникновения ошибки в вашем IPN скрипте. Например, если скрипту не удалось подключиться к базе данных, в которой он сохраняет данные о транзакциях, он может вернуть HTTP статус 500, и IPN будет повторен позднее. Повторный IPN будет отправлен также, если IPN-скрипт не обращается к серверу PayPal для проверки транзакции.

Как видно из описания параметров return, rm и notify_url, IPN может передваться двум скриптам, указанным в параметрах return и notify_url. Между ними 2 различия:
IPN для return будет отправлен только однократно, непосредственно после оплаты. notify_url может вызываться несколько раз (см. предыдущий параграф).
Вывод скрипта return будет показан пользователю. Заметьте, что если в выводе содержатся ссылки, то они должны быть абсолютными. Вывод скрипта notify_url в браузер пользователя не выводится.

Подписка на контент

Функция подписки PayPal очень удобна, однако имеет существенный недостаток. Дело в том, что если подписка имеет пробный период, то пользователь может отменить подписку, пока пробный период не закончился, и подписаться снова, получив таким образом еще один пробный период, и так до бесконечности. Возможны два выхода. Первый, самый простой - вообще не использовать пробные периоды. Второй - предоставлять лишь ограниченный доступ в течение пробного периода. Также имейте в виду, что не существует механизма автоматического возврата денег при отмене подписки пользователем в середине цикла.

IPN-скрипт для подписки должен обрабатывать несколько видов IPN. Это часто вызывает затруднения. Опишу подробнее различные виды IPN (txn_type)
subscr_signup подписка создана. Этот IPN уведомляет лишь о подписке, но не о платеже.
subscr_payment осуществлен платеж либо за пробный период (если он не бесплатный), либо за основной цикл подписки.
subscr_failed попытка платежа завершилась неудачно. Этот IPN отправляется только как уведомление, обычно никаких действий при этом предпринимать не нужно.
subscr_cancel подписка отменена покупателем, продавцом или автоматически (при невозможности осуществить платеж). При получении этого IPN не следует закрывать доступ пользователя к ресурсу, так как подписка может быть отменена в середине цикла, за который уже заплачено.
subscr_eot окончание подписки. При получении этого IPN нужно запретить пользователю доступ к ресурсу.
subscr_modify изменение параметров подписки.
В большинстве случаев достаточно реализовать обработку subscr_signup, subscr_cancel, subscr_payment и subscr_eot.

Пусть имеется некий ресурс, к которому мы хотим предоставить платный доступ. Оплата составляет $10 в месяц, предоставляется неделя бесплатного ограниченного доступа.

Заключение

В заключение несколько советов
Никогда не доверяйте данным, полученным IPN-скриптом, до получения ответа VERIFIED от PayPal. Храните данные об обработанных транзакциях, и после получения ответа VERIFIED проверяйте, не обрабатывалась ли эта транзакция ранее.
Для всех возможных IPN определите, какие данные вы предполагаете получить. Любое несовпадение подозрительно.
Не используйте для идентификации покупателей payer_email - e-mail может быть изменен. Используйте payer_id
Получение txn_type=web_accept или txn_type=subscr_payment еще не означает, что вы получили оплату. Всегда проверяйте payment_status=completed. Единственное исключение: у вас неамериканский аккаунт и pending_reason=intl
Ограничивайте размер передаваемого IPN-скрипту POST-запроса на уровне нескольких килобайт
# httpd.conf
<files my_ipn_script.php>
php_admin_value post_max_size 10K
</files>
В любой системе может произойти сбой, PayPal не является исключением. Если IPN-скрипт получил подозрительные данные, следует писать в лог и уведомлять администратора. Полезно также вывести форму, заполнив которую, пользователь может отправить сообщение о проблеме.