Подключение 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