Знакомимся с PayPal API

:

На данный момент, PayPal — это самая популярная платформа электронных платежей. Именно то, насколько легко можно открыть счет и начать получать на него средства, по сравнению с традиционными способами получения платежей, и является причиной #1 его запредельной популярности. Второй причиной, многие со мной согласятся, является мощный API, предоставляемый PayPal. В этом топике я по порядку разложу все способы и трюки, связанные с работой PayPal API, чтобы вы избежали проблем с его интеграцией.

Предупреждение: PayPal API является одним из самых отвратительных, что я когда либо видел. Несоответствующая действительности, кое-где неполная или несогласованная документация, непредсказумые отказы и существенная разница между работой API в реальных условиях и в песочнице, все это по сути боль в заднице, с которой приходится сталкиваться.

Часть 1: Типы платежей в PayPal


PayPal предлагает целый спектр способов платежей, что не может не запутать человека, малознакомого с PP:

Экспресс-платеж
Основной сервис из целого ряда аналогичных, предлагаемых PayPal. Экспресс-платеж позволит вам получать средства, не имея аккаунта продавца, и в общем-то каких бы то ни было особых требований, окромя банального подтверждения аккаунта (либо через банковский аккаунт, либо через кредитную\дебетовую карту), нет. Раньше, пользователи могли принимать экспресс-платежи только от других пользователей PayPal, но с тех пор, как в систему ввели возможность оплаты прямо с банковской карты для тех, у кого нет аккаунта, получать платежи можно практически от всех, у кого есть карта. Прошу заметить, что экспресс-платежи целиком и полностью выполняются на платформе PayPal, таким образом данный метод невозможно в полной мере интегрировать на ваш веб-сайт.

Прямой платеж
Этот метод позволит вам получать платежи с банковских карт через простейший вызов API. Это позволит вам полностью интегрировать весь процесс перевода на ваш веб-сайт, что, в некоторых случаях, сделает покупки для ваших пользователей более удобными. Есть еще одна вариация прямого платежа, которая позволит вам авторизовать перевод и выполнить его только через некоторый промежуток времени. Метод с откладыванием платежа доступен только пользователям из США, Канады и Соединенного Королевства.

Повторяющийся платеж
Этот метод позволит вам повторно снимать средства со счета пользователя (к примеру, он используется в ежемесячной оплате за какую-нибудь подписку).

Массовый платеж
Массовый платеж позволит разделить полученные средства между несколькими аккаунтами.

Адаптивные платежи
Этот вариант в принципе выполняет ту же функцию, что и предыдущий, но с некоторыми отличиями (я вроде бы уже упомянул о том, что PayPal API запутанный и полный излишеств).

Этот список нельзя назвать полным, но в нем перечислены самые популярные способы получения средств (Хотите больше — добро пожаловать в документацию).

Часть 2: Создание запроса к API
PayPal поддерживает два формата передачи данных через протокол HTTP: NVP и SOAP. NVP — аббревиатура к Name-Value Pair (Пара Имя-Значение), тем временем SOAP означает Simple Object Access Protocol. Я расскажу только о NVP, так как он гораздо более гибок по сравнению с SOAP.

У всех методов, перечисленных в ч. 1 имеются собственные параметры, но у них у всех есть несколько одинаковых основных параметров, которые передаются для идентификации API-аккаунта и авторизации платежа:

  • USER
    Имя вашего PayPal API аккаунта;
  • PWD
    Пароль вашего PayPal API аккаунта;
  • VERSION
    Номер версии NVP API, к примеру 74.0 (последняя версия на момент написания статьи);
  • SIGNATURE
    Электронная подпись PayPal API. Параметр следует использовать только в том случае, если вы используете сертификат для авторизации;
Последним из обязательных параметров является METHOD, который объявляет, какой способ передачи средств мы будем использовать.

Запросы передаются с помощью протокола HTTPS. Для примера мы будем использовать cURL для формирования нашего простого запроса, а затем поместим весь процесс в отдельный класс:

class Paypal {
   /**
    * Последние сообщения об ошибках
    * @var array
    */
   protected $_errors = array();

   /**
    * Данные API
    * Обратите внимание на то, что для песочницы нужно использовать соответствующие данные
    * @var array
    */
   protected $_credentials = array(
      'USER' => 'seller_1297608781_biz_api1.lionite.com',
      'PWD' => '1297608792',
      'SIGNATURE' => 'A3g66.FS3NAf4mkHn3BDQdpo6JD.ACcPc4wMrInvUEqO3Uapovity47p',
   );

