Подключение PayPal Standard Checkout
В данном руководстве последовательно описан мой опыт внедрения PayPal Standard Checkout с использованием языка Java на платформе Google App Engine. Данная статья рассчитана на людей уже имеющих опыт работы с облачной платформой GAE.
Задача
Потребовалось мне интегрировать платёжную систему PayPal на сайт собственного проекта который будет предоставлять сервис по подписке. Начав работу с PayPal Express Checkout API через некоторое время пришло осознание того что система приёма платежа становится слишком громоздкой, в то время как у готовых кнопок Standard Checkout отсутствует необходимая гибкость, которая требуется в случае интеграции сайта с другими платёжными системами.
Выход был найден в использовании инструментов Standard Checkout которые предоставляет PayPal разработчикам сторонних “корзин” для сайта.
Изменение типа счёта
Для того чтобы начать принимать платежи с помощью платёжной системы PayPal нам первым делом требуется произвести изменение типа счёта. При первой регистрации в PayPal нам даётся стандартный тип счёта — “Personal”, по нему можно лишь оплачивать товары и услуги. Для автоматизации приёма платежей необходим тип счёта “Premier” либо “Business”. Чем отличаются типы счёта можно посмотреть в таблице сравнения. В целом отличие в том что счёт “Business” предоставляет использование счёта разными пользователями. Поскольку данная особенность необходима лишь на больших предприятиях выбираем тип “Premier”. Тип счёта указан в левом верхнем углу под словами “Добро пожаловать...”
Уведомление о поступившем платеже
Существуют два способа уведомления о платеже — Payment Data Transfer (PDT) и Instant Payment Notification (IPN). Поскольку у IPN есть такие преимущества как асинхронная работа, то выбираем именно этот способ. Более подробная информация находится в Order Management Integration Guide
Отключение PDT:
“PayPal” — “Профиль” — “Мои инструменты продаж” — “Настройки веб-сайта” — “Передача сведений о платеже (необязательно)” — Выкл.
Активация IPN:
“PayPal” — “Профиль” — “Мои инструменты продаж” — “Уведомления о мгновенных платежах” — “Изменить параметры” — “”Принимать IPN-сообщения (Включено)” и добавить “URL-адрес для уведомления””
Возвращение покупателя на сайт продавца
После оплаты покупки в мерчанте PayPal, покупателя рекомендуется автоматически направить на страницу уведомления об успешно проведённом платеже или на сайт продавца. “PayPal” — “Профиль” — “Мои инструменты продаж” — “Настройки веб-сайта”.
“Автоматический возврат” — Вкл. и указать “url возврата”.
Создание счетов в PayPal песочнице
Для тестирования процесса приёма платежей существует так называемая песочница — sandbox. За операциями в песочнице не стоят реальные денежные средства, мы оперируем просто цифрами на созданных виртуальных тестовых счетах. После регистрации на PayPal Sandbox переходим в раздел “Test accounts” и создаём два “Preconfigured” счёта — один представляет собой покупателя (Buyer), другой представляет собой продавца (Seller). При создании счёта Seller его типом по умолчанию является тип “Business”. В тестировании разница между Premier и Business отсутствует. При создании счёта рекомендуется вписать любую сумму в валюте открываемого счёта. Эта сумма будет расходоваться или добавляться в зависимости от операций между созданными счетами. Автоматически созданный email и пароль рекомендуется записать поскольку эти реквизиты являются доступом к счетам в песочнице по адресу www.sandbox.paypal.com
Указание типа кодировки
Работу с данными производим в кодировке UTF-8. Для смены подировки переходим по адресу https://www.sandbox.paypal.com/cgi-bin/webscr?cmd=_profile-language-encoding Далее выбираем язык вебсайта, сохраняем его, возвращаемся назад и после нажатия кнопки «Дополнительные возможности» («More Options») выбираем необходимую кодировку.
Получение документации
Работа с мерчантом PayPal возможна двумя способами:
С использованием PayPal Express Checkout API и с использованием Standard Checkout.
Последний проще и лучше всего подходит для получения платежей за небольшой набор фиксированных товаров или услуг. его и выбираем. Для более детального ознакомления с данным способом желательно загрузить Standard Checkout Integration Guide
Кроме того, абсолютно вся документация в формате PDF и HTML находится по ссылке
Получения платежа
Принцип работы PayPal Standard Chechout проиллюстрирован на изображении

- На странице JSP находится форма POST запроса с указанием с указанием как минимум четырёх параметров:
- amount_1 — цена одной единицы товара
- business — email адрес PayPal счёта продаца (Seller счёт в песочнице)
- item_name_1 — наименование товара
- upload — уведомление что данная “корзина” создана сторонним поставщиком
- После нажатия на кнопку формы покупатель переходит на сайт песочницы PayPal по адресу https://www.sandbox.paypal.com/cgi-bin/webscr где ему требуется ознакомиться с выставленным счётом и авторизоваться (Buyer счёт в песочнице)
- Покупателю предлагается оплатить счёт нажатием на соответствующую кнопку
- Покупатель автоматически переходит на сайт продавца по url указанном в профиле продавца или по url указанном в параметре return формы POST
- Мерчант PayPal посылает IPN POST запрос по url указанному в профиле
Уведомления о полученном платеже (IPN)
Принцип работы IPN проиллюстрирован на изображении