   /**
    * Указываем, куда будет отправляться запрос
    * Реальные условия - https://api-3t.paypal.com/nvp
    * Песочница - https://api-3t.sandbox.paypal.com/nvp
    * @var string
    */
   protected $_endPoint = 'https://api-3t.sandbox.paypal.com/nvp';

   /**
    * Версия API
    * @var string
    */
   protected $_version = '74.0';

   /**
    * Сформировываем запрос
    *
    * @param string $method Данные о вызываемом методе перевода
    * @param array $params Дополнительные параметры
    * @return array / boolean Response array / boolean false on failure
    */
   public function request($method,$params = array()) {
      $this -> _errors = array();
      if( empty($method) ) { // Проверяем, указан ли способ платежа
         $this -> _errors = array('Не указан метод перевода средств');
         return false;
      }

      // Параметры нашего запроса
      $requestParams = array(
         'METHOD' => $method,
         'VERSION' => $this -> _version
      ) + $this -> _credentials;

      // Сформировываем данные для NVP
      $request = http_build_query($requestParams + $params);

      // Настраиваем cURL
      $curlOptions = array (
         CURLOPT_URL => $this -> _endPoint,
         CURLOPT_VERBOSE => 1,
         CURLOPT_SSL_VERIFYPEER => true,
         CURLOPT_SSL_VERIFYHOST => 2,
         CURLOPT_CAINFO => dirname(__FILE__) . '/cacert.pem', // Файл сертификата
         CURLOPT_RETURNTRANSFER => 1,
         CURLOPT_POST => 1,
         CURLOPT_POSTFIELDS => $request
      );

      $ch = curl_init();
      curl_setopt_array($ch,$curlOptions);

      // Отправляем наш запрос, $response будет содержать ответ от API
      $response = curl_exec($ch);

      // Проверяем, нету ли ошибок в инициализации cURL
      if (curl_errno($ch)) {
         $this -> _errors = curl_error($ch);
         curl_close($ch);
         return false;
      } else  {
         curl_close($ch);
         $responseArray = array();
         parse_str($response,$responseArray); // Разбиваем данные, полученные от NVP в массив
         return $responseArray;
      }
   }
}

Обратите внимание на то, что я использовал CA-сертификат для SSL-валидации. Файл сертификата можно найти на сайте cURL. Не забудьте изменить путь к сертификату в соответствии с тем расположением, куда вы его загрузили.

Ответ на запрос будет в формате NVP, и я его разбил в массив. Параметр ACK указывает результат обработки запроса: Success или SuccessWithWarning в случае, если запрос был успешным, Error или Warning в случае, если запрос провалился.

Запрос может быть отклонен по множеству причин, причем для каждого метода платежа есть свои причины, которые детально рассматриваются в документации. Чуть позже мы рассмотрим некоторые из них и научимся их устранять. Кстати, обратите внимание на то, что все параметры в запросе чувствительны к регистру, зачастую именно это является причиной сбоя.

Часть 3: Работаем с экспресс-платежом
Одним из самых популярных способов перевода средств является экспресс-платеж, который позволяет получать средства без открытия специального аккаунта (Website Payments Pro), который доступен только гражданам США, причем платеж проводится на стороне PayPal, что не требует никакой защиты.

Весь платеж проходит согласно следующему алгоритму:

  1. Мы запрашиваем токен доступа от PayPal, отправляя детали перевода;
  2. Если токен получен, мы перенаправляем пользователя на сайт PayPal, используя полученный токен;
  3. Пользователь проводит или отменяет платеж на платформе PayPal, а затем перенаправляется назад на наш сайт;
  4. Мы завершаем платеж, либо когда пользователь будет перенаправлен назад, либо через Instant Payment Notification (IPN).

1. Получаем токен: SetExpressCheckout
Запуск процесса экспресс-оплаты происходит путем отправки данных о платеже в PayPal API, после чего мы получаем токен, который идентифицирует нашу транзакцию. Этот токен пригодится на следующем шаге, когда мы направим пользователя на платформу PayPal.

Следующие параметры должны быть указаны в запросе:

  • METHOD
    Здесь нужно указать, какой способ оплаты мы выбираем (к примеру SetExpressCheckout);
  • RETURNURL
    Адрес, на который будет перенаправлен пользователь после успешного совершения платежа;
  • CANCELURL
    Адрес, на который будет перенаправлен пользователь, если во время платежа произошла ошибка;
  • PAYMENTREQUEST_0_AMT
    Итоговая сумма для перевода. Указывать нужно два десятичных числа, разделенных точкой (.). По желанию можно использовать запятую (,) для разделения тысяч;
  • PAYMENTREQUEST_0_ITEMAMT
    Итоговая стоимость, не включающая в себя комиссию, таксы, стоимость доставки и прочие доп. расходы. Если таковых не имеется, значение этого параметра должно соответствовать значению PAYMENTREQUEST_0_AMT.
Так же мы можем передать дополнительные параметры, которые позволят расширить набор информации о платеже, у некоторых из параметров есть значения по-умолчанию:
  • PAYMENTREQUEST_0_CURRENCYCODE
    Этот параметр определяет валюту, в которой будут проводиться все операции. Указывать нужно трехзначный код. По-умолчанию установлено значение USD;
  • PAYMENTREQUEST_0_SHIPPINGAMT
    Стоимость доставки заказа;
  • PAYMENTREQUEST_0_TAXAMT
    Полная сумма всех комиссий (необходимо только в том случае, если в заказе находится несколько позиций и на каждую из них ставится собственная комиссия);
  • PAYMENTREQUEST_0_DESC
    Описание перевода.
В случае, если пользователь платит сразу за несколько товаров, то можно для удобства сделать список товаров:
  • L_PAYMENTREQUEST_0_NAMEm
    Имя товара;
  • L_PAYMENTREQUEST_0_DESCm
    Описание товара;
  • L_PAYMENTREQUEST_0_AMTm
    Стоимость единицы;
  • L_PAYMENTREQUEST_0_QTYm
    Количество товара.

Переменная m идентифицирует конкретный товар (используйте один и тот же индекс переменной для параметров, применимых к одному и тому же товару).

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

Мы используем функцию, написанную нами ранее в ч. 2, чтобы сформировать запрос SetExpressCheckout:

// Параметры нашего запроса
$requestParams = array(
   'RETURNURL' => 'http://www.yourdomain.com/payment/success',
   'CANCELURL' => 'http://www.yourdomain.com/payment/cancelled'
);

$orderParams = array(
   'PAYMENTREQUEST_0_AMT' => '500',
   'PAYMENTREQUEST_0_SHIPPINGAMT' => '4',
   'PAYMENTREQUEST_0_CURRENCYCODE' => 'GBP',
   'PAYMENTREQUEST_0_ITEMAMT' => '496'
);

$item = array(
   'L_PAYMENTREQUEST_0_NAME0' => 'iPhone',
   'L_PAYMENTREQUEST_0_DESC0' => 'White iPhone, 16GB',
   'L_PAYMENTREQUEST_0_AMT0' => '496',
   'L_PAYMENTREQUEST_0_QTY0' => '1'
);

$paypal = new Paypal();
$response = $paypal -> request('SetExpressCheckout',$requestParams + $orderParams + $item);

2. Используем полученный токен для перенаправления на PayPal
Если запрос был успешным, мы получим токен в параметре TOKEN в ответе от PayPal.

if(is_array($response) && $response['ACK'] == 'Success') { // Запрос был успешно принят
      $token = $response['TOKEN'];
      header( 'Location: https://www.paypal.com/webscr?cmd=_express-checkout&token=' . urlencode($token) );
}
Теперь пользователь отправляется на платформу PayPal, где будет обрабатываться весь процесс перевода. Когда пользователь успешно проведет или отменит платеж, его отправит на одну из соответствующих страниц, которые мы указали в запросе.

3. Завершение перевода
Предположим, что пользователь подтвердил перевод, после чего его отправит назад на наш вебсайт. В этот самый момент, нам надо использовать две соответствующие функции API: DoExpressCheckoutPayment завершит перевод, но перед этим нам необходимо получить больше информации о покупателе используя GetExpressCheckoutDetails.

PayPal перенаправит пользователя назад на наш веб-сайт вместе с токеном, который мы будем использовать для вызова этих функций. Токен будет указан в URL в параметре token. Мы проверим его наличие в адресе, на который перебросит пользователя и отправим необходимые запросы к API в случае, если он будет присутствовать.

Функция GetExpressCheckoutDetails требует передачи одного лишь токена, а DoExpressCheckoutPayment требует указания нескольких дополнительных параметров:

  • PAYMENTREQUEST_0_PAYMENTACTION
    Этот параметр определяет необходимое нам действие. В значении должно быть указано Sale в случае, если мы не указали другое действие в функции SetExpressCheckout (среди возможных значений присутствуют Authorization и Capture);
  • PAYERID
    Этот параметр указывает уникальный идентификатор аккаунта PayPal. Он, как и токен, будет возвращен в URL при перенаправлении пользователя (в параметре PayerID).