- Ожидание POST запроса от мерчанта PayPal
- Создаём запрос к PayPal который содержит абсолютно те же IPN переменные с пришедшими значениями добавляя к запросу заголовок cmd=_notify-validate
- Отправляем запрос на sandbox.paypal.com
- Мерчант PayPal должен в ответ послать сообщение VERIFIED или INVALID
- Проверяем статус ответа который должен представлять собой код 200
- В случае если ответом является слово VERIFIED делаем следующие проверки:
- Если верифицированный ответ от мерчанта PayPal прошёл все проверки, обрабатываем действие базирующееся на значении переменной txn_type если она существует. В противном случае обрабатываем действие базирующуюся на значении переменной reason_code. Значения которые могут принимать данные переменные указаны в Order Management Integration Guide
- В случае если ответом является INVALID или код ответа не является 200 сохраняем сообщение для дальнейшего разбирательства
Работа с GAE
Код JSP страницы order.jsp:
<%@ page contentType="text/html; charset=UTF-8" language="java" %> // information about values of parameters please see at url // https://www.paypal.com/en_US/pdf/PP_WebsitePaymentsStandard_IntegrationGuide.pdf <% // final String strMerchantUrl = "https://www.paypal.com/cgi-bin/webscr"; final String strMerchantUrl = "https://www.sandbox.paypal.com/cgi-bin/webscr"; final String strEmail = "email_of_seller"; final String strDescription = "my item description"; final String strAmount = "1.3"; final String strCurrencyCode = "USD"; // your counter of invoice String strInvoice = "234234234"; final String returnUrl = "site_of_seller"; %> <form action=<%= strMerchantUrl %> method="post" accept-charset="UTF-8"> <input type="hidden" name="cmd" value="_cart" /> <input type="hidden" name="upload" value="1" /> <input type="hidden" name="business" value="<%= strEmail %>" /> <input type="hidden" name="item_name_1" value="<%= strDescription %>" /> <input type="hidden" name="amount_1" value="<%= strAmount %>" /> <input type="hidden" name="currency_code" value="<%= strCurrencyCode %>" /> <input type="hidden" name="no_shipping" value="1" /> <input type="hidden" name="invoice" value="<%= strInvoice %>" /> <input type="hidden" name="return" value="<%= returnUrl %>" /> <input type="image" value="PayPal" src="https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif" alt="Submit button" align="left" style="margin-right:7px;" /> </form>
Код IPN обработчика пакете payment.paypal.Ipn.java
package payment.paypal; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.BufferedReader; import java.io.InputStreamReader; import java.util.logging.Logger; import java.util.Enumeration; import java.net.URLEncoder; import java.net.URL; import java.net.URLConnection; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @SuppressWarnings("serial") public class Ipn extends HttpServlet { private static final Logger log = Logger.getLogger(Ipn.class.getName()); public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { try { // read post from PayPal system and add 'cmd' Enumeration<?> en = req.getParameterNames(); String str = "cmd=_notify-validate"; while (en.hasMoreElements()) { String paramName = (String) en.nextElement(); String paramValue = req.getParameter(paramName); str = str + "&" + paramName + "=" + URLEncoder.encode(paramValue, "UTF-8"); } // test log IPN string log.info("[Paypal IPN string] " + str); // post back to PayPal system to validate // URL url = new URL("https://www.paypal.com/cgi-bin/webscr"); URL url = new URL("https://www.sandbox.paypal.com/cgi-bin/webscr"); URLConnection conn = url.openConnection(); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); OutputStreamWriter wr = new OutputStreamWriter( conn.getOutputStream()); wr.write(str); wr.flush(); // response from PayPal - VERIFIED or INVALID BufferedReader br = new BufferedReader(new InputStreamReader( conn.getInputStream())); String line = br.readLine(); // test log check string log.info("[PayPal check string] " + line); wr.close(); br.close(); // assign posted variables to local variables String itemName = req.getParameter("item_name"); String itemNumber = req.getParameter("item_number"); String paymentStatus = req.getParameter("payment_status"); String paymentAmount = req.getParameter("mc_gross"); String paymentCurrency = req.getParameter("mc_currency"); String txnId = req.getParameter("txn_id"); String receiverEmail = req.getParameter("receiver_email"); String payerEmail = req.getParameter("payer_email"); // check notification validation if (line.equals("VERIFIED")) { // check that txnId has not been previously processed // check that receiverEmail is your Primary PayPal email // check that paymentAmount/paymentCurrency are correct // process payment } else if (line.equals("INVALID")) { // log for investigation log.warning(line); } else { // error } } catch (Exception e) { log.warning("[ipn] " + e); } } }
Маппинг пути в файле /war/WEB-INF/web.xml
<!-- payment paypal --> <servlet> <servlet-name>Ipn</servlet-name> <servlet-class>payment.paypal.Ipn</servlet-class> </servlet> <!-- --> <!-- payment paypal mapping --> <servlet-mapping> <servlet-name>Ipn</servlet-name> <url-pattern>/payment/paypal/ipn</url-pattern> </servlet-mapping> <!-- -->
Создаём конфигурацию лога в файле /war/WEB-INF/appengine-web.xml
<!-- Configure java.util.logging --> <system-properties> <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/> </system-properties>
Статья была написана по мотивам топика http://habrahabr.ru/blogs/php/128198/, а также после долгих поисков по интернету простого и удобного способа получения оплаты с помощью PayPal. Разумеется охвачена не вся информация, к примеру в коде отсутствует проверка суммы оплаты. Без данной проверки злонамеренный пользователь может сохранить html страницу формы, отредактировать цену в сторону уменьшения и получить скажем цифровой товар или подписку по цене ниже покупной. Проверки зависят от реализации сервиса и требуют индивидуального подхода при интеграции платёжной системы на сайт продавца.
В работе использовался образец кода Java/JSP предоставленный PayPal
Данный способ можно использовать для оплаты услуг, продаже подписок на сервис, товаров, а также пополнения баланса учётной записи пользователя на сайте.
upd: указание кодировки, спасибо за мысль winbackgo