if( isset($_GET['token']) && !empty($_GET['token']) ) { // Токен присутствует
   // Получаем детали оплаты, включая информацию о покупателе.
   // Эти данные могут пригодиться в будущем для создания, к примеру, базы постоянных покупателей
   $paypal = new Paypal();
   $checkoutDetails = $paypal -> request('GetExpressCheckoutDetails', array('TOKEN' => $_GET['token']));

   // Завершаем транзакцию
   $requestParams = array(
      'PAYMENTREQUEST_0_PAYMENTACTION' => 'Sale',
      'PAYERID' => $_GET['PayerID']
   );

   $response = $paypal -> request('DoExpressCheckoutPayment',$requestParams);
   if( is_array($response) && $response['ACK'] == 'Success') { // Оплата успешно проведена
      // Здесь мы сохраняем ID транзакции, может пригодиться во внутреннем учете
      $transactionId = $response['PAYMENTINFO_0_TRANSACTIONID'];
   }
}

Часть 4: Работаем с прямым платежом
Прямой платеж позволяет полностью контролировать весь процесс перевода средств прямо на вашем сайте. По какой-то непонятной причине, в этом случае покупатели без PayPal-аккаунта не смогут оплачивать услуги и товары на вашем сайте, но зато можно будет сделать весь процесс максимально простым, что положительно скажется на мнении пользователей. Полный контроль над процессом фактически позволяет нам оптимизировать и увеличивать продажи.


Этот способ немного проще, чем экспресс-платеж, потому, что все взаимодействие с пользователем происходит на вашем веб-сайте, и нам понадобится произвести всего лишь один запрос к API: DoDirectPayment.

Но стоит обратить внимание на то, что придется сделать еще несколько запросов, если вы собираетесь списать деньги со счета пользователя через определенный промежуток времени (например, когда вы ждете передачи товара в службу доставки). Для этого будут использоваться методы Authorization и Capture, о которых я не стану вам рассказывать, но все таки знать об их существовании надо.

1. Необходимые параметры
Прямой платеж, как и было сказано ранее, требует указания совершенно иных параметров. Несмотря на то, что параметры, указывающие детали перевода, схожи с экспресс-платежом (те же назначения, другие имена, так веселее), этот метод требует информацию о банковской карте и адресе.

Основные параметры прямого платежа:

  • METHOD
    Очевидно, в этом параметре надо указать DoDirectPayment;
  • IPADDRESS
    В этом параметре необходимо указать IP-адрес покупателя. Используя PHP, мы запросто можем получить его с помощью $_SERVER['REMOTE_ADDR'], правда, придется проделать немного больше работы, если на вашем сервере PHP связывается с внешним миром через посредника (к примеру, если вы используете nginx);
  • PAYMENTACTION
    Этот параметр определяет желаемое нами действие. Как и было несколько раз сказано ранее, нам нужно указать в нем Sale.

Данные банковской карты:

  • CREDITCARDTYPE
    Тип банковской карты (например Visa, MasterCard и другие);
  • ACCT
    Номер банковской карты (вам тоже нравятся эти чудесные аббревиатуры?);
  • EXPDATE
    Дата истечения срока работы карты в формате MMГГГГ (две цифры месяц, четыре цифры год);
  • CVV2
    Трех-четырехзначный код, так же известен как защитный код. Указан на обратной стороне карты.

Данные о покупателе:

  • FIRSTNAME, LASTNAME
    Имя и фамилия покупателя, в отдельных полях. По желанию можно так же передать электронную почту пользователя в параметре EMAIL;
  • CITY, STATE, COUNTRYCODE, ZIP
    Город, штат, двузначный код страны, почтовый индекс, все эти поля обязательны;
  • STREET, STREET2
    Две строки для адреса. Обязательна только первая.
Адрес будет обработан системой проверки адреса (AVS). Вам будет возвращена специфическая ошибка, если транзакция была отклонена всвязи с неправильным адресом.

Параметры для указания деталей оплаты схожи с аналогичными для экспресс-платежа, но с некоторыми отличиями в именах (надо использовать AMT, ITEMAMT, CURRENCYCODE, SHIPPINGAMT, TAXAMT и DESC). За подробностями обращайтесь к документации.

Таким же образом, для ввода информации о товарах необходимо использовать следующие параметры: L_NAMEm, L_DESCm, L_AMTm и L_QTYm. Переменная m используется для разграничения параметров для каждого отдельного товара (замените на 0, 1 и далее для нумерованных позиций в заказе). Обратитесь к документации за более полным списком.

2. Проведение транзакции
Отправка запроса с использованием написанной нами ранее функции очень схожа со случаем с экспресс-платежом. Мы передаем все параметры точно таким же образом, но в качестве метода указываем DoDirectPayment.

$requestParams = array(
   'IPADDRESS' => $_SERVER['REMOTE_ADDR'],
   'PAYMENTACTION' => 'Sale'
);

$creditCardDetails = array(
   'CREDITCARDTYPE' => 'Visa',
   'ACCT' => '4929802607281663',
   'EXPDATE' => '062012',
   'CVV2' => '984'
);

$payerDetails = array(
   'FIRSTNAME' => 'John',
   'LASTNAME' => 'Doe',
   'COUNTRYCODE' => 'US',
   'STATE' => 'NY',
   'CITY' => 'New York',
   'STREET' => '14 Argyle Rd.',
   'ZIP' => '10010'
);

$orderParams = array(
   'AMT' => '500',
   'ITEMAMT' => '496',
   'SHIPPINGAMT' => '4',
   'CURRENCYCODE' => 'GBP'
);

$item = array(
   'L_NAME0' => 'iPhone',
   'L_DESC0' => 'White iPhone, 16GB',
   'L_AMT0' => '496',
   'L_QTY0' => '1'
);

$paypal = new Paypal();
$response = $paypal -> request('DoDirectPayment',
   $requestParams + $creditCardDetails + $payerDetails + $orderParams + $item
);

if( is_array($response) && $response['ACK'] == 'Success') { 
   $transactionId = $response['TRANSACTIONID'];
}

Часть 5: Обработка ошибок
Если бы мы жили в идеальном мире, этой части бы не было. В реальности же вам придется обращаться к этой части или аналогичным разделам в других статьях достаточно часто. PayPal может отказать в проведении платежа по одной из целой кучи ошибок, причем исправление некоторых из них нам не подконтрольно.

Переменная $response, в которой содержится ответ от функции paypalApiRequest(), может содержать в параметре ACK значение, не соответствующее Success, а именно:

  • Success
    Операция была проведена успешно;
  • SuccessWithWarning
    Операция была проведена успешно, но не идеально. API вернул сообщение, объясняющее, что нужно поправить;
  • Failure
    Операция не была успешной, в ответе есть коды ошибок, объясняющие отказ;
  • FailureWithWarning
    Операция не была успешной, в ответе есть коды ошибок и сообщения, на которые вам стоит взглянуть.

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

Ошибки, возвращаемые PayPal, заключены в четыре параметра в ответе:

  • L_ERRORCODE0
    Цифровой код ошибки, который можно расшифровать по списку кодов ошибок PayPal;
  • L_SHORTMESSAGE0
    Короткое сообщение, объясняющее проблему;
  • L_LONGMESSAGE0
    Подробное сообщение, объясняющее проблему;
  • L_SEVERITYCODE0
    Код трудности. Я не имею никакого понятия зачем он нужен, документация про него молчит, ну и ладно, давайте про него забудем.
Переменная 0 в конце параметров — это порядковый номер возникшей ошибки (если ошибок несколько, то в переменной будет стоять 1, 2 и так далее).

Вот небольшой список наиболее частых ошибок:

  • 10002
    Данные авторизации в API не прошли проверку;
  • 81***
    Один из необходимых параметров отсутствует;
  • 104**
    В одном из параметров было передано некорректное значение.

Как решать все эти проблемы на практике?
Некоторые ошибки PayPal могут содержать в своем описании приватную информацию, которую вы вряд ли захотите показывать пользователям. По этой причине, отображение ошибок пользователям рекомендуется отключить, даже если это может оказаться полезным.

В большинстве случаев, я бы посоветовал сделать следующее:

  1. Создайте белый список ошибок, которые можно показывать пользователям (к примеру, неверные данные о банковской карте);
  2. Сделайте так, что бы при возвращении ошибок, они сверялись с белым списком;
  3. Если ошибка в этом списке отсутствует, пользователю надо отобразить какую-нибудь общую ошибку, к примеру «Во время обработки вашего платежа произошла ошибка. Подождите несколько минут или свяжитесь с нами для решения данной проблемы».


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

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

Оригинал статьи: Getting Started With The PayPal API, Eran Galperin, 5 сентября 2011.

Я очень надеюсь, что этот перевод действительно будет полезен хабрасообществу. Другими словами, я все еще не теряю надежды на праздник 24 сентября. Поживем — увидим.

image
Этот текст распространяется на условиях лицензии « Creative Commons Attribution-NonCommercial-ShareAlike 3.0».
Вы можете копировать, редактировать и использовать не в коммерческих целях этот текст при обязательном указании авторства и сохранении оригинальной лицензии.