diff --git "a/.github/ISSUE_TEMPLATE/\321\201\320\276\320\276\320\261\321\211\320\265\320\275\320\270\320\265-\320\276\320\261-\320\276\321\210\320\270\320\261\320\272\320\265.md" "b/.github/ISSUE_TEMPLATE/\321\201\320\276\320\276\320\261\321\211\320\265\320\275\320\270\320\265-\320\276\320\261-\320\276\321\210\320\270\320\261\320\272\320\265.md" new file mode 100644 index 0000000..92b4896 --- /dev/null +++ "b/.github/ISSUE_TEMPLATE/\321\201\320\276\320\276\320\261\321\211\320\265\320\275\320\270\320\265-\320\276\320\261-\320\276\321\210\320\270\320\261\320\272\320\265.md" @@ -0,0 +1,27 @@ +--- +name: Сообщение об ошибке +about: Сообщение об ошибке +title: '' +labels: '' +assignees: yourpayments + +--- + +**Опишите ошибку** +Понятное и полное описание проблемы + +**Как воспроизвести** +Шаги для воспроизведения проблемы: +1. +2. +3. + +**Скриншоты и вывод программы** +Приведите скриншоты и/или тексты ошибок + +**Окружение: ваше ПО, например:** + - Версия PHP + - OS + - Браузер + +**Дополнительная информация** diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0871971 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "Composer" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml deleted file mode 100644 index 1ef7894..0000000 --- a/.github/workflows/php.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: PHP Composer - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -permissions: - contents: read - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v3 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" - # Docs: https://getcomposer.org/doc/articles/scripts.md - - # - name: Run test suite - # run: composer run-script test diff --git a/.gitignore b/.gitignore index a67d42b..ff93c1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ composer.phar /vendor/ +.idea # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file # composer.lock + +.ai \ No newline at end of file diff --git a/LICENSE b/LICENSE index e762b5f..d15a607 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ Условия лицензирования -Copyright (c) 2022 PayU Russia +Copyright (c) ООО НКО "Твои платежи" (YPMN.RU). Данная лицензия разрешает, безвозмездно, лицам, получившим копию данного программного обеспечения и сопутствующей документации (в дальнейшем именуемыми "Программное Обеспечение"), использовать Программное Обеспечение без ограничений, @@ -8,6 +8,8 @@ Copyright (c) 2022 PayU Russia сублицензирование и/или продажу копий Программного Обеспечения, также как и лицам, которым предоставляется данное Программное Обеспечение, при соблюдении следующих условий: +Данное программное обеспечение используется для обработки платежей через API ООО НКО "Твои платежи" (YPMN.RU). + Вышеупомянутый копирайт и данные условия должны быть включены во все копии или значимые части данного Программного Обеспечения. @@ -20,7 +22,7 @@ Copyright (c) 2022 PayU Russia MIT License -Copyright (c) 2022 PayU Russia +Copyright (c) "Your Payments" Ltd (YPMN.RU). Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,6 +31,8 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Software is used to process payments with "Your Payments" Ltd (YPMN.RU). + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. diff --git a/README.md b/README.md index f8f8d4b..e64d93f 100644 --- a/README.md +++ b/README.md @@ -1,378 +1,78 @@ -# php-payu4 -Примеры использования PayU API v4. - -PayU - многофункциональная платёжная система, поддерживающая не только простые платежи с банковских карт, но и множество -форм оплаты, а также подписки и выплаты на карты. - -Данный репозиторий написан по принципам SOLID, и каждый программный интерфейс снабжен подробной документацией на -русском языке. - -Репозиторий также содержит примеры по принципу "одна строка кода - одна строка документации". +# «Твои Платежи»: Интеграция на PHP +Готовая библиотека + подробные примеры с комментариями. Требования: [PHP 7.4 и выше](https://github.com/yourpayments/php-api-client/blob/main/composer.json) -Репозиторий опубликован в виде [пакета Composer](https://packagist.org/packages/payuru/php-payu4) и может использоваться со всеми современными -фреймворками: Laravel, Symfony, Yii и другими. +![](https://repository-images.githubusercontent.com/638835276/2067d028-b541-4355-b069-3c12c8a28042) -Для работы рекомендуется использовать любую современную IDE (VS Code, Intellij Idea/PHPStorm, -Eclipse, Netbeans, etc), чтобы получать подробные подсказки прямо во время редактирования кода. -![IDE screenshot](screenshot.jpg "IDE screenshot") +[Пакет Composer](https://packagist.org/packages/yourpayments/php-api-client) может +использоваться с любыми фреймворками, платформами и CMS, включая, но не ограничиваясь: Laravel, Bitrix, Wordpress, Yii, Symfony, и др. - -## Требования -Актуальные требования для использования пакета можно посмотреть -в файле [composer.json](https://github.com/payuru/php-payu4/blob/main/composer.json) -в секции "require" - -## Установка -### Composer -[Composer](https://getcomposer.org/) - это инструмент для управления зависимостями в PHP. Он позволяет вам объявить -библиотеки, от которых зависит ваш проект, и он будет управлять ими (устанавливать/обновлять) за вас. +## Установка за 1 минуту ```shell -composer require payuru/php-payu4 -``` - -```php -// Для использования классов, например: -use payuru\phpPayu4\Authorization; -use payuru\phpPayu4\Delivery; -use payuru\phpPayu4\IdentityDocument; -use payuru\phpPayu4\Merchant; -use payuru\phpPayu4\Payment; -use payuru\phpPayu4\Client; -use payuru\phpPayu4\Billing; -use payuru\phpPayu4\ApiRequest; -use payuru\phpPayu4\PaymentException; -use payuru\phpPayu4\Product; -use payuru\phpPayu4\Capture; -use payuru\phpPayu4\Refund; -use payuru\phpPayu4\Std; - -// Подключите загрузчик классов от Composer -require vendor/autoload.php; -``` - -### PHP без фреймворков -Клонируйте или скачайте, а затем подключите файлы этого репозитория - -## Примеры использования -### Начало работы -```php -// Создадим объект Мерчанта с помощью Идентификатора Мерчанта и Секретного Ключа Мерчанта -$merchant = new Merchant('rudevru1', 'hE9I1?3@|C8@w[1I&=y)'); +composer require yourpayments/php-api-client ``` -### Создание (авторизация) платежа -Метод создаёт платёж (транзакцию) в системе PayU. -В зависимости от настройки, средства списываются либо сразу, -либо после отправки метода "capture". -#### Упрощённая интеграция, минимальный набор полей -```php - 'Заказ №' . $merchantPaymentReference, - 'sku' => $merchantPaymentReference, - 'unitPrice' => 1.42, - 'quantity' => 2, -]); - -// Опишем Биллинговую (платёжную) информацию -$billing = new Billing; -// Установим Код страны -$billing->setCountryCode('RU'); -// Установим Имя Плательщика -$billing->setFirstName('Иван'); -// Установим Фамилия Плательщика -$billing->setLastName('Петров'); -// Установим Email Плательщика -$billing->setEmail('test1@payu.ru'); -// Установим Телефон Плательщика -$billing->setPhone('+7-800-555-35-35'); -// Установим Город -$billing->setCity('Москва'); - -// Создадим клиентское подключение -$client = new Client; -// Установим биллинг -$client->setBilling($billing); - -// Создадим платёж -$payment = new Payment; -// Установим позиции -$payment->addProduct($orderAsProduct); -// Установим валюту -$payment->setCurrency('RUB'); -// Создадим и установим авторизацию по типу платежа -$payment->setAuthorization(new Authorization('CCVISAMC',true)); -// Установим номер заказа (должен быть уникальным в вашей системе) -$payment->setMerchantPaymentReference($merchantPaymentReference); -// Установим адрес перенаправления пользователя после оплаты -$payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); -// Установим клиентское подключение -$payment->setClient($client); - -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос -$responseData = $apiRequest->sendAuthRequest($payment, $merchant); -// Преобразуем ответ из JSON в массив -try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты - echo Std::drawPayuButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // .. или сделаем редирект на форму оплаты (опционально) - // Std::redirect($responseData["paymentResult"]['url']); -} catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
- Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
-
-
' . $exception->getMessage() . '
', - 'type' => 'danger', - ]); -} -``` -#### Расширенные возможности, полный набор полей -```php -setName('Синий Мяч'); -// Установим Артикул -$product1->setSku('ball-05'); -// Установим Стоимость за единицу -$product1->setUnitPrice('500'); -// Установим Количество -$product1->setQuantity(1); -// Установим НДС -$product1->setVat(20); - -//Опишем вторую позицию с помощью сокращённого синтаксиса: -$product2 = new Product([ - 'name' => 'Жёлтый Круг', - 'sku' => 'toy-15', - 'unitPrice' => '1600', - 'quantity' => '3', - 'vat' => 0, -]); +(если на вашем проекте нет composer, слонируйте или скачайте, а затем подключите ([require](https://www.php.net/manual/ru/function.require.php)) файлы этого репозитория) -// Опишем Биллинговую (платёжную) информацию -$billing = new Billing; -// Установим Код страны -$billing->setCountryCode('RU'); -// Установим Город -$billing->setCity('Москва'); -// Установим Регион -$billing->setState('Центральный регион'); -// Установим Адрес Плательщика (первая строка) -$billing->setAddressLine1('Улица Старый Арбат, дом 10'); -// Установим Адрес Плательщика (вторая строка) -$billing->setAddressLine1('Офис PayU'); -// Установим Почтовый Индекс Плательщика -$billing->setZipCode('121000'); -// Установим Имя Плательщика -$billing->setFirstName('Иван'); -// Установим Фамилия Плательщика -$billing->setLastName('Петров'); -// Установим Телефон Плательщика -$billing->setPhone('+79670660742'); -// Установим Email Плательщика -$billing->setEmail('test1@payu.ru'); - -// (необязательно) Опишем Доствку и принимающее лицо -$delivery = new Delivery; -// Установим документ, подтверждающий право приёма доставки -$delivery->setIdentityDocument( - new IdentityDocument('123456', 'PERSONALID') -); -// Установим Код страны -$delivery->setCountryCode('RU'); -// Установим Город -$delivery->setCity('Москва'); -// Установим Регион -$delivery->setState('Центральный регион'); -// Установим Адрес Лица, принимающего заказ (первая строка) -$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); -// Установим Адрес Лица, принимающего заказ (вторая строка) -$delivery->setAddressLine1('Офис PayU'); -// Установим Почтовый Индекс Лица, принимающего заказ -$delivery->setZipCode('121000'); -// Установим Имя Лица, принимающего заказ -$delivery->setFirstName('Мария'); -// Установим Фамилия Лица, принимающего заказ -$delivery->setLastName('Петрова'); -// Установим Телефон Лица, принимающего заказ -$delivery->setPhone('+79670660743'); -// Установим Email Лица, принимающего заказ -$delivery->setEmail('test2@payu.ru'); -// Установим Название Компании, в которой можно оставить заказ -$delivery->setCompanyName('ООО "Вектор"'); - -// Создадим клиентское подключение -$client = new Client; -// Установим биллинг -$client->setBilling($billing); -// Установим доставку -$client->setDelivery($delivery); -// Установим IP (автоматически) -$client->setCurrentClientIp(); -// И Установим время (автоматически) -$client->setCurrentClientTime(); - -// Создадим платёж -$payment = new Payment; -// Установим позиции -$payment->addProduct($product1); -$payment->addProduct($product2); -// Установим валюту -$payment->setCurrency('RUB'); -// Создадим и установим авторизацию по типу платежа -$payment->setAuthorization(new Authorization('CCVISAMC',true)); -// Установим номер заказа (должен быть уникальным в вашей системе) -$payment->setMerchantPaymentReference('primer_nomer__' . time()); -// Установим адрес перенаправления пользователя после оплаты -$payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); -// Установим клиентское подключение -$payment->setClient($client); - -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос -$responseData = $apiRequest->sendAuthRequest($payment, $merchant); -// Преобразуем ответ из JSON в массив -try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты - echo Std::drawPayuButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // .. или сделаем редирект на форму оплаты (опционально) - // Std::redirect($responseData["paymentResult"]['url']); -} catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
- Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
-
-
' . $exception->getMessage() . '
', - 'type' => 'danger', - ]); -} -``` -### Страница пользователя после совершения платежа -Данные о состоянии платежа после его создания передаются в параметрах POST ($_POST) -```php -print_r($_POST); -``` -### Получить номер транзакции в PayU (GetStatus) -```php -setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос к API -$responseData = $apiRequest->sendStatusRequest($merchantPaymentReference); -``` - -### Списание средств (Capture) -В зависимости от настройки мерчанта, PayU может списывать денежные средства автоматически, -// Либо с помощью дополнительного запроса, описанного ниже. -```php -setPayuPaymentReference(2297597); - -// Cумма исходной операции на авторизацию -$capture->setOriginalAmount(5300); -// Cумма фактического списания -$capture->setAmount(3700); -// Валюта -$capture->setCurrency('RUB'); - -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос к API -$responseData = $apiRequest->sendCaptureRequest($capture, $merchant); +## Запуск в контейнере docker +Создайте и запустите docker контейнер следующей командой: +```shell +docker compose up ``` -### Отмена платежа (Refund) -```php -setPayuPaymentReference(2297597); -// Cумма исходной операции на списание (Capture) -// Пример: если сумма авторизации была 5300, а сумма списания 3700 (частичное списание), указать 3700 -$refund->setOriginalAmount(3700); -// Cумма фактического списания -$refund->setAmount(3700); -// Установим валюту -$refund->setCurrency('RUB'); -// Создадим HTTP-запрос к API -$apiRequest = new ApiRequest($merchant); -// Включить режим отладки (удалите в рабочей программе!) -$apiRequest->setDebugMode(); -// Переключиться на тестовый сервер (удалите в рабочей программе!) -$apiRequest->setSandboxMode(); -// Отправим запрос к API -$responseData = $apiRequest->sendRefundRequest($refund, $merchant); +либо в фоновом режиме командой: +```shell +docker compose up --detach ``` +После выполнения сервис с документацией и примерами будет доступен по адресу http://localhost:8080/ + +## Примеры с комментариями на русском языке: +##### 1. [Начало работы: настройка интеграции](src/Examples/start.php) + +##### 2. Платежи +1. [Cамый простой платёж](src/Examples/simpleGetPaymentLink.php) +2. [Подробный платёж](src/Examples/getPaymentLink.php) +3. [Платёж через СБП (Систему Быстрых Платежей)](src/Examples/getFasterPayment.php) +4. [Платёж со сплитом (разделением платежа)](src/Examples/getPaymentLinkMarketplace.php) +5. [Списание средств](src/Examples/paymentCapture.php) + +##### 3. Подписки СБП +1. [Создание подписки СБП](src/Examples/getBindingFasterPayment.php) +2. [Оплата по подписке СБП](src/Examples/paymentByFasterBinding.php) + +##### 4. Токенизация карты (чтобы запомнить карту клиента и не вводить повторно) +1. [Создание платёжного токена ](src/Examples/getToken.php) +2. [Оплата токеном](src/Examples/paymentByToken.php) + +##### 5. Отчёты +1. [Проверка статуса платежа](src/Examples/paymentGetStatus.php) +2. [Запрос детального отчета по заказу](src/Examples/getReportOrderDetails.php) +3. [Запрос быстрого отчёта по заказам для сверки](src/Examples/getReportOrder.php) +4. [Запрос отчёта по заказам](src/Examples/getReportGeneral.php) +5. [Запрос отчёта в виде графика](src/Examples/getReportChart.php) + +##### 6. Возврат средств плательщику (Refund) +1. [Возврат средств](src/Examples/paymentRefund.php) +2. [Возврат средств со сплитом (разделением платежа)](src/Examples/paymentRefundMarketplace.php) + +##### 7. Выплаты +1. [Выплаты на банковские карты](src/Examples/payoutCreate.php) + +##### 8. [Безопасные поля (Secure fields)](src/Examples/secureFields.php) +2. [Создание сессии](src/Examples/getSession.php) +3. [Оплата одноразовым токеном](src/Examples/oneTimeTokenPayment.php) + +##### 9. [Страница после оплаты](src/Examples/returnPage.php) + +##### 10. Подключение продавцов (сабмерчантов маркетплейсов) +1. [Подключение продавца-юридического лица (отправка анкеты)](src/Examples/qstCreateOrg.php) +2. [Подключение продавца-ИП (отправка анкеты)](src/Examples/qstCreateIp.php) +3. [Получение статуса анкеты](src/Examples/qstStatus.php) +4. [Печать анкеты](src/Examples/qstPrint.php) +5. [Список анкет](src/Examples/qstList.php) ## Ссылки -- [Докуметация по API](https://dev.payu.ru/ru/documents/apiv4/) -- [Основной сайт PayU Россия](https://payu.ru/) -- Начните знакомство с кодом с этих файлов: [example.php](https://github.com/payuru/php-payu4/blob/main/example.php) и - класса [PaymentInterface.php](https://github.com/payuru/php-payu4/blob/main/src/PaymentInterface.php) -- [Задать вопрос или сообщить о проблеме](https://github.com/payuru/php-payu4/issues/new) +- [НКО «Твои Платежи»](https://YPMN.ru/) +- [Докуметация API](https://ypmn.ru/ru/documentation/) +- [Тестовые банковские карты](https://ypmn.ru/ru/documentation/#tag/testing) +- [Задать вопрос или сообщить о проблеме](https://github.com/yourpayments/php-api-client/issues/new) ------------- -![](https://www.nco-payu.ru/media/images/global/payu@2x.png) - -[PayU.ru](https://PayU.ru/ "Платёжная система для сайтов и не только") +🟢 [«Твои Платежи»](https://YPMN.ru/ "Платёжная система для сайтов, платформ и приложений") -- финтех-составляющая для сайтов, платформ и приложений diff --git a/assets/css/secureFields.css b/assets/css/secureFields.css new file mode 100644 index 0000000..237b232 --- /dev/null +++ b/assets/css/secureFields.css @@ -0,0 +1,146 @@ +body { + min-width: 388px; +} + +.form { + display: none; + justify-content: center; + margin: 25px 12px; +} + +.form form { + width: 100%; + max-width: 500px; + min-width: 350px; +} + +.rowDateAndCVV { + flex-direction: row; +} + +@media only screen and (max-width: 438px) { + .rowDateAndCVV { + flex-direction: column; + } +} + +.form button { + width: 100%; +} + +.secret-field { + display: flex; + align-items: center; +} + +.left-column-input { + /*width: 130px;*/ +} + +.right-column-input { + /*width: 130px;*/ +} + +.btn-primary { + color: #fff; + background-color: #3A9D86; + border-color: #3A9D86; + outline: none !important; + box-shadow: none !important; +} + +.btn-primary:hover, .btn-primary:focus { + color: #000; + background-color: #83E8AB; + border-color: #83E8AB; + outline: none !important; + box-shadow: none !important; +} + +.btn-primary:disabled, .btn-primary[disabled] { + color: #000; + background-color: #aaaaaa; + border-color: #999999; + outline: none !important; + box-shadow: none !important; +} + +.form-control, .input-group-text { + border: 1px solid #3A9D86; +} + +.result, .load { + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + margin: 25px 12px; +} + +.result .icon { + margin-bottom: 15px; +} + +.result .title { + font-size: 17px; + font-weight: 500; + margin-bottom: 8px; +} + +.result .message { + font-size: 14px; + font-weight: 400; + margin-bottom: 25px; + line-height: 1.6em; +} + +.result .message span { + font-size: inherit; +} + +.json { + display: block; + text-align: left; + margin: 5px 0; +} + +.load { + display: flex; +} + +.load svg { + display: block; + width: 200px; + height: 200px; +} + +#submitLoad { + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + background-color: #3A9D86; + border-color: #3A9D86; + outline: none !important; + box-shadow: none !important; + padding: 6px; +} + +#submitLoad svg { + width: 24px; + height: 24px; +} + +.reset { + display: none; +} + +#simple-form { + display: none; +} + +.no-padding-left { + padding-left: 0; +} diff --git a/assets/js/secureFields.js b/assets/js/secureFields.js new file mode 100644 index 0000000..e906024 --- /dev/null +++ b/assets/js/secureFields.js @@ -0,0 +1,452 @@ +/* +Загрузка SDK Secure Fields + */ +secureFieldsJs.addEventListener('load', () => { + console.log('eventListener secureFieldsJs'); + initPaymentProcess(); + initPaymentProcessSimple(); +}) + +document.body.appendChild(secureFieldsJs); + +/* +Процесс получения одноразового токена + */ +let validationSuccess = []; +let eventsToListen = ['change', 'blur']; +let elementsTypesListened = []; + +/* +Настройка Secure Fields, отображение полей Secure Fields, процесс получения одноразового токена + */ +function initPaymentProcess() { + console.log('function initPaymentProcess'); + console.log(merchantCode); + console.log(sessionId); + + /* + Создание объекта Secure Fields. + Первый аргумент (обязательный) - аутентификационные данные. + Второй аргумент (опциональный) - пользовательские стили. + */ + const auth = { + merchantCode: merchantCode, + sessionId: sessionId + }; + + const fonts = [ + { + // src: 'https://fonts.googleapis.com/css?family=Source+Code+Pro' + } + ]; + + const formElements = new PayUSecureFields.Init(auth, { + fonts + }) + + console.log(formElements); + + /* + Добавление плейсхолдеров для полей Secure Fields. + */ + const placeholders = { + cardNumber: '1234 1234 1234 1234', + expDate: 'MM / YY', + cvv: '123', + userAgreement: 'ru' + }; + + /* + Отображение формы после загрузки secure-fields.min.js + */ + document.getElementById('load').style.display = 'none'; + document.getElementById('form').style.display = 'flex'; + + let style = { + base: { + fontSize: '0.85em', + lineHeight: '1.1em' + } + }; + + /* + Отображение полей Secure Fields и подключение слушателей на поля для валидации введенных данных + */ + const cardNumber = formElements.create('cardNumber', { + placeholders + }); + cardNumber.mount('#card-number'); + formValidation(cardNumber, eventsToListen, 'card-number'); + + cardNumber.on('cardInfo', (event) => { + console.log('cardInfoEvent', event) + }); + + const expiry = formElements.create('creditCardExpiry', { + placeholders + }); + expiry.mount('#exp-date'); + formValidation(expiry, eventsToListen, 'exp-date'); + + const cvv = formElements.create('cvv', { + placeholders + }); + cvv.mount('#cvv'); + formValidation(cvv, eventsToListen, 'cvv'); + + const userAgreement = formElements.create('userAgreement', { + style, + placeholders + }); + userAgreement.mount('#user-agreement'); + formValidation(userAgreement, eventsToListen, 'user-agreement'); + + /* + Валидация поля с именем картодержателя + */ + cardHolderValidation(); + + /* + Создание токена при нажатии на кнопку формы + */ + document.getElementById('payment-form').addEventListener('submit', async(event) => { + console.log('submit'); + + /* + svg загрузки вместо кнопки после нажатия + */ + document.getElementById('submitLoad').style.display = 'flex'; + document.getElementById('pay_button').style.display = 'none'; + + event.preventDefault(); + + /* + Имя картодержателя является обязательным + */ + const additionalData = { + holder_name: document.getElementById('cardholder-name').value + }; + + try { + /* + Получение и обработка ответа при создании одноразового токена + */ + console.log('cardNumber'); + + const result = await PayUSecureFields.createToken(cardNumber, {additionalData}); + + console.log('createToken'); + console.log(result); + processResult(result); + } catch (err) { + /* + Вывод об ошибке при наличии + */ + console.log('createTokenError - ' + err.name + ': ' + err.message); + viewResult(false, err.name, [err.message], true); + } + + document.getElementById('submitLoad').style.display = 'none'; + document.getElementById('pay_button').style.display = 'block'; + }) +} + +/* +Процесс обработки результата получения токена +*/ +function processResult(result) { + console.log('function processResult'); + + if (typeof result.errors == 'object' && Object.keys(result.errors).length) { + console.log('createToken errors'); + viewResult(false, 'Tokenization failure', result.errors, true); + return; + } + + if (result.statusCode === 'SUCCESS') { + console.log('createToken success'); + pay(result['token']); + } +} + +/* + +*/ +function pay(token) { + console.log('function pay'); + console.log(token); + + let oneTimeTokenPaymentResult = jsonRequest( + '?function=oneTimeTokenPayment&json=true', + 'post', + 'token=' + encodeURIComponent(token) + '&sessionId=' + encodeURIComponent(sessionId), + 'json', + function(data){ + + if (data['status'] === 'SUCCESS' && data['paymentResult']['type'] === 'redirect') { + viewRedirect('Redirect to bank\'s page', true); + window.location.href = data['paymentResult']['url']; + return; + } + + if (data['status'] === 'SUCCESS') { + viewResult(true, 'Payment successful', [], true); + return; + } + + viewResult(false, 'Payment failed', [data['message']], true); + } + ); + + if (oneTimeTokenPaymentResult === false) { + viewResult(false, 'Payment failed', ['Payment json request failure'], true); + } +} + +function formValidation(object, listeners, containerId) { + console.log('function formValidation'); + + let elementType = object['elementType']; + + elementsTypesListened.push(elementType); + + validationSuccess[elementType] = false; + + if (elementType === 'userAgreement') { + validationSuccess[elementType] = true; + } + + let container = document.getElementById(containerId); + let validationContainer = document.getElementById(containerId + '-validation'); + + listeners.forEach( function(listener) { + + object.on(listener, (event) => { + console.log('listener'); + console.log(object); + console.log(event); + + if (event['statusCode'] === 'SUCCESS' && event['empty'] === false) { + console.log('SUCCESS'); + + container.classList.remove( 'is-invalid'); + validationContainer.innerHTML = ''; + + validationSuccess[elementType] = true; + } else { + console.log('ERROR'); + + console.log(event['errors']); + + container.classList.add('is-invalid'); + validationContainer.innerHTML = ''; + + validationSuccess[elementType] = false; + + for (const key in event['errors']) { + let error = event['errors'][key]; + validationContainer.innerHTML += error + '
'; + console.log(error); + } + + if (event['empty'] === true && elementType === 'cvv') { + validationContainer.innerHTML += 'CVV is mandatory field
'; + } + } + + changeButtonAbility(); + }) + + }); +} + +function cardHolderValidation() { + console.log('function cardHolderValidation'); + + let cardHolderEventsToListen= eventsToListen; + cardHolderEventsToListen.push('input'); + + let elementType = 'cardHolder'; + + elementsTypesListened.push(elementType); + + validationSuccess[elementType] = false; + + let container = document.getElementById('cardholder-name'); + let validationContainer = document.getElementById('cardholder-name-validation'); + + cardHolderEventsToListen.forEach( function(listener) { + console.log('listener'); + console.log('cardHolder'); + console.log(listener); + + let cardHolderInput = document.getElementById('cardholder-name'); + + cardHolderInput.addEventListener(listener, () => { + + if (cardHolderInput.value !== '') { + console.log('SUCCESS'); + + container.classList.remove( 'is-invalid'); + validationContainer.innerHTML = ''; + + validationSuccess[elementType] = true; + } else { + console.log('ERROR'); + + container.classList.add('is-invalid'); + validationContainer.innerHTML = 'Holder\'s name is mandatory field'; + + validationSuccess[elementType] = false; + } + + changeButtonAbility(); + }) + }) +} + +function changeButtonAbility() { + console.log('function changeButtonAbility'); + + console.log(elementsTypesListened); + + for (const key in elementsTypesListened) { + let elementType = elementsTypesListened[key]; + + console.log(elementType); + console.log(validationSuccess[elementType]); + + if (validationSuccess[elementType] === false) { + console.log('disable button'); + document.getElementById('pay_button').disabled = true; + return; + } + + } + + console.log('enable button'); + document.getElementById('pay_button').disabled = false; +} + +function jsonRequest(url, method, requestData, responseType, successCallback) { + console.log('function jsonRequest: ' + url); + + let xhr = new XMLHttpRequest(); + xhr.open(method, url, false); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + + let jsonRequestResult = false; + + xhr.onload = function() { + let status = xhr.status; + + if (status === 200) { + console.log('jsonRequest success'); + console.log(xhr.response); + + successCallback(JSON.parse(xhr.response)); + jsonRequestResult = true; + } else { + console.log('jsonRequest error'); + console.log('Error ' + xhr.status + ': ' + xhr.statusText); + + viewResult(false, 'Request Error', [], true); + } + }; + + xhr.ontimeout = (e) => { + console.log('jsonRequest timeout error'); + viewResult(false, 'Request Error', [], true); + }; + + console.log(requestData); + + xhr.send(requestData); + + console.log(jsonRequestResult); + + return jsonRequestResult; +} + +/* +Отображение результата получения одноразового токена и оплаты +*/ + +const resultBlock = '\n' + + '
\n' + + '
\n' + + '
\n' + + '

\n' + + '

\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
'; + +const successIcon = '\n' + + ' \n' + + ' \n' + + ' \n' + + ' '; + +const failureIcon = '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' fail\n' + + ' \n' + + ' \n' + + ' '; + +function viewResult(success, title, messages, hideForm = false) { + console.log('function viewResult'); + console.log(success); + console.log(title); + console.log(messages); + + document.getElementById('bootstrap-tab-pane').innerHTML += resultBlock; + + if (success === true) { + document.getElementById('icon').innerHTML = successIcon; + } else { + document.getElementById('icon').innerHTML = failureIcon; + document.getElementById('reset').style.display = 'block'; + } + + document.getElementById('title').innerHTML = title; + + let div = document.getElementById('message'); + for (const key in messages) { + div.innerHTML += '' + messages[key] + ''; + } + + if (hideForm === true) { + document.getElementById('form').style.display = 'none'; + } + document.getElementById('load').style.display = 'none'; + document.getElementById('result').style.display = 'flex'; +} + +function viewRedirect(title, hideForm = false) { + console.log('function viewRedirect'); + console.log(title); + + document.getElementById('bootstrap-tab-pane').innerHTML += resultBlock; + + document.getElementById('title').innerHTML = title; + + if (hideForm === true) { + document.getElementById('form').style.display = 'none'; + } + document.getElementById('load').style.display = 'none'; + document.getElementById('result').style.display = 'flex'; +} diff --git a/assets/js/secureFieldsSimple.js b/assets/js/secureFieldsSimple.js new file mode 100644 index 0000000..b679597 --- /dev/null +++ b/assets/js/secureFieldsSimple.js @@ -0,0 +1,258 @@ +/* +Настройка Secure Fields, отображение полей Secure Fields, процесс получения одноразового токена + */ +function initPaymentProcessSimple() { + console.log('function initPaymentProcessSimple'); + console.log(merchantCode); + console.log(sessionId); + + /* + Создание объекта Secure Fields. + Первый аргумент (обязательный) - аутентификационные данные. + Второй аргумент (опциональный) - пользовательские стили. + */ + const auth = { + merchantCode: merchantCode, + sessionId: sessionId + }; + + const fonts = [ + { + // src: 'https://fonts.googleapis.com/css?family=Source+Code+Pro' + } + ]; + + const formElements = new PayUSecureFields.Init(auth, { + fonts + }) + + console.log(formElements); + + /* + Добавление плейсхолдеров для полей Secure Fields. + */ + const placeholders = { + cardNumber: '1234 1234 1234 1234', + expDate: 'MM / YY', + cvv: '123', + userAgreement: 'ru' + }; + + /* + Отображение формы после загрузки secure-fields.min.js + */ + document.getElementById('simple-load').style.display = 'none'; + document.getElementById('simple-form').style.display = 'block'; + + /* + Отображение полей Secure Fields + */ + const cardNumber = formElements.create('cardNumber', { + placeholders + }); + cardNumber.mount('#simple-card-number'); + + const expiry = formElements.create('creditCardExpiry', { + placeholders + }); + expiry.mount('#simple-exp-date'); + + const cvv = formElements.create('cvv', { + placeholders + }); + cvv.mount('#simple-cvv'); + + const userAgreement = formElements.create('userAgreement', { + placeholders + }); + userAgreement.mount('#simple-user-agreement'); + + /* + Создание токена при нажатии на кнопку формы + */ + document.getElementById('simple-payment-form').addEventListener('submit', async(event) => { + console.log('submit'); + + event.preventDefault(); + + /* + Имя картодержателя является обязательным + */ + const additionalData = { + holder_name: document.getElementById('simple-cardholder-name').value + }; + + try { + /* + Получение и обработка ответа при создании одноразового токена + */ + console.log('cardNumber'); + + const result = await PayUSecureFields.createToken(cardNumber, {additionalData}); + + console.log('createToken'); + console.log(result); + + processResultSimple(result); + } catch (err) { + /* + Вывод об ошибке при наличии + */ + console.log('createTokenError - ' + err.name + ': ' + err.message); + + viewResultSimple(false, err.name, [err.message], true); + } + }) +} + +/* +Обработка ответа при создании токена + */ +function processResultSimple(result) { + console.log('function processResultSimple'); + + /* + Вывод ошибок создания токена + */ + if (typeof result.errors == 'object' && Object.keys(result.errors).length) { + console.log('createToken errors'); + + viewResultSimple(false, 'Tokenization failure', result.errors, true); + return; + } + + /* + В случае успешного создания токена переходим к процессу оплаты + */ + if (result.statusCode === 'SUCCESS') { + console.log('createToken success'); + + paySimple(result['token']); + } +} + +/* +Процесс оплаты + */ +function paySimple(token) { + console.log('function paySimple'); + console.log(token); + + let oneTimeTokenPaymentResult = jsonRequest( + '?function=oneTimeTokenPayment&json=true', + 'post', + 'token=' + encodeURIComponent(token) + '&sessionId=' + encodeURIComponent(sessionId), + 'json', + function(data){ + + /* + Если для оплаты необходимо пройти проверку 3-D Secure, то происходит редирект на соответствующую страницу + */ + if (data['status'] === 'SUCCESS' && data['paymentResult']['type'] === 'redirect') { + viewRedirectSimple('Redirect to bank\'s page', true); + window.location.href = data['paymentResult']['url']; + return; + } + + /* + В случае успешной оплаты сообщаем об этом пользователю + */ + if (data['status'] === 'SUCCESS') { + viewResultSimple(true, 'Payment successful', [], true); + return; + } + + /* + В случае ошибок по результатам оплаты выводим их + */ + viewResultSimple(false, 'Payment failed', [data['message']], true); + } + ); + + /* + Если запрос не прошел, выводим информацию + */ + if (oneTimeTokenPaymentResult === false) { + viewResultSimple(false, 'Payment failed', ['Payment json request failure'], true); + } +} + +/* +Блоки результатов + */ +const resultBlockSimple = '\n' + + '
\n' + + '
\n' + + '
\n' + + '

\n' + + '

\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '
'; + +const successIconSimple = '\n' + + ' \n' + + ' \n' + + ' \n' + + ' '; + +const failureIconSimple = '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + ' fail\n' + + ' \n' + + ' \n' + + ' '; + +/* +Вывод результата + */ +function viewResultSimple(success, title, messages, hideForm = false) { + document.getElementById('simple-tab-pane').innerHTML += resultBlockSimple; + + if (success === true) { + document.getElementById('simple-icon').innerHTML = successIconSimple; + } else { + document.getElementById('simple-icon').innerHTML = failureIconSimple; + document.getElementById('simple-reset').style.display = 'block'; + } + + document.getElementById('simple-title').innerHTML = title; + + let div = document.getElementById('simple-message'); + for (const key in messages) { + div.innerHTML += '' + messages[key] + ''; + } + + if (hideForm === true) { + document.getElementById('simple-form').style.display = 'none'; + } + document.getElementById('simple-load').style.display = 'none'; + document.getElementById('simple-result').style.display = 'flex'; +} + +/* +Вывод информации о редиректе на проверку 3-D Secure + */ +function viewRedirectSimple(title, hideForm = false) { + document.getElementById('simple-tab-pane').innerHTML += resultBlockSimple; + + document.getElementById('simple-title').innerHTML = title; + + if (hideForm === true) { + document.getElementById('simple-form').style.display = 'none'; + } + document.getElementById('simple-load').style.display = 'none'; + document.getElementById('simple-result').style.display = 'flex'; +} diff --git a/composer.json b/composer.json index 10ecf7c..dbe6829 100644 --- a/composer.json +++ b/composer.json @@ -1,10 +1,10 @@ { - "name": "payuru/php-payu4", - "description": "PayU - powerful payment gateway PHP integration", + "name": "yourpayments/php-api-client", + "description": "Your Payments - powerful payment gateway PHP integration", "type": "package", "keywords": ["payments", "processing"], "require": { - "php": ">=8.1.0", + "php": ">=7.4.0", "ext-json": "*", "ext-curl": "*", "ext-mbstring": "*" @@ -12,15 +12,15 @@ "license": "mit", "authors": [ { - "name": "PayU Team", - "email": "help@payu.ru", - "homepage": "https://dev.payu.ru/ru/documents/apiv4/" + "name": "YPMN Team", + "email": "itsupport@ypmn.ru", + "homepage": "https://dev.ypmn.ru/ru/documents/apiv4/" } ], "minimum-stability": "dev", "autoload": { "psr-4": { - "payuru\\phpPayu4\\": "src/" + "Ypmn\\": "src/" } } } diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8d91d2c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,20 @@ +version: '4' +name: php-api-client + +services: + nginx: + image: nginx:latest + volumes: + - ./docker/nginx/.conf:/etc/nginx/conf.d/default.conf + - ./:/var/www/php-api-client + - ./docker/nginx/logs:/var/log/nginx + ports: + - "8080:80" + depends_on: + - php + container_name: php-api-client-nginx-container + php: + build: ./docker/php + volumes: + - ./:/var/www/php-api-client + container_name: php-api-client-php-container \ No newline at end of file diff --git a/docker/nginx/.conf b/docker/nginx/.conf new file mode 100644 index 0000000..508c4b8 --- /dev/null +++ b/docker/nginx/.conf @@ -0,0 +1,17 @@ +server { + server_name php-api-client; + root /var/www/php-api-client; + + index index.php; + + location ~ \.php$ { + fastcgi_pass php:9000; + + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + } + + error_log /var/log/nginx/project_error.log; + access_log /var/log/nginx/project_access.log; +} \ No newline at end of file diff --git a/docker/nginx/logs/.gitignore b/docker/nginx/logs/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/docker/nginx/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile new file mode 100644 index 0000000..521728d --- /dev/null +++ b/docker/php/Dockerfile @@ -0,0 +1,5 @@ +FROM php:8.3-fpm + +RUN apt-get update && apt-get install -y curl libcurl4-openssl-dev libmcrypt-dev libonig-dev + +RUN docker-php-ext-install curl mbstring \ No newline at end of file diff --git a/example.php b/example.php index b9aeee5..0bb7cb0 100644 --- a/example.php +++ b/example.php @@ -1,6 +1,6 @@ 'Заказ №' . $merchantPaymentReference, - 'sku' => $merchantPaymentReference, - 'unitPrice' => 1.42, - 'quantity' => 2, - ]); - - // Опишем Биллинговую (платёжную) информацию - $billing = new Billing; - // Установим Код страны - $billing->setCountryCode('RU'); - // Установим Имя Плательщика - $billing->setFirstName('Иван'); - // Установим Фамилия Плательщика - $billing->setLastName('Петров'); - // Установим Email Плательщика - $billing->setEmail('test1@payu.ru'); - // Установим Телефон Плательщика - $billing->setPhone('+7-800-555-35-35'); - // Установим Город - $billing->setCity('Москва'); - - // Создадим клиентское подключение - $client = new Client; - // Установим биллинг - $client->setBilling($billing); - - // Создадим платёж - $payment = new Payment; - // Установим позиции - $payment->addProduct($orderAsProduct); - // Установим валюту - $payment->setCurrency('RUB'); - // Создадим и установим авторизацию по типу платежа - $payment->setAuthorization(new Authorization('CCVISAMC',true)); - // Установим номер заказа (должен быть уникальным в вашей системе) - $payment->setMerchantPaymentReference($merchantPaymentReference); - // Установим адрес перенаправления пользователя после оплаты - $payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); - // Установим клиентское подключение - $payment->setClient($client); - - // Создадим HTTP-запрос к API - $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) - $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) - $apiRequest->setSandboxMode(); - // Отправим запрос - $responseData = $apiRequest->sendAuthRequest($payment, $merchant); - // Преобразуем ответ из JSON в массив - try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты - echo Std::drawPayuButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // .. или сделаем редирект на форму оплаты (опционально) - // Std::redirect($responseData["paymentResult"]['url']); - } catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
- Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
-
-
' . $exception->getMessage() . '
', - 'type' => 'danger', - ]); - - throw new PaymentException('Платёжный метод временно недоступен'); - } + case 'start': + include './src/Examples/'.$_GET['function'] . '.php'; break; + case 'simpleGetPaymentLink': case 'getPaymentLink': - // Оплата по ссылке PayU - // Представим, что нам надо оплатить пару позиций: Синий Мяч и Жёлтый Круг - - // Опишем первую позицию - $product1 = new Product; - // Установим Наименование (название товара или услуги) - $product1->setName('Синий Квадрат'); - // Установим Артикул - $product1->setSku('ball-05'); - // Установим Стоимость за единицу - $product1->setUnitPrice('500'); - // Установим Количество - $product1->setQuantity(1); - // Установим НДС - $product1->setVat(20); - - //Опишем вторую позицию с помощью сокращённого синтаксиса: - $product2 = new Product([ - 'name' => 'Оранжевый Круг', - 'sku' => 'toy-15', - 'unitPrice' => 160000, - 'quantity' => 3, - 'vat' => 0, - ]); - - // Опишем Биллинговую (платёжную) информацию - $billing = new Billing; - // Установим Код страны - $billing->setCountryCode('RU'); - // Установим Город - $billing->setCity('Москва'); - // Установим Регион - $billing->setState('Центральный регион'); - // Установим Адрес Плательщика (первая строка) - $billing->setAddressLine1('Улица Старый Арбат, дом 10'); - // Установим Адрес Плательщика (вторая строка) - $billing->setAddressLine1('Офис PayU'); - // Установим Почтовый Индекс Плательщика - $billing->setZipCode('121000'); - // Установим Имя Плательщика - $billing->setFirstName('Иван'); - // Установим Фамилия Плательщика - $billing->setLastName('Петров'); - // Установим Телефон Плательщика - $billing->setPhone('+79670660742'); - // Установим Email Плательщика - $billing->setEmail('test1@payu.ru'); - - // (необязательно) Опишем Доствку и принимающее лицо - $delivery = new Delivery; - // Установим документ, подтверждающий право приёма доставки - $delivery->setIdentityDocument( - new IdentityDocument('123456', 'PERSONALID') - ); - // Установим Код страны - $delivery->setCountryCode('RU'); - // Установим Город - $delivery->setCity('Москва'); - // Установим Регион - $delivery->setState('Центральный регион'); - // Установим Адрес Лица, принимающего заказ (первая строка) - $delivery->setAddressLine1('Улица Старый Арбат, дом 10'); - // Установим Адрес Лица, принимающего заказ (вторая строка) - $delivery->setAddressLine1('Офис PayU'); - // Установим Почтовый Индекс Лица, принимающего заказ - $delivery->setZipCode('121000'); - // Установим Имя Лица, принимающего заказ - $delivery->setFirstName('Мария'); - // Установим Фамилия Лица, принимающего заказ - $delivery->setLastName('Петрова'); - // Установим Телефон Лица, принимающего заказ - $delivery->setPhone('+79670660743'); - // Установим Email Лица, принимающего заказ - $delivery->setEmail('test2@payu.ru'); - // Установим Название Компании, в которой можно оставить заказ - $delivery->setCompanyName('ООО "Вектор"'); - - // Создадим клиентское подключение - $client = new Client; - // Установим биллинг - $client->setBilling($billing); - // Установим доставку - $client->setDelivery($delivery); - // Установим IP (автоматически) - $client->setCurrentClientIp(); - // И Установим время (автоматически) - $client->setCurrentClientTime(); - - // Создадим платёж - $payment = new Payment; - // Установим позиции - $payment->addProduct($product1); - $payment->addProduct($product2); - // Установим валюту - $payment->setCurrency('RUB'); - // Создадим и установим авторизацию по типу платежа - $payment->setAuthorization(new Authorization('CCVISAMC',true)); - // Установим номер заказа (должен быть уникальным в вашей системе) - $payment->setMerchantPaymentReference('primer_nomer__' . time()); - // Установим адрес перенаправления пользователя после оплаты - $payment->setReturnUrl('http://127.0.0.1:8080/?function=returnPage'); - // Установим клиентское подключение - $payment->setClient($client); - - // Создадим HTTP-запрос к API - $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) - $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) - $apiRequest->setSandboxMode(); - // Отправим запрос - $responseData = $apiRequest->sendAuthRequest($payment, $merchant); - // Преобразуем ответ из JSON в массив - try { - $responseData = json_decode((string) $responseData["response"], true); - - // Нарисуем кнопку оплаты - echo Std::drawPayuButton([ - 'url' => $responseData["paymentResult"]['url'], - 'sum' => $payment->sumProductsAmount(), - ]); - - // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: - // echo Std::redirect($responseData["paymentResult"]['url']); - } catch (Exception $exception) { - //TODO: обработка исключения - echo Std::alert([ - 'text' => ' - Извините, платёжный метод временно недоступен.
- Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
-
-
' . $exception->getMessage() . '
', - 'type' => 'danger', - ]); - - throw new PaymentException('Платёжный метод временно недоступен'); - } - break; - + case 'getPaymentLinkMarketplace': + case 'getToken': + case 'paymentByToken': case 'paymentCapture': - // Запрос на списание денег - // В зависимости от настройки мерчанта, PayU может списывать денежные средства автоматически, - // Либо с помощью дополнительного запроса, описанного ниже. - - // Создадим такой запрос: - $capture = (new Capture); - - // Номер платежа PayU (возвращается в ответ на запрос на авторизацию в JSON Response) - $capture->setPayuPaymentReference(2297597); - - // Cумма исходной операции на авторизацию - $capture->setOriginalAmount(5300); - // Cумма фактического списания - $capture->setAmount(3700); - // Валюта - $capture->setCurrency('RUB'); - - // Создадим HTTP-запрос к API - $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) - $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) - $apiRequest->setSandboxMode(); - // Отправим запрос к API - $responseData = $apiRequest->sendCaptureRequest($capture, $merchant); - - break; case 'paymentGetStatus': - // Получить номер транзакции в PayU - - // Номер заказа - $merchantPaymentReference = 'primer_nomer__184'; - // Создадим HTTP-запрос к API - $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) - $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) - $apiRequest->setSandboxMode(); - // Отправим запрос к API - $responseData = $apiRequest->sendStatusRequest($merchantPaymentReference); - - break; + case 'payoutCreate': case 'paymentWebhook': - //сформировать вебхук - break; case 'paymentRefund': - // Инициировать возврат средств - - // Создадим запрос - $refund = (new Refund); - - // Установим номер платежа PayU - возвращается в ответ на запрос на авторизацию платежа в JSON Response - // См. пример с запросом Payment выше - $refund->setPayuPaymentReference(2297597); - // Cумма исходной операции на авторизацию - $refund->setOriginalAmount(3700); - // Cумма фактического списания - $refund->setAmount(3700); - // Установим валюту - $refund->setCurrency('RUB'); - // Создадим HTTP-запрос к API - $apiRequest = new ApiRequest($merchant); - // Включить режим отладки (удалите в рабочей программе!) - $apiRequest->setDebugMode(); - // Переключиться на тестовый сервер (удалите в рабочей программе!) - $apiRequest->setSandboxMode(); - // Отправим запрос к API - $responseData = $apiRequest->sendRefundRequest($refund, $merchant); - break; - + case 'paymentRefundMarketplace': + case 'getSession': + case 'oneTimeTokenPayment': case 'returnPage': - // Страница после оплаты: - echo '

Благодарим за оплату

Чек выслан вам на почту.'; - echo '
$_GET: ' . print_r($_GET, true) . '
'; - echo '
$_POST: ' . print_r($_POST, true) . '
'; + case 'secureFields': + case 'getReportGeneral': + case 'getReportChart': + case 'getReportOrder': + case 'getReportOrderDetails': + case 'getFasterPayment': + case 'getBindingFasterPayment': + case 'paymentByFasterBinding': + case 'qstCreateOrg': + case 'qstCreateIp': + case 'qstStatus': + case 'qstPrint': + case 'SOMGetPaymentLink': + case 'qstList': + require './src/Examples/start.php'; + @include './src/Examples/'.$_GET['function'] . '__prepend.php'; + require './src/Examples/'.$_GET['function'] . '.php'; break; default: @@ -342,5 +73,3 @@ echo $e->getHtmlMessage(); } } - -include 'example_template.html'; diff --git a/example_footer.html b/example_footer.html new file mode 100644 index 0000000..be78c5b --- /dev/null +++ b/example_footer.html @@ -0,0 +1,12 @@ + + + +
+
+
+ ❤ Желаем приятной интеграции. С искренним уважением, НКО «Твои Платежи» +
+
+
+ + diff --git a/example_header.php b/example_header.php new file mode 100644 index 0000000..4a27f70 --- /dev/null +++ b/example_header.php @@ -0,0 +1,95 @@ + + + + + Твои Платежи | Сервис для работы с электронными платежами + + + + + + + + + + + + + + + +
+
+ + +
+ + $examples[ $_GET['function'] ]['about'] . ' +
+
+ Адрес этого примера на Github + '.( !$examples[ $_GET['function'] ]['docLink'] ? '' : 'Документация API' ).' + ', + ]); + } diff --git a/example_list.php b/example_list.php new file mode 100644 index 0000000..58b635e --- /dev/null +++ b/example_list.php @@ -0,0 +1,169 @@ + [ + 'name' => 'Начало работы', + 'about' => ' + Первый шаг интеграции с YPMN API – это получение кода мерчанта и секретного ключа после подключения (спросите у Вашего менеджера). +
+
Они нужны для отправки всех запросов к API. +
+
На стороне клиента они используются для создания объекта Merchant (смотрите файл с примером). + + ', + 'docLink' => '', + 'link' => '', + ], + 'simpleGetPaymentLink' => [ + 'name' => 'Самая простая кнопка оплаты', + 'about' => 'В этом примере показана самая простая реализация. С минимальным набором полей без детализации, просто оплата заказа c определённой суммой.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1authorize/post', + 'link' => '', + ], + 'getPaymentLink' => [ + 'name' => 'Подробный платёж', + 'about' => 'Это пример платежа с максимальным набором полей.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1authorize/post', + 'link' => '', + ], + 'getFasterPayment' => [ + 'name' => 'Оплата через СБП', + 'about' => 'Пример платежа через систему быстрых платежей', + 'docLink' => 'https://ypmn.ru/ru/documentation/', + 'link' => '', + ], + 'getBindingFasterPayment' => [ + 'name' => 'Создание подписки СБП', + 'about' => 'В этом примере отправляется запрос на создание подписки СБП с одновременной оплатой', + 'docLink' => 'https://ypmn.ru/ru/documentation/', + 'link' => '', + ], + 'paymentByFasterBinding' => [ + 'name' => 'Оплата по подписке СБП', + 'about' => 'Это пример демонстрирует оплату через СБП по средством ранее созщданной подписки', + 'docLink' => 'https://ypmn.ru/ru/documentation/', + 'link' => '', + ], + 'getPaymentLinkMarketplace' => [ + 'name' => 'Платёж со сплитом', + 'about' => 'Это пример платежа со сплитом (разделением оплаты на несколько плательщиков).', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-split-api', + 'link' => '', + ], + 'getToken' => [ + 'name' => 'Создание токена', + 'about' => 'Приложение передаёт номер успешно оплаченного заказа в YPMN API, и получает в ответ платёжный токен.

Это называется "Токенизация карты" (чтобы запомнить карту клиента и не вводить повторно.

Очень полезная функция для подписок и регулярных платежей.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/token-api/paths/~1v4~1token/post', + 'link' => '', + ], + 'paymentByToken' => [ + 'name' => 'Оплата токеном', + 'about' => 'Оплата с помощью токена (теперь не нужно повторно вводить данные банковской карты)', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1authorize/post', + 'link' => '', + ], + 'paymentCapture' => [ + 'name' => 'Списание средств', + 'about' => 'Списание ранее заблокированной на счету суммы. Не обязательно, если у Вас настроена оплата в 1 шаг.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1capture/post', + 'link' => '', + ], + 'paymentRefund' => [ + 'name' => 'Возврат средств', + 'about' => 'Запрос на полный или частичный возврат средств.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1refund/post', + 'link' => '', + ], + 'paymentRefundMarketplace' => [ + 'name' => 'Возврат средств со сплитом', + 'about' => 'Запрос на полный или частичный возврат средств с разделением на несколько получателей.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1refund/post', + 'link' => '', + ], + 'paymentGetStatus' => [ + 'name' => 'Проверка статуса платежа', + 'about' => 'Запрос к YPMN API о состоянии платежа.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1status~1%7BmerchantPaymentReference%7D/get', + 'link' => '', + ], + 'payoutCreate' => [ + 'name' => 'Создание выплаты', + 'about' => 'Запрос к YPMN для совершения выплаты на карту (для компаний, сертифицированных по PCI-DSS). У вас должно быть достаточно средств на специальном счету для выплат.

Тестовая карта (для выплат на тестовом контуре): 4149605380309302', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payouts-api', + 'link' => '', + ], + 'getSession' => [ + 'name' => 'Создание сессии', + 'about' => 'Создание уникальной сессии YPMN', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/sessions/paths/~1v4~1payments~1sessions/post', + 'link' => '', + ], + 'oneTimeTokenPayment' => [ + 'name' => 'Оплата одноразовым токеном', + 'about' => 'Оплата одноразовым токеном', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api/paths/~1v4~1payments~1authorize/post', + 'link' => '', + ], + 'returnPage' => [ + 'name' => 'Страница после оплаты', + 'about' => 'Это пример страницы, на которую плательщик возвращается после совершения платежа.', + 'docLink' => '', + 'link' => '', + ], + 'secureFields' => [ + 'name' => 'Безопасные поля (Secure fields)', + 'about' => 'Это пример формы оплаты с использованием Secure Fields.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/secure-fields', + 'link' => '', + ], + 'getReportGeneral' => [ + 'name' => 'Запрос отчёта в формате JSON', + 'about' => 'Это пример получения отчета в формате JSON.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/reports/paths/~1v4~1reports~1general/get', + 'link' => '', + ], + 'getReportChart' => [ + 'name' => 'Запрос отчёта в виде графика', + 'about' => 'Это пример получения отчета в виде графика.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/reports/paths/~1v4~1reports~1chart/get', + 'link' => '', + ], + 'SOMGetPaymentLink' => [ + 'name' => 'Оплата зарубежными картами', + 'about' => 'В этом примере показана простая реализация оплаты заказа зарубежной картой.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/payment-api', + 'link' => '', + ], + 'qstCreateOrg' => [ + 'name' => 'Подключение продавца-организации (отправка анкеты)', + 'about' => 'В этом примере показана реализация отправки анкеты подключаемого продавца-организации на проверку в YPMN.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/qst-api/paths/~1v4~1qst~1create/post', + 'link' => '', + ], + 'qstCreateIp' => [ + 'name' => 'Подключение продавца-ИП (отправка анкеты)', + 'about' => 'В этом примере показана реализация отправки анкеты подключаемого продавца-ИП на проверку в YPMN.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/qst-api/paths/~1v4~1qst~1create/post', + 'link' => '', + ], + 'qstStatus' => [ + 'name' => 'Получение статуса анкеты', + 'about' => 'В этом примере показано получение статуса анкеты по её ID.

ID анкеты возвращается при отправке анкеты на проверку.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/qst-api/paths/~1v4~1qst~1status~1%7Bid%7D/get', + 'link' => '', + ], + 'qstPrint' => [ + 'name' => 'Печать анкеты', + 'about' => 'В этом примере показано получение заполненной pdf версии анкеты по её ID.

ID анкеты возвращается при отправке анкеты на проверку.

Распечатать можно только одобренную анкету - в статусе approved.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/qst-api/paths/~1v4~1qst~1print~1%7Bid%7D/get', + 'link' => '', + ], + 'qstList' => [ + 'name' => 'Список анкет', + 'about' => 'В этом примере показано получение списка анкет.', + 'docLink' => 'https://ypmn.ru/ru/documentation/#tag/qst-api/paths/~1v4~1qst~1list/get', + 'link' => '', + ], +]; diff --git a/example_template.html b/example_template.html deleted file mode 100644 index a5e2ff2..0000000 --- a/example_template.html +++ /dev/null @@ -1,11 +0,0 @@ -
-
-
    -
  1. simpleGetPaymentLink -- самый простой платёж
  2. -
  3. getPaymentLink -- платёж со всеми полями
  4. -
  5. paymentCapture -- списание средств
  6. - -
  7. paymentRefund -- запрос на возврат
  8. -
  9. paymentGetStatus -- проверка статуса платежа
  10. -
  11. returnPage -- страница после оплаты
  12. -
diff --git a/index.php b/index.php index 25425dc..4d14fcf 100644 --- a/index.php +++ b/index.php @@ -3,10 +3,11 @@ ini_set('display_startup_errors', '1'); error_reporting(E_ALL); -// Эта фукнция подключает клас +// Эта фукнция подключает классы spl_autoload_register(function ($className) { $className = explode('\\', $className); $className = end($className); + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . $className . '.php'; if (is_readable($filename)) { @@ -14,4 +15,14 @@ } }); -require 'example.php'; +// обработка json запросов +$jsonMode = $_REQUEST['json'] ?? false; + +if ($jsonMode) { + require 'example.php'; +} else { + include 'example_list.php'; + include 'example_header.php'; + require 'example.php'; + include 'example_footer.html'; +} diff --git a/screenshot.jpg b/screenshot.jpg index 56e6ddb..b779b05 100644 Binary files a/screenshot.jpg and b/screenshot.jpg differ diff --git a/src/AirlineInfoInterface.php b/src/AirlineInfoInterface.php index fcf06d8..b093cd5 100644 --- a/src/AirlineInfoInterface.php +++ b/src/AirlineInfoInterface.php @@ -1,13 +1,13 @@ -setValue($value); + + $this->setCurrency($currency); + } + + /** @inheritdoc */ + public function getValue(): float + { + return $this->value; + } + + /** @inheritdoc + */ + public function setValue(float $value): self + { + if ($value < 1) { + throw new PaymentException('Слишком маленькая выплата'); + } + + $this->value = $value; + + return $this; + } + + /** @inheritdoc */ + public function getCurrency(): string + { + return $this->currency; + } + + /** @inheritdoc */ + public function setCurrency(string $currency): self + { + $this->currency = $currency; + + return $this; + } + + /** @inheritdoc */ + public function arraySerialize() : array + { + return [ + "currency" => $this->getCurrency() ?? 'RUB', + "value" => round( $this->getValue(), 2), + ]; + } +} diff --git a/src/AmountInterface.php b/src/AmountInterface.php new file mode 100644 index 0000000..3689e59 --- /dev/null +++ b/src/AmountInterface.php @@ -0,0 +1,37 @@ +merchant = $merchant; } + /** @inheritdoc */ + public function getHost() : string + { + return $this->host; + } + + /** @inheritdoc */ + public function setHost(string $host) : self + { + if (filter_var($host, FILTER_VALIDATE_URL)) { + $this->host = $host; + + return $this; + } else { + throw new PaymentException('Некорректный URL для отправки запросов'); + } + } + + /** @deprecated старая версия */ + public function sendGetReportRequest(?string $startDate = null, ?string $endDate = null, ?array $orderStatus = null): string + { + //проверить даты + if ($startDate !== null) { + if (($startDate = strtotime($startDate)) === false) { + throw new \Exception('Неверная дата для формирования запроса'); + } else { + $startDate = date('Y-m-d', $startDate); + } + } else { + $startDate = date('Y-m-d', strtotime('today')); + } + + if ($endDate !== null) { + if (($endDate = strtotime($endDate)) === false) { + throw new \Exception('Неверная дата для формирования запроса'); + } else { + $endDate = date('Y-m-d', $endDate); + } + } else { + $endDate = date('Y-m-d', strtotime('tomorrow')); + } + + $merchant = $this->merchant->getCode(); + $timeStamp = time(); + $parameters = compact('merchant', 'startDate', 'endDate', 'timeStamp'); + + //сформировать URL + $url = $this->getHost() + . $this::REPORTS_ORDERS_API + . '?' + . http_build_query($parameters) + . '&signature=' + . $this->reportsSign($parameters); + + + if ($this->getDebugMode()) { + echo Std::alert([ + 'text' => $url, + ]); + } + + // отправить запрос + $curl = curl_init(); + $requestHttpVerb = 'GET'; + + $date = (new DateTime())->format(DateTimeInterface::ATOM); + $setopt_array = [ + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => $requestHttpVerb, + CURLOPT_HTTPHEADER => [ + 'Accept: application/json', + 'Content-Type: application/json', + 'X-Header-Date: ' . $date, +// 'X-Header-Merchant: ' . $this->merchant->getCode() + ] + ]; + + curl_setopt_array($curl, $setopt_array); + + $response = curl_exec($curl); + $err = curl_error($curl); + curl_close($curl); + + if ($this->getDebugMode()) { + $this->echoDebugMessage('Ответ от ' . $this->getHost() . ':'); + $this->echoDebugMessage(Std::json_fix_cyr($response)); + + if ($err) { + $this->echoDebugMessage('Ошибка:'); + $this->echoDebugMessage($err); + } + } + + // вернуть результат + return Std::json_fix_cyr($response); + } + + private function buildReportsSourceString($parameters) + { + $hashString = ''; + + foreach ($parameters as $currentData) { +// if (is_array($currentData)) { +// //TODO +// $currentData = ''; +// } + + if (strlen($currentData) > 0) { + $hashString .= strlen($currentData); + $hashString .= $currentData; + } + } + + return $hashString; + } + + /** + * Расчет подписи для API v3 + * @param $parameters + * @return string + */ + private function reportsSign($parameters) + { + $sourceString = $this->buildReportsSourceString($parameters); + + return hash_hmac('MD5', $sourceString, $this->merchant->getSecret()); + } + /** * Отправка GET-запроса * @param string $api адрес API (URI) - * @return array ответ сервера PayU + * @return array ответ сервера Ypmn + * @throws PaymentException */ private function sendGetRequest(string $api): array { $curl = curl_init(); $date = (new DateTime())->format(DateTimeInterface::ATOM); - $urlToPostTo = ($this->getSandboxMode() ? self::SANDBOX_HOST : self::HOST) . $api; $requestHttpVerb = 'GET'; $setopt_array = [ - CURLOPT_URL => $urlToPostTo, + CURLOPT_URL => $this->getHost() . $api, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, @@ -61,42 +220,102 @@ private function sendGetRequest(string $api): array 'X-Header-Signature:' . $this->getSignature( $this->merchant, $date, - $urlToPostTo, + $this->getHost() . $api, $requestHttpVerb, md5(''), ) ] ]; + $headers = []; + + if ($this->getDebugShowResponseHeaders()) { + $this->addCurlOptHeaderFunction($setopt_array, $headers); + } + curl_setopt_array($curl, $setopt_array); $response = curl_exec($curl); $err = curl_error($curl); curl_close($curl); + if (true === $this->getDebugMode()) { + $this->echoDebugMessage('GET-Запрос к серверу Ypmn:'); + $this->echoDebugMessage($this->getHost() . $api); + $this->echoDebugMessage('Ответ от сервера Ypmn:'); + if ($this->getJsonDebugResponse()) { + $this->echoDebugMessage(json_encode(json_decode($response), JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + } else { + $this->echoDebugMessage($response); + } + + if ($this->getDebugShowResponseHeaders()) { + $this->echoDebugMessage('Заголовки ответа от сервера Ypmn:'); + $this->echoDebugMessage(implode("\n", $headers)); + } + + if (mb_strlen($err) > 0) { + $this->echoDebugMessage('Ошибка'); + echo '
Вы можете отправить запрос на поддержку на itsupport@ypmn.ru'; + echo '
Последняя версия примеров на Github'; + echo '
Оставить заявку на улучшение'; + echo '
Контакты'; + } else { + $cpanel_url = 'https://' . ($this->getSandboxMode() ? 'sandbox' : 'secure' ). '.ypmn.ru/cpanel/'; + + if ($this->getSandboxMode()) { + echo Std::alert([ + 'type' => 'warning', + 'text' => ' + Внимание! + У вас настроен тестовый режим. +
Все запросы уходят на тестовый сервер sandbox.ypmn.ru +
+
+ Когда закончите тестирование, закомментируйте или удалите строки кода: + + $apiRequest->setDebugMode(); // вывод отладки +
$apiRequest->setSandboxMode(); // тестовый сервер +
+ ', + ]); + } + } + } + return ['response' => $response, 'error' => $err]; } /** * Отправка POST-запроса - * @param JsonSerializable $data запрос + * @param string|JsonSerializable $data запрос * @param string $api адрес API (URI) - * @return array ответ сервера PayU + * @return array ответ сервера Ypmn * @throws PaymentException */ - private function sendPostRequest(JsonSerializable $data, string $api): array + public function sendPostRequest($data, string $api): array { - $encodedJsonData = $data->jsonSerialize(); + if ($data instanceof JsonSerializable) { + $encodedJsonData = $data->jsonSerialize(); + } elseif (is_string($data)) { + if (json_decode($data) !== null) { + $encodedJsonData = $data; + } else { + throw new PaymentException('Incorrect request body type'); + } + } else { + throw new PaymentException('Incorrect request body JSON'); + } + $encodedJsonDataHash = md5($encodedJsonData); $curl = curl_init(); $date = (new DateTime())->format(DateTimeInterface::ATOM); - $urlToPostTo = ($this->getSandboxMode() ? self::SANDBOX_HOST : self::HOST) . $api; $requestHttpVerb = 'POST'; - curl_setopt_array($curl, [ - CURLOPT_URL => $urlToPostTo, + $setOptArray = [ + CURLOPT_URL => $this->getHost() . $api, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, @@ -112,38 +331,64 @@ private function sendPostRequest(JsonSerializable $data, string $api): array 'X-Header-Signature:' . $this->getSignature( $this->merchant, $date, - $urlToPostTo, + $this->getHost() . $api, $requestHttpVerb, $encodedJsonDataHash ) ] - ]); + ]; + + $headers = []; + + if ($this->getDebugShowResponseHeaders()) { + $this->addCurlOptHeaderFunction($setOptArray, $headers); + } + + curl_setopt_array($curl, $setOptArray); $response = curl_exec($curl); $err = curl_error($curl); curl_close($curl); if (true === $this->getDebugMode()) { - $this->echoDebugMessage('Запрос к серверу PayU:'); + $this->echoDebugMessage('POST-Запрос к серверу Ypmn:'); $this->echoDebugMessage($encodedJsonData); - $this->echoDebugMessage('Ответ от сервера PayU:'); - $this->echoDebugMessage(json_encode(json_decode($response), JSON_PRETTY_PRINT)); + $this->echoDebugMessage('Ответ от сервера Ypmn:'); + $this->echoDebugMessage(json_encode(json_decode($response), JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE)); + + if ($this->getDebugShowResponseHeaders()) { + $this->echoDebugMessage('Заголовки ответа от сервера Ypmn:'); + $this->echoDebugMessage(implode("\n", $headers)); + } if (mb_strlen($err) > 0) { $this->echoDebugMessage('Ошибка'); $this->echoDebugMessage($encodedJsonData); - echo '
Следуйте документации'; - echo '
Вы можете отправить запрос на поддержку на help@payu.ru'; - echo '
Последняя версия примеров на Github'; - echo '
Оставить заявку на улучшение'; - echo '
Контакты'; + + echo '
Вы можете отправить запрос на поддержку на itsupport@ypmn.ru'; + echo '
Последняя версия примеров на Github'; + echo '
Оставить заявку на улучшение'; + echo '
Контакты'; } else { + $cpanel_url = 'https://' . ($this->getSandboxMode() ? 'sandbox' : 'secure' ). '.ypmn.ru/cpanel/'; + if ($this->getSandboxMode()) { - echo '
Внимание! У вас включен тестовый режим (режим песочницы). Все запросы уходят на sandbox.payu.ru'; + echo Std::alert([ + 'type' => 'warning', + 'text' => ' + Внимание! + У вас настроен тестовый режим. +
Все запросы уходят на тестовый сервер sandbox.ypmn.ru +
+
+ Когда закончите тестирование, закомментируйте или удалите строки кода: + + $apiRequest->setDebugMode(); // вывод отладки +
$apiRequest->setSandboxMode(); // тестовый сервер +
+ ', + ]); } - $cpanel_url = 'https://' . ($this->getSandboxMode() ? 'sandbox' : 'secure' ). '.payu.ru/cpanel/'; - echo '
Отслеживайте состояние транзакции по адресу ' . $cpanel_url . ''; - echo '

'; } } @@ -151,9 +396,21 @@ private function sendPostRequest(JsonSerializable $data, string $api): array throw new PaymentException($err); } + if ($response == null || strlen($response) === 0) { + throw new PaymentException('Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.'); + } + return ['response' => $response, 'error' => $err]; } + /** @inheritdoc + * @throws PaymentException + */ + public function sendSessionRequest(SessionRequest $sessionRequest): array + { + return $this->sendPostRequest($sessionRequest, self::SESSION_API); + } + /** @inheritdoc */ public function sendAuthRequest(PaymentInterface $payment): array { @@ -175,7 +432,77 @@ public function sendRefundRequest(RefundInterface $refund): array /** @inheritdoc */ public function sendStatusRequest(string $merchantPaymentReference): array { - return $this->sendGetRequest(self::STATUS_API . '/' . $merchantPaymentReference); + $responseData = $this->sendGetRequest(self::STATUS_API . '/' . $merchantPaymentReference); + + if (mb_strlen($responseData['error']) > 0) { + throw new PaymentException($responseData['error']); + } + + if ($responseData['response'] == null || strlen($responseData['response']) === 0) { + throw new PaymentException('Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.'); + } + + return $responseData; + } + + /** @inheritdoc */ + public function sendTokenCreationRequest(PaymentReference $payuPaymentReference): array + { + return $this->sendPostRequest($payuPaymentReference, self::TOKEN_API); + } + + /** @inheritdoc */ + public function sendTokenPaymentRequest(MerchantToken $tokenHash): array + { + return $this->sendPostRequest($tokenHash, self::AUTHORIZE_API); + } + + /** @inheritDoc */ + public function sendPayoutCreateRequest(PayoutInterface $payout) + { + return $this->sendPostRequest($payout, self::PAYOUT_CREATE_API); + } + + /** @inheritdoc */ + public function sendReportChartRequest(array $params): array + { + $this->setJsonDebugResponse(false); + return $this->sendGetRequest(self::REPORT_CHART_API . '/?' . http_build_query($params)); + } + + /** @inheritdoc */ + public function sendReportChartUpdateRequest(array $params): array + { + $getParams = [ + 'startDate' => $_GET['startDate'], + 'endDate' => $_GET['endDate'], + 'status' => $_GET['status'], + 'type' => $_GET['type'], + 'periodLength' => $_GET['periodLength'], + 'jsonForUpdate' => 'true' + ]; + + $params = array_merge($getParams, $params); + + return $this->sendGetRequest(self::REPORT_CHART_API . '/?' . http_build_query($params)); + } + + /** @inheritdoc */ + public function sendReportGeneralRequest(array $params): array + { + return $this->sendGetRequest(self::REPORT_GENERAL_API . '/?' . http_build_query($params)); + } + + /** @inheritdoc */ + public function sendReportOrderRequest(array $params): array + { + return $this->sendGetRequest(self::REPORT_ORDERS_API_V4 . '/?' . http_build_query($params)); + } + + /** @inheritdoc */ + public function sendReportOrderDetailsRequest(array $params): array + { + return $this->sendGetRequest(self::REPORT_ORDER_DETAILS_API . '/?' . http_build_query($params)); } /** @@ -189,6 +516,14 @@ public function sendStatusRequest(string $merchantPaymentReference): array */ private function getSignature(MerchantInterface $merchant, string $date, string $url, string $httpMethod, string $bodyHash): string { + if (strlen($merchant->getCode()) < 2) { + throw new PaymentException('YPMN-001: No Merchant Code'); + } + + if (strlen($merchant->getCode()) < 2) { + throw new PaymentException('YPMN-001: No Merchant Code'); + } + $urlParts = parse_url($url); $urlHashableParts = $httpMethod . $urlParts['path']; $this->echoDebugMessage($urlParts); @@ -210,7 +545,30 @@ public function getSandboxMode(): bool /** @inheritdoc */ public function setSandboxMode(bool $sandboxModeIsOn = true): self { + if ($sandboxModeIsOn) { + $this->setLocalMode(false); + } $this->sandboxModeIsOn = $sandboxModeIsOn; + $this->host = self::SANDBOX_HOST; + + return $this; + } + + /** @inheritdoc */ + public function getLocalMode(): bool + { + return $this->localModeIsOn; + } + + /** @inheritdoc */ + public function setLocalMode(bool $localModeIsOn = true): self + { + if ($localModeIsOn) { + $this->setSandboxMode(false); + } + $this->localModeIsOn = $localModeIsOn; + $this->host = self::LOCAL_HOST; + return $this; } @@ -227,6 +585,19 @@ public function setDebugMode(bool $debugModeIsOn = true): self return $this; } + /** @inheritdoc */ + public function setJsonDebugResponse(bool $jsonDebugResponse): self + { + $this->jsonDebugResponse = $jsonDebugResponse; + return $this; + } + + /** @inheritdoc */ + public function getJsonDebugResponse(): bool + { + return $this->jsonDebugResponse; + } + /** * Вывод отладочного сообщения * @param $mixedInput @@ -248,4 +619,66 @@ class="w-100 d-block" >'.print_r($mixedInput, true).''; } } + + /** @inheritdoc */ + public function sendPodeliRegistrationMerchantRequest(PodeliMerchant $merchant): array + { + return $this->sendPostRequest($merchant, self::PODELI_MERCHANT_REGISTRATION_API); + } + + /** @inheritdoc */ + public function sendQstCreateRequest(QstInterface $qst): array + { + return $this->sendPostRequest($qst, self::QST_CREATE_API); + } + + /** @inheritdoc */ + public function sendQstStatusRequest(int $qstId): array + { + return $this->sendGetRequest(self::QST_STATUS_API . '/' . $qstId); + } + + /** @inheritdoc */ + public function sendQstPrintRequest(int $qstId): array + { + return $this->sendGetRequest(self::QST_PRINT_API . '/' . $qstId); + } + + /** @inheritdoc */ + public function sendQstListRequest(): array + { + return $this->sendGetRequest(self::QST_LIST_API); + } + + /** @inheritdoc */ + public function getDebugShowResponseHeaders(): bool + { + return $this->debugShowResponseHeaders; + } + + /** @inheritdoc */ + public function setDebugShowResponseHeaders(bool $debugShowResponseHeaders = true): self + { + $this->debugShowResponseHeaders = $debugShowResponseHeaders; + return $this; + } + + /** + * @param array $curlOptArr + * @param array $headers + * @return void + */ + private function addCurlOptHeaderFunction(array &$curlOptArr, array &$headers): void + { + $curlOptArr += [ + CURLOPT_HEADERFUNCTION => static function($curl, $header) use (&$headers) + { + if (strlen(trim($header)) > 0) { + $headers[] = trim($header); + } + + return strlen($header); + } + ]; + } } diff --git a/src/ApiRequestInterface.php b/src/ApiRequestInterface.php index 8f74a30..eaed9f5 100644 --- a/src/ApiRequestInterface.php +++ b/src/ApiRequestInterface.php @@ -1,6 +1,6 @@ setPaymentMethod($paymentMethodType); - $this->setUsePaymentPage($isUsed); + $this->setUsePaymentPage($isPaymentPageUsed); } /** @@ -41,12 +54,24 @@ public function __constructor(string $paymentMethodType, bool $isUsed) { public function setPaymentMethod(string $paymentMethodType) : self { switch ($paymentMethodType) { - case 'CCVISAMC': + case self::TYPE_CCVISAMC: $this->paymentMethod = self::TYPE_CCVISAMC; break; - case 'FASTER_PAYMENTS': + case self::TYPE_FASTER_PAYMENTS: $this->paymentMethod = self::TYPE_FASTER_PAYMENTS; break; + case self::TYPE_SOM: + $this->paymentMethod = self::TYPE_SOM; + break; + case self::TYPE_SBERPAY: + $this->paymentMethod = self::TYPE_SBERPAY; + break; + case self::TYPE_TPAY: + $this->paymentMethod = self::TYPE_TPAY; + break; + case self::TYPE_ALFAPAY: + $this->paymentMethod = self::TYPE_ALFAPAY; + break; default: throw new PaymentException('Неверный тип оплаты в авторизации'); } @@ -83,13 +108,13 @@ public function getPaymentMethod(): string } /** @inheritDoc */ - public function getCardDetails(): CardDetailsInterface + public function getCardDetails(): ?CardDetailsInterface { return $this->cardDetails; } /** @inheritDoc */ - public function setCardDetails(CardDetailsInterface $cardDetails): self + public function setCardDetails(?CardDetailsInterface $cardDetails): self { if (is_null($this->merchantToken) && $this->usePaymentPage === false) { $this->cardDetails = $cardDetails; @@ -106,6 +131,15 @@ public function getMerchantToken(): ?MerchantTokenInterface return $this->merchantToken; } + public function setOneTimeUseToken(?OneTimeUseToken $oneTimeUseToken): self + { + $this->setCardDetails(null); + $this->setUsePaymentPage(false); + $this->oneTimeUseToken = $oneTimeUseToken; + + return $this; + } + /** * @inheritDoc * @throws PaymentException @@ -121,6 +155,20 @@ public function setMerchantToken(?MerchantTokenInterface $merchantToken): self } } + /** @inheritDoc */ + public function setPaymentPageOptions(PaymentPageOptionsInterface $paymentPageOptions): self + { + $this->paymentPageOptions = $paymentPageOptions; + + return $this; + } + + /** @inheritDoc */ + public function getPaymentPageOptions(): PaymentPageOptionsInterface + { + return $this->paymentPageOptions; + } + /** * @return array */ @@ -135,10 +183,18 @@ public function arraySerialize(): array $resultArray['cardDetails'] = $this->cardDetails->toArray(); } + if (!is_null($this->oneTimeUseToken)) { + $resultArray['oneTimeUseToken'] = $this->oneTimeUseToken->toArray(); + } + if (!is_null($this->merchantToken)) { $resultArray['merchantToken'] = $this->merchantToken->toArray(); } + if (!is_null($this->paymentPageOptions) && $this->paymentPageOptions->getOrderTimeout() > 0) { + $resultArray['paymentPageOptions'] = $this->paymentPageOptions->toArray(); + } + return $resultArray; } } diff --git a/src/AuthorizationInterface.php b/src/AuthorizationInterface.php index e46ca71..0b4eaca 100644 --- a/src/AuthorizationInterface.php +++ b/src/AuthorizationInterface.php @@ -1,58 +1,67 @@ -identityDocument ?? null; } + + /** @inheritdoc */ + public function getType(): string + { + return $this->type; + } + + /** @inheritdoc */ + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } } diff --git a/src/BillingInterface.php b/src/BillingInterface.php index 52baa6f..56e2a55 100644 --- a/src/BillingInterface.php +++ b/src/BillingInterface.php @@ -1,175 +1,190 @@ -payuPaymentReference = $paymentIdString; @@ -35,7 +35,7 @@ public function setPayuPaymentReference(string $paymentIdString): CaptureInterfa } /** @inheritDoc */ - public function getPayuPaymentReference(): string + public function getYpmnPaymentReference(): string { return $this->payuPaymentReference; } @@ -110,7 +110,7 @@ public function jsonSerialize() { //TODO: проверка необходимых параметров $requestData = [ - 'payuPaymentReference' => $this->getPayuPaymentReference(), + 'payuPaymentReference' => $this->getYpmnPaymentReference(), 'originalAmount' => $this->getOriginalAmount(), 'amount' => $this->getAmount(), 'currency' => $this->getCurrency() diff --git a/src/CaptureApiRequest.php b/src/CaptureApiRequest.php index 16508a0..bcf2412 100644 --- a/src/CaptureApiRequest.php +++ b/src/CaptureApiRequest.php @@ -1,6 +1,6 @@ jsonSerialize(); @@ -54,7 +53,6 @@ public function sendRequest(CaptureInterface $capture, MerchantInterface $mercha return ['response' => $response, 'error' => $err]; } -// private function getSignature($merchantCode, $secret, $date, $url, $httpMethod, $bodyHash): string private function getSignature(MerchantInterface $merchant, $date, $url, $httpMethod, $bodyHash): string { $urlParts = parse_url($url); @@ -63,4 +61,4 @@ private function getSignature(MerchantInterface $merchant, $date, $url, $httpMet return hash_hmac('sha256', $hashableString, $merchant->getSecret()); } -} \ No newline at end of file +} diff --git a/src/CaptureInterface.php b/src/CaptureInterface.php index 27e8540..577c199 100644 --- a/src/CaptureInterface.php +++ b/src/CaptureInterface.php @@ -1,6 +1,6 @@ number = $number; + return $this; } @@ -94,13 +96,13 @@ public function setExpiryYear(int $expiryYear): self } /** @inheritDoc */ - public function getCvv(): int + public function getCvv(): string { return $this->cvv; } /** @inheritDoc */ - public function setCvv(int $cvv): self + public function setCvv(string $cvv): self { $this->cvv = $cvv; return $this; diff --git a/src/CardDetailsInterface.php b/src/CardDetailsInterface.php index 53e7eb7..47fa411 100644 --- a/src/CardDetailsInterface.php +++ b/src/CardDetailsInterface.php @@ -1,165 +1,152 @@ - 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 200.42, + 'quantity' => 1, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); +// Установим Телефон Плательщика +$billing->setPhone('+7-800-555-35-35'); +// Установим Город +$billing->setCity('Москва'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($orderAsProduct); +// Установим валюту +$payment->setCurrency('RUB'); +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization(new Authorization('SOM',false)); +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference($merchantPaymentReference); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + if ($responseData) { + // Выведем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // .. или сделаем редирект на форму оплаты (опционально) + // Std::redirect($responseData["paymentResult"]['url']); + } +} catch (Exception $exception) { + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/getBindingFasterPayment.php b/src/Examples/getBindingFasterPayment.php new file mode 100644 index 0000000..e48641f --- /dev/null +++ b/src/Examples/getBindingFasterPayment.php @@ -0,0 +1,156 @@ +setName('Круассаны Цезарь'); // Установим Наименование товара или услуги +$product1->setSku('toy-01'); // Установим Артикул +$product1->setVat(20); // Установим НДС +$product1->setUnitPrice(10); // Установим Стоимость за единицу +$product1->setQuantity(1); // Установим Количество + +/* Опишем вторую позицию с помощью сокращённого синтаксиса */ +$product2 = new Product([ + 'name' => 'Ассорти рулетиков Нежность', + 'sku' => 'toy-02', + 'unitPrice' => 8, + 'quantity' => 3, + 'vat' => 10, +]); + +/* Опишем третью позицию с помощью JSON */ +$product3 = new Product( + json_decode( + '{ + "name": "Ассорти мини-десертов", + "sku": "toy-03", + "unitPrice": 12, + "quantity": 2, + "vat": 0 + }', + true + ) +); + +/* Опишем Биллинговую (платёжную) информацию */ +$billing = new Billing; +$billing->setCountryCode('RU'); // Установим Код страны +$billing->setCity('Москва'); // Установим Город +$billing->setState('Центральный регион'); // Установим Регион +$billing->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Плательщика (первая строка) +$billing->setAddressLine2('Офис Ypmn'); // Установим Адрес Плательщика (вторая строка) +$billing->setZipCode('121000'); // Установим Почтовый Индекс Плательщика +$billing->setFirstName('Иван'); // Установим Имя Плательщика +$billing->setLastName('Петров'); // Установим Фамилия Плательщика +$billing->setPhone('9670660742'); // Установим Телефон Плательщика +$billing->setEmail('develop@ypmn.ru'); // Установим Email Плательщика + +/* Опишем Доствку и принимающее лицо (необязательно) */ +$delivery = new Delivery; +// Установим документ, подтверждающий право приёма доставки +$delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') +); +$delivery->setCountryCode('RU'); // Установим Код страны +$delivery->setCity('Москва'); // Установим Город +$delivery->setState('Центральный регион'); // Установим Регион +$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Лица, принимающего заказ (первая строка) +$delivery->setAddressLine2('Офис Ypmn'); // Установим Адрес Лица, принимающего заказ (вторая строка) +$delivery->setZipCode('121000'); // Установим Почтовый Индекс Лица, принимающего заказ +$delivery->setFirstName('Мария'); // Установим Имя Лица, принимающего заказ +$delivery->setLastName('Петрова'); // Установим Фамилия Лица, принимающего заказ +$delivery->setPhone('89670660743'); // Установим Телефон Лица, принимающего заказ +$delivery->setEmail('develop@ypmn.ru'); // Установим Email Лица, принимающего заказ +$delivery->setCompanyName('ООО "Вектор"'); // Установим Название Компании, в которой можно оставить заказ + +/* Создадим клиентское подключение */ +$client = new Client; +$client->setBilling($billing); // Установим биллинг +$client->setDelivery($delivery); // Установим доставку +$client->setCurrentClientIp(); // Установим IP (автоматически) +$client->setCurrentClientTime(); // И Установим время (автоматически) + +/* Создадим платёж */ +$payment = new Payment; +$payment->addProduct($product1); // Установим товарную позицию 1 +$payment->addProduct($product2); // Установим товарную позицию 2 +$payment->addProduct($product3); // Установим товарную позицию 3 +$payment->setCurrency('RUB'); // Установим валюту + +/* Создадим авторизацию по типу платежа */ +$authorization = new Authorization('FASTER_PAYMENTS',false); + +/* Запрашиваем подписку */ +$storedCredentials = new StoredCredentials(); +$storedCredentials->setConsentType("recurring"); +$storedCredentials->setSubscriptionPurpose("Ежедневная доставка \"К Завтраку\""); + +// Назначим авторизацию для нашего платежа // +$payment->setAuthorization($authorization); +// Запросим подписку +$payment->setStoredCredentials($storedCredentials); +// Установим номер заказа (должен быть уникальным в вашей системе) // +$payment->setMerchantPaymentReference('primer_nomer__' . time()); +// Установим адрес перенаправления пользователя после оплаты // +$payment->setReturnUrl('http://' . $_SERVER['SERVER_NAME'] . '/php-api-client/?function=returnPage'); +$payment->setClient($client); // Установим клиентское подключение + +/* Создадим HTTP-запрос к API */ +$apiRequest = new ApiRequest($merchant); + +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +// Отправим запрос // +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); + +/* Преобразуем ответ из JSON в массив */ +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'] ?? "", + 'sum' => $payment->sumProductsAmount() ?? 0, + ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/getFasterPayment.php b/src/Examples/getFasterPayment.php new file mode 100644 index 0000000..b18b928 --- /dev/null +++ b/src/Examples/getFasterPayment.php @@ -0,0 +1,148 @@ +setName('Круассаны Цезарь'); // Установим Наименование товара или услуги +$product1->setSku('toy-01'); // Установим Артикул +$product1->setVat(20); // Установим НДС +$product1->setUnitPrice(10); // Установим Стоимость за единицу +$product1->setQuantity(1); // Установим Количество + +/* Опишем вторую позицию с помощью сокращённого синтаксиса */ +$product2 = new Product([ + 'name' => 'Ассорти рулетиков Нежность', + 'sku' => 'toy-02', + 'unitPrice' => 8, + 'quantity' => 3, + 'vat' => 10, +]); + +/* Опишем третью позицию с помощью JSON */ +$product3 = new Product( + json_decode( + '{ + "name": "Ассорти мини-десертов", + "sku": "toy-03", + "unitPrice": 12, + "quantity": 2, + "vat": 0 + }', + true + ) +); + +/* Опишем Биллинговую (платёжную) информацию */ +$billing = new Billing; +$billing->setCountryCode('RU'); // Установим Код страны +$billing->setCity('Москва'); // Установим Город +$billing->setState('Центральный регион'); // Установим Регион +$billing->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Плательщика (первая строка) +$billing->setAddressLine2('Офис Ypmn'); // Установим Адрес Плательщика (вторая строка) +$billing->setZipCode('121000'); // Установим Почтовый Индекс Плательщика +$billing->setFirstName('Иван'); // Установим Имя Плательщика +$billing->setLastName('Петров'); // Установим Фамилия Плательщика +$billing->setPhone('9670660742'); // Установим Телефон Плательщика +$billing->setEmail('develop@ypmn.ru'); // Установим Email Плательщика + +/* Опишем Доствку и принимающее лицо (необязательно) */ +$delivery = new Delivery; +// Установим документ, подтверждающий право приёма доставки +$delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') +); +$delivery->setCountryCode('RU'); // Установим Код страны +$delivery->setCity('Москва'); // Установим Город +$delivery->setState('Центральный регион'); // Установим Регион +$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Лица, принимающего заказ (первая строка) +$delivery->setAddressLine2('Офис Ypmn'); // Установим Адрес Лица, принимающего заказ (вторая строка) +$delivery->setZipCode('121000'); // Установим Почтовый Индекс Лица, принимающего заказ +$delivery->setFirstName('Мария'); // Установим Имя Лица, принимающего заказ +$delivery->setLastName('Петрова'); // Установим Фамилия Лица, принимающего заказ +$delivery->setPhone('89670660743'); // Установим Телефон Лица, принимающего заказ +$delivery->setEmail('develop@ypmn.ru'); // Установим Email Лица, принимающего заказ +$delivery->setCompanyName('ООО "Вектор"'); // Установим Название Компании, в которой можно оставить заказ + +/* Создадим клиентское подключение */ +$client = new Client; +$client->setBilling($billing); // Установим биллинг +$client->setDelivery($delivery); // Установим доставку +$client->setCurrentClientIp(); // Установим IP (автоматически) +$client->setCurrentClientTime(); // И Установим время (автоматически) + +/* Создадим платёж */ +$payment = new Payment; +$payment->addProduct($product1); // Установим товарную позицию 1 +$payment->addProduct($product2); // Установим товарную позицию 2 +$payment->addProduct($product3); // Установим товарную позицию 3 +$payment->setCurrency('RUB'); // Установим валюту + +/* Создадим авторизацию по типу платежа */ +$authorization = new Authorization('FASTER_PAYMENTS',false); + +// Назначим авторизацию для нашего платежа // +$payment->setAuthorization($authorization); +// Установим номер заказа (должен быть уникальным в вашей системе) // +$payment->setMerchantPaymentReference('primer_nomer__' . time()); +// Установим адрес перенаправления пользователя после оплаты // +$payment->setReturnUrl('http://' . $_SERVER['SERVER_NAME'] . '/php-api-client/?function=returnPage'); +$payment->setClient($client); // Установим клиентское подключение + +/* Создадим HTTP-запрос к API */ +$apiRequest = new ApiRequest($merchant); + +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +// Отправим запрос // +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); + +/* Преобразуем ответ из JSON в массив */ +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'] ?? "", + 'sum' => $payment->sumProductsAmount() ?? 0, + ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/getPaymentLink.php b/src/Examples/getPaymentLink.php new file mode 100644 index 0000000..80f02ac --- /dev/null +++ b/src/Examples/getPaymentLink.php @@ -0,0 +1,162 @@ +setName('Синий Мяч'); +// Установим Артикул +$product1->setSku('toy-05'); +// Установим Стоимость за единицу +$product1->setUnitPrice(10); +// Установим Количество +$product1->setQuantity(1); +// Установим НДС +$product1->setVat(20); + +//Опишем вторую позицию с помощью сокращённого синтаксиса: +$product2 = new Product([ + 'name' => 'Жёлтый Круг', + 'sku' => 'toy-15', + 'unitPrice' => 2, + 'quantity' => 3, + 'vat' => 0, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Город +$billing->setCity('Москва'); +// Установим Регион +$billing->setState('Центральный регион'); +// Установим Адрес Плательщика (первая строка) +$billing->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Плательщика (вторая строка) +$billing->setAddressLine2('Офис Ypmn'); +// Установим Почтовый Индекс Плательщика +$billing->setZipCode('121000'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Телефон Плательщика +$billing->setPhone('+79670660742'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); + +// (необязательно) Опишем Доствку и принимающее лицо +$delivery = new Delivery; +// Установим документ, подтверждающий право приёма доставки +$delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') +); +// Установим Код страны +$delivery->setCountryCode('RU'); +// Установим Город +$delivery->setCity('Москва'); +// Установим Регион +$delivery->setState('Центральный регион'); +// Установим Адрес Лица, принимающего заказ (первая строка) +$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Лица, принимающего заказ (вторая строка) +$delivery->setAddressLine2('Офис Ypmn'); +// Установим Почтовый Индекс Лица, принимающего заказ +$delivery->setZipCode('121000'); +// Установим Имя Лица, принимающего заказ +$delivery->setFirstName('Мария'); +// Установим Фамилия Лица, принимающего заказ +$delivery->setLastName('Петрова'); +// Установим Телефон Лица, принимающего заказ +$delivery->setPhone('+79670660743'); +// Установим Email Лица, принимающего заказ +$delivery->setEmail('test2@ypmn.ru'); +// Установим Название Компании, в которой можно оставить заказ +$delivery->setCompanyName('ООО "Вектор"'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); +// Установим доставку +$client->setDelivery($delivery); +// Установим IP (автоматически) +$client->setCurrentClientIp(); +// И Установим время (автоматически) +$client->setCurrentClientTime(); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($product1); +$payment->addProduct($product2); +// Установим валюту +$payment->setCurrency('RUB'); + +// Создадим авторизацию по типу платежа +$authorization = new Authorization('CCVISAMC',true); +// Можно установить лимит времени для оплаты заказа (в секундах) +$authorization->setPaymentPageOptions(new PaymentPageOptions(600)); +// Назначим авторизацию для нашего платежа +$payment->setAuthorization($authorization); + +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference('primer_nomer__' . time()); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('http://' . $_SERVER['SERVER_NAME'] . '/php-api-client/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/getPaymentLinkMarketplace.php b/src/Examples/getPaymentLinkMarketplace.php new file mode 100644 index 0000000..6e7ed7b --- /dev/null +++ b/src/Examples/getPaymentLinkMarketplace.php @@ -0,0 +1,163 @@ +setName('Синий Квадрат'); +// Установим Артикул +$product1->setSku('ball-05'); +// Установим Стоимость за единицу +$product1->setUnitPrice(500); +// Установим Количество +$product1->setQuantity(1); +// Установим НДС +$product1->setVat(20); +// Установим Код Мерчанта (для маркетплейса) +$product1->setMarketplaceSubmerchantByCode('SUBMERCHANT_1'); + +//Опишем вторую позицию с помощью сокращённого синтаксиса: +$product2 = new Product([ + 'name' => 'Оранжевый Круг', + 'sku' => 'toy-15', + 'unitPrice' => 160000, + 'quantity' => 3, + 'vat' => 0, + 'merchantCode' => 'SUBMERCHANT_2', +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Город +$billing->setCity('Москва'); +// Установим Регион +$billing->setState('Центральный регион'); +// Установим Адрес Плательщика (первая строка) +$billing->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Плательщика (вторая строка) +$billing->setAddressLine1('Офис Ypmn'); +// Установим Почтовый Индекс Плательщика +$billing->setZipCode('121000'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Телефон Плательщика +$billing->setPhone('+79670660742'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); + +// (необязательно) Опишем Доствку и принимающее лицо +$delivery = new Delivery; +// Установим документ, подтверждающий право приёма доставки +$delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') +); +// Установим Код страны +$delivery->setCountryCode('RU'); +// Установим Город +$delivery->setCity('Москва'); +// Установим Регион +$delivery->setState('Центральный регион'); +// Установим Адрес Лица, принимающего заказ (первая строка) +$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); +// Установим Адрес Лица, принимающего заказ (вторая строка) +$delivery->setAddressLine1('Офис Ypmn'); +// Установим Почтовый Индекс Лица, принимающего заказ +$delivery->setZipCode('121000'); +// Установим Имя Лица, принимающего заказ +$delivery->setFirstName('Мария'); +// Установим Фамилия Лица, принимающего заказ +$delivery->setLastName('Петрова'); +// Установим Телефон Лица, принимающего заказ +$delivery->setPhone('+79670660743'); +// Установим Email Лица, принимающего заказ +$delivery->setEmail('test2@ypmn.ru'); +// Установим Название Компании, в которой можно оставить заказ +$delivery->setCompanyName('ООО "Вектор"'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); +// Установим доставку +$client->setDelivery($delivery); +// Установим IP (автоматически) +$client->setCurrentClientIp(); +// И Установим время (автоматически) +$client->setCurrentClientTime(); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($product1); +$payment->addProduct($product2); +// Установим валюту +$payment->setCurrency('RUB'); +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization(new Authorization('CCVISAMC',true)); +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference('primer_nomer__' . time()); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/getReportChart.php b/src/Examples/getReportChart.php new file mode 100644 index 0000000..e01611f --- /dev/null +++ b/src/Examples/getReportChart.php @@ -0,0 +1,31 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); + +// Подготовим диапазон дат для отчета +$endDate = (new DateTime('now'))->format("Y-m-d"); + +$startDate = (new DateTime($endDate)) + ->modify('-14 day') + ->format("Y-m-d"); + +// Отправим запрос +$responseData = $apiRequest->sendReportChartRequest([ + 'startDate' => $startDate, + 'endDate' => $endDate, + 'periodLength' => 'day' +]); \ No newline at end of file diff --git a/src/Examples/getReportGeneral.php b/src/Examples/getReportGeneral.php new file mode 100644 index 0000000..0d59de9 --- /dev/null +++ b/src/Examples/getReportGeneral.php @@ -0,0 +1,31 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); + +// Подготовим диапазон дат для отчета +$endDate = (new DateTime('now'))->format("Y-m-d"); + +$startDate = (new DateTime($endDate)) + ->modify('-14 day') + ->format("Y-m-d"); + +// Отправим запрос +$responseData = $apiRequest->sendReportGeneralRequest([ + 'startDate' => $startDate, + 'endDate' => $endDate, + 'periodLength' => 'day' +]); diff --git a/src/Examples/getReportOrder.php b/src/Examples/getReportOrder.php new file mode 100644 index 0000000..ad47577 --- /dev/null +++ b/src/Examples/getReportOrder.php @@ -0,0 +1,37 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); + +// Подготовим диапазон дат для отчета +$endDate = (new DateTime('now'))->format('c'); + +$startDate = (new DateTime($endDate)) + ->modify('-14 day') + ->format('c'); + +$data = [ + 'startDate' => $startDate, + 'endDate' => $endDate, + 'byConfirmation' => 'YES', + 'statuses' => [ + 'pending' => 'YES', + 'authorized' => 'YES' + ] +]; + +// Отправим запрос +$responseData = $apiRequest->sendReportOrderRequest($data); diff --git a/src/Examples/getReportOrderDetails.php b/src/Examples/getReportOrderDetails.php new file mode 100644 index 0000000..dbbdbcb --- /dev/null +++ b/src/Examples/getReportOrderDetails.php @@ -0,0 +1,24 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); + +$data = [ + 'merchantPaymentReference' => '2297597', +]; + +// Отправим запрос +$responseData = $apiRequest->sendReportOrderDetailsRequest($data); diff --git a/src/Examples/getSession.php b/src/Examples/getSession.php new file mode 100644 index 0000000..e51b5bf --- /dev/null +++ b/src/Examples/getSession.php @@ -0,0 +1,23 @@ +setSandboxMode(); +$apiRequest->setDebugMode(); + +try { + $session = $apiRequest->sendSessionRequest($sessionRequest); + +} catch (\Ypmn\PaymentException $e) { +} diff --git a/src/Examples/getToken.php b/src/Examples/getToken.php new file mode 100644 index 0000000..efac48b --- /dev/null +++ b/src/Examples/getToken.php @@ -0,0 +1,53 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$reference = (isset($_GET['reference']) ? $_GET['reference'] : '2450767'); +$ypmnPaymentReference = new PaymentReference($reference); +$responseData = $apiRequest->sendTokenCreationRequest($ypmnPaymentReference); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + if (isset($responseData['token'])) { + echo Std::alert([ + 'type' => 'success', + 'text' => ' + Карта успешно токенизирована (токен получен). +
+
Вот он: ' . $responseData['token'] . ' +
+
Теперь его можно использовать в платежах вместо данных карты + ', + ]); + } + +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/oneTimeTokenPayment.php b/src/Examples/oneTimeTokenPayment.php new file mode 100644 index 0000000..46a46d2 --- /dev/null +++ b/src/Examples/oneTimeTokenPayment.php @@ -0,0 +1,130 @@ + 'FAILED', + 'message' => 'Token and sessionId are required' + ]); + exit(); + } + + throw new PaymentException('Необходимо передать одноразовый токен и ID сессии'); +} + +// Оплата по токену +// Установим номер (ID) заказа (номер заказа в вашем магазине, должен быть уникален в вашей системе) +$merchantPaymentReference = "order_id_" . time(); +$orderAsProduct = new Product([ + 'name' => 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 1.42, + 'quantity' => 2, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); +// Установим Телефон Плательщика +$billing->setPhone('+7-800-555-35-35'); +// Установим Город +$billing->setCity('Москва'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($orderAsProduct); +// Установим валюту +$payment->setCurrency('RUB'); + +// токен +$oneTimeUseToken = new OneTimeUseToken($_REQUEST['token'] ?? 'some token', $_REQUEST['sessionId'] ?? 'test session id'); + +$auth = new Authorization(); +$auth->setUsePaymentPage(false); +$auth->setPaymentMethod('CCVISAMC'); +$auth->setOneTimeUseToken($oneTimeUseToken); + +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization($auth); + +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference($merchantPaymentReference); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl( + ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http') . + '://' . + $_SERVER['HTTP_HOST'] . + '/?function=returnPage' +); + +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +if (!$jsonMode) { + $apiRequest->setDebugMode(); +} +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); + +try { + // Отправляем запрос и получаем ответ + $responseData = $apiRequest->sendAuthRequest($payment); + // Преобразуем ответ из JSON в массив + $responseData = json_decode((string) $responseData["response"], true); + + if ($jsonMode) { + echo json_encode($responseData); + exit(); + } +} catch (Exception $exception) { + + if ($jsonMode) { + echo json_encode([ + 'status' => 'FAILED', + 'message' => 'Payment method is unavailable' + ]); + exit(); + } + + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/oneTimeTokenPayment__prepend.php b/src/Examples/oneTimeTokenPayment__prepend.php new file mode 100644 index 0000000..916bceb --- /dev/null +++ b/src/Examples/oneTimeTokenPayment__prepend.php @@ -0,0 +1,17 @@ + +
+
+
+
+ + +
+
+ + +
+ +
+
+
+ diff --git a/src/Examples/paymentByFasterBinding.php b/src/Examples/paymentByFasterBinding.php new file mode 100644 index 0000000..a73d86b --- /dev/null +++ b/src/Examples/paymentByFasterBinding.php @@ -0,0 +1,161 @@ +setName('Круассаны Цезарь'); // Установим Наименование товара или услуги +$product1->setSku('toy-01'); // Установим Артикул +$product1->setVat(20); // Установим НДС +$product1->setUnitPrice(10); // Установим Стоимость за единицу +$product1->setQuantity(1); // Установим Количество + +/* Опишем вторую позицию с помощью сокращённого синтаксиса */ +$product2 = new Product([ + 'name' => 'Ассорти рулетиков Нежность', + 'sku' => 'toy-02', + 'unitPrice' => 8, + 'quantity' => 3, + 'vat' => 10, +]); + +/* Опишем третью позицию с помощью JSON */ +$product3 = new Product( + json_decode( + '{ + "name": "Ассорти мини-десертов", + "sku": "toy-03", + "unitPrice": 12, + "quantity": 2, + "vat": 0 + }', + true + ) +); + +/* Опишем Биллинговую (платёжную) информацию */ +$billing = new Billing; +$billing->setCountryCode('RU'); // Установим Код страны +$billing->setCity('Москва'); // Установим Город +$billing->setState('Центральный регион'); // Установим Регион +$billing->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Плательщика (первая строка) +$billing->setAddressLine2('Офис Ypmn'); // Установим Адрес Плательщика (вторая строка) +$billing->setZipCode('121000'); // Установим Почтовый Индекс Плательщика +$billing->setFirstName('Иван'); // Установим Имя Плательщика +$billing->setLastName('Петров'); // Установим Фамилия Плательщика +$billing->setPhone('9670660742'); // Установим Телефон Плательщика +$billing->setEmail('develop@ypmn.ru'); // Установим Email Плательщика + +/* Опишем Доствку и принимающее лицо (необязательно) */ +$delivery = new Delivery; +// Установим документ, подтверждающий право приёма доставки +$delivery->setIdentityDocument( + new IdentityDocument('123456', 'PERSONALID') +); +$delivery->setCountryCode('RU'); // Установим Код страны +$delivery->setCity('Москва'); // Установим Город +$delivery->setState('Центральный регион'); // Установим Регион +$delivery->setAddressLine1('Улица Старый Арбат, дом 10'); // Установим Адрес Лица, принимающего заказ (первая строка) +$delivery->setAddressLine2('Офис Ypmn'); // Установим Адрес Лица, принимающего заказ (вторая строка) +$delivery->setZipCode('121000'); // Установим Почтовый Индекс Лица, принимающего заказ +$delivery->setFirstName('Мария'); // Установим Имя Лица, принимающего заказ +$delivery->setLastName('Петрова'); // Установим Фамилия Лица, принимающего заказ +$delivery->setPhone('89670660743'); // Установим Телефон Лица, принимающего заказ +$delivery->setEmail('develop@ypmn.ru'); // Установим Email Лица, принимающего заказ +$delivery->setCompanyName('ООО "Вектор"'); // Установим Название Компании, в которой можно оставить заказ + +/* Создадим клиентское подключение */ +$client = new Client; +$client->setBilling($billing); // Установим биллинг +$client->setDelivery($delivery); // Установим доставку +$client->setCurrentClientIp(); // Установим IP (автоматически) +$client->setCurrentClientTime(); // И Установим время (автоматически) + +/* Создадим платёж */ +$payment = new Payment; +$payment->addProduct($product1); // Установим товарную позицию 1 +$payment->addProduct($product2); // Установим товарную позицию 2 +$payment->addProduct($product3); // Установим товарную позицию 3 +$payment->setCurrency('RUB'); // Установим валюту + +/* Создадим запись с токеном привязки */ +$merchantToken = new MerchantToken(); +$merchantToken->setBindingId("ab2bb91a-0012-4129-a2bf-cd18418bc726"); + +/* Создадим авторизацию по типу платежа */ +$authorization = new Authorization('FASTER_PAYMENTS',false); +$authorization->setMerchantToken($merchantToken); + +/* Запрашиваем подписку */ +$storedCredentials = new StoredCredentials(); +$storedCredentials->setUseType("merchant"); + +// Назначим авторизацию для нашего платежа // +$payment->setAuthorization($authorization); +// Запросим подписку +$payment->setStoredCredentials($storedCredentials); +// Установим номер заказа (должен быть уникальным в вашей системе) // +$payment->setMerchantPaymentReference('primer_nomer__' . time()); +// Установим адрес перенаправления пользователя после оплаты // +$payment->setReturnUrl('http://' . $_SERVER['SERVER_NAME'] . '/php-api-client/?function=returnPage'); +$payment->setClient($client); // Установим клиентское подключение + +/* Создадим HTTP-запрос к API */ +$apiRequest = new ApiRequest($merchant); + +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +// Отправим запрос // +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); + +/* Преобразуем ответ из JSON в массив */ +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'] ?? "", + 'sum' => $payment->sumProductsAmount() ?? 0, + ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/paymentByToken.php b/src/Examples/paymentByToken.php new file mode 100644 index 0000000..193f4ea --- /dev/null +++ b/src/Examples/paymentByToken.php @@ -0,0 +1,107 @@ + 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 1.42, + 'quantity' => 2, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); +// Установим Телефон Плательщика +$billing->setPhone('+7-800-555-35-35'); +// Установим Город +$billing->setCity('Москва'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($orderAsProduct); +// Установим валюту +$payment->setCurrency('RUB'); + +// токен +$token = new MerchantToken(); +$tokenHash = (isset($_GET['token']) ? $_GET['token'] : 'f7bcd9b9990b2d73cff5ad3df306b343'); +$token->setTokenHash($tokenHash); + +$auth = new Authorization(); +$auth->setUsePaymentPage(false); +$auth->setPaymentMethod('CCVISAMC'); +$auth->setMerchantToken($token); + +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization($auth); + +// Установим токен транзакции +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference($merchantPaymentReference); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); + +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + // Нарисуем кнопку оплаты 5 +// echo Std::drawYpmnButton([ +// 'url' => $responseData["paymentResult"]['url'] +// ]); + + // Либо сделаем редирект (перенаправление) браузера по адресу оплаты: + // echo Std::redirect($responseData["paymentResult"]['url']); +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/paymentCapture.php b/src/Examples/paymentCapture.php new file mode 100644 index 0000000..2de31d5 --- /dev/null +++ b/src/Examples/paymentCapture.php @@ -0,0 +1,35 @@ +setYpmnPaymentReference('2297597'); + +// Cумма исходной операции на авторизацию +$capture->setOriginalAmount(5300); +// Cумма фактического списания +$capture->setAmount(3700); +// Валюта +$capture->setCurrency('RUB'); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос к API +$responseData = $apiRequest->sendCaptureRequest($capture, $merchant); diff --git a/src/Examples/paymentGetStatus.php b/src/Examples/paymentGetStatus.php new file mode 100644 index 0000000..eb84f26 --- /dev/null +++ b/src/Examples/paymentGetStatus.php @@ -0,0 +1,21 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос к API +$responseData = $apiRequest->sendStatusRequest($merchantPaymentReference); diff --git a/src/Examples/paymentRefund.php b/src/Examples/paymentRefund.php new file mode 100644 index 0000000..4c6a0be --- /dev/null +++ b/src/Examples/paymentRefund.php @@ -0,0 +1,28 @@ +setYpmnPaymentReference("2297597"); +// Cумма исходной операции на авторизацию +$refund->setOriginalAmount(3700); +// Cумма фактического списания +$refund->setAmount(3700); +// Установим валюту +$refund->setCurrency('RUB'); +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос к API +$responseData = $apiRequest->sendRefundRequest($refund, $merchant); diff --git a/src/Examples/paymentRefundMarketplace.php b/src/Examples/paymentRefundMarketplace.php new file mode 100644 index 0000000..1bf5341 --- /dev/null +++ b/src/Examples/paymentRefundMarketplace.php @@ -0,0 +1,38 @@ +setYpmnPaymentReference("2297597"); +// Cумма исходной операции на авторизацию +$refund->setOriginalAmount(3700); +// Cумма фактического списания +$refund->setAmount(3700); + +// Добавим Сабмерчантов +$refund->addMarketPlaceSubmerchant('SUBMERCHANT_1', 3000); +$refund->addMarketPlaceSubmerchant('SUBMERCHANT_2', 700); + +// Установим валюту +$refund->setCurrency('RUB'); +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос к API +$responseData = $apiRequest->sendRefundRequest($refund, $merchant); diff --git a/src/Examples/payoutCreate.php b/src/Examples/payoutCreate.php new file mode 100644 index 0000000..b6bb033 --- /dev/null +++ b/src/Examples/payoutCreate.php @@ -0,0 +1,81 @@ +setMerchantPayoutReference('payout__' . time()); + +// Назначим сумму (здесь пример передачи данных из формы + стандартное значение) +$payout->setAmount( + new Amount((float) @$_POST['summ'] ?: 150.00, 'RUB') +); + +// Назначим Описание +$payout->setDescription(@$_POST['description'] ?: 'Тестовое Описание Платежа'); + +// Опишем и назначим Направление и Получателя платежа +$destination = new PayoutDestination(); +// Назначим номер карты (здесь пример передачи данных из формы + стандартное значение) +$destination->setCardNumber(@$_POST['cc-number'] ?: "4149605380309302"); +// Опишем получателя +$recipient = new Billing(); +// E-mail получателя +$recipient->setEmail('support@ypmn.ru'); +// Город получателя +$recipient->setCity('Москва'); +// Адрес получателя +$recipient->setAddressLine1('Арбат, 10'); +// Почтовый индекс получателя +$recipient->setZipCode('121000'); +// Код страны получателя (2 буквы, на английском) +$recipient->setCountryCode('RU'); + +// Имя получателя из GET-запроса +$postRecipientName = explode(' ', @$_POST['reciever-name'] ?: ''); +// Установим Имя получателя для платежа (здесь пример передачи данных из формы + стандартное значение) +$recipient->setFirstName(@$postRecipientName[0] ?: 'Иван'); +// Фамилия получателя (здесь пример передачи данных из формы + стандартное значение) +$recipient->setLastName(@$postRecipientName[1] ?: @$postRecipientName[0] ?: 'Иванович'); +$destination->setRecipient($recipient); +$payout->setDestination($destination); + +// Опишем и назначим Источник платежа +$source = new PayoutSource(); +// Опишем отправителя +$sender = new Billing(); +// Имя отправителя +$sender->setFirstName('Василий'); +// Фамилия отправителя +$sender->setLastName('Петрович'); +// Телефон отправителя +$sender->setPhone('0764111111'); +// Email отправителя +$sender->setEmail('test@example.ru');; +$source->setSender($sender); +$payout->setSource($source); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendPayoutCreateRequest($payout); diff --git a/src/Examples/payoutCreate__prepend.php b/src/Examples/payoutCreate__prepend.php new file mode 100644 index 0000000..fc37b01 --- /dev/null +++ b/src/Examples/payoutCreate__prepend.php @@ -0,0 +1,80 @@ + + + + +
+
+
+
+
+
+
+
+
+ Выплата на банковскую карту +
+
+ + + +
+
+
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+
+
+
+
+
+
+
diff --git a/src/Examples/payoutSbpCreate.php b/src/Examples/payoutSbpCreate.php new file mode 100644 index 0000000..3cd27e5 --- /dev/null +++ b/src/Examples/payoutSbpCreate.php @@ -0,0 +1,76 @@ +setMerchantPayoutReference( + 'payout__' + . str_replace('.', '', uniqid('' . time(), true)) +); + +// Назначим сумму (здесь пример передачи данных из формы + стандартное значение) +$payout->setAmount( + new Amount((float) @$_POST['summ'] ?: 150.00, 'RUB') +); + +// Назначим Описание +$payout->setDescription(@$_POST['description'] ?: 'Тестовое Описание Платежа'); + +// Опишем и назначим Направление и Получателя платежа +$destination = (new PayoutMobileDestination()) + ->setPhoneNumber(@$_POST['ph-number'] ?: "79001112233") // Назначим номер телефона (здесь пример передачи данных из формы + стандартное значение) + ->setBankInformation((int)$_POST['bank'], $_POST['bankName']); // Установим id/имя банка из списка НСПК +// Имя получателя из GET-запроса +$postRecipientName = explode(' ', @$_POST['reciever-name'] ?: ''); + +// Опишем получателя +$recipient = (new Billing()) + ->setEmail('support@ypmn.ru') // E-mail получателя + ->setCity('Москва') // Город получателя + ->setAddressLine1('Арбат, 10') // Адрес получателя + ->setZipCode('121000') // Почтовый индекс получателя + ->setCountryCode('RU') // Код страны получателя (2 буквы, на английском) + ->setFirstName(@$postRecipientName[0] ?: 'Иван') // Установим Имя получателя для платежа (здесь пример передачи данных из формы + стандартное значение) + ->setLastName(@$postRecipientName[1] ?: @$postRecipientName[0] ?: 'Иванович'); // Фамилия получателя (здесь пример передачи данных из формы + стандартное значение) + +$destination->setRecipient($recipient); +$payout->setDestination($destination); + +// Опишем и назначим Источник платежа +$source = new PayoutSource(); +// Опишем отправителя +$sender = (new Billing()) + ->setFirstName('Василий') // Имя отправителя + ->setLastName('Петрович') // Фамилия отправителя + ->setPhone('0764111111') // Телефон отправителя + ->setEmail('test@example.ru'); // Email отправителя +$source->setSender($sender); +$payout->setSource($source); + +// Создадим HTTP-запрос к API +$apiRequest = (new ApiRequest(new Merchant($_POST['merchantCode'], $_POST['merchantSecret']))) + ->setDebugMode() // (Опционально) Включить режим отладки (закомментируйте или удалите в рабочей программе!) + ->setSandboxMode(); // (Опционально) Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) + +// Отправим запрос +$responseData = $apiRequest->sendPayoutCreateRequest($payout); diff --git a/src/Examples/qstCreateIp.php b/src/Examples/qstCreateIp.php new file mode 100644 index 0000000..6543cbf --- /dev/null +++ b/src/Examples/qstCreateIp.php @@ -0,0 +1,113 @@ +setInn('773200328662'); + +/* Создадим объект данных анкеты добавляемого сабмерчанта */ +$qstSchema = new QstSchema(); + +/* Добавим в данные анкеты номер телефона сабмерчанта */ +$qstSchema->addPhone('+7 495 1234567, доб. 123'); + +/* Добавим в данные анкеты email сабмерчанта */ +$qstSchema->addEmail('example@ypmn.com'); + +/* Создадим и заполним объект юридического адреса сабмерчанта */ +$qstLegalAddress = (new QstSchemaLegalAddress()) + ->setZip('123456') // индекс + ->setRegion('Москва') // регион + ->setCity('Москва') // город + ->setStreet('ул. Охотный ряд') // улица + ->setHouse('1'); // дом +/* Укажем объект юридического адреса сабмерчанта в данных анкеты */ +$qstSchema->setLegalAddress($qstLegalAddress); + +/* + * Создадим объект фактического адреса сабмерчанта и отметим, что + * фактический адрес сабмерчанта соответствует юридическому + */ +$qstActualAddress = (new QstSchemaActualAddress())->setChecked(true); +/* Укажем объект фактического адреса сабмерчанта в данных анкеты */ +$qstSchema->setActualAddress($qstActualAddress); + +/* + * Создадим объект почтового адреса сабмерчанта и отметим, что + * почтовый адрес сабмерчанта соответствует юридическому + */ +$qstPostAddress = (new QstSchemaPostAddress())->setChecked(true); +/* Укажем объект почтового адреса сабмерчанта в данных анкеты */ +$qstSchema->setPostAddress($qstPostAddress); + +/* Создадим объект удостоверяющего документа и заполним его паспортными данными ИП */ +$qstIdentityDoc = (new QstSchemaIdentityDoc()) + ->setSeries('1234') // номер паспорта + ->setNumber('123456') // серия + ->setIssueDate('2000-01-30') // дата выдачи + ->setIssuedBy('МВД') // кем выдан + ->setIssuedByKP('123-456'); // к/п + +/* + * Заполним дату и место рождения ИП в данных анкеты. + * Укажем объект с паспортными данным ИП в данных анкеты. + */ +$qstSchema + ->setBirthDate('1969-02-23') // дата рождения ИП + ->setBirthPlace('Москва') // место рождения ИП + ->setIdentityDoc($qstIdentityDoc); // объект с паспортными данным ИП + +/* Создадим и заполним объект с банковскими данными ИП */ +$qstBankAccount = (new QstSchemaBankAccount()) + ->setBankBIK('044525700') // БИК + ->setBankCorAccount('30101810200000000700') // кор. счет + ->setBankAccount('40702810100002400756'); // расч. счет +/* Добавим объект с банковскими данными сабмерчанта в данные анкеты */ +$qstSchema->addBankAccount($qstBankAccount); + +/* Заполним дополнительное поле #1 (при наличии) */ +$qstSchema->setAdditionalFieldByKey(1, 'Доп. поле'); + +/* Установим объект с данными анкеты в объект анкеты */ +$qst->setSchema($qstSchema); + +/* Создадим HTTP-запрос к API */ +$apiRequest = new ApiRequest($merchant); + +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +/* Запрос на отправку анкеты */ +$responseData = $apiRequest->sendQstCreateRequest($qst); + +/* Преобразуем ответ из JSON в массив */ +try { + $responseData = json_decode((string) $responseData["response"], true); + if (isset($responseData['id'])) { + echo "Анкета #{$responseData['id']} создана и отправлена на проверку"; + } else { + echo "Анкета не создана, см. причину в ответа от сервера YPMN"; + } +} catch (Exception $exception) { + echo "Ошибка запроса: {$exception->getMessage()}"; + throw new Exception($exception->getMessage()); +} diff --git a/src/Examples/qstCreateOrg.php b/src/Examples/qstCreateOrg.php new file mode 100644 index 0000000..0c1d750 --- /dev/null +++ b/src/Examples/qstCreateOrg.php @@ -0,0 +1,120 @@ +setInn('7704217370'); + +/* Создадим объект данных анкеты добавляемого сабмерчанта */ +$qstSchema = new QstSchema(); + +/* Добавим в данные анкеты номер телефона сабмерчанта */ +$qstSchema->addPhone('+7 495 1234567, доб. 123'); +/* Добавим в данные анкеты еще один номер телефона сабмерчанта */ +$qstSchema->addPhone('+7 499 7654321, доб. 321'); + +/* Добавим в данные анкеты email сабмерчанта */ +$qstSchema->addEmail('example@ypmn.com'); + +/* Создадим и заполним объект юридического адреса сабмерчанта */ +$qstLegalAddress = (new QstSchemaLegalAddress()) + ->setZip('123112') // индекс + ->setRegion('Москва') // регион + ->setCity('Москва') // город + ->setStreet('Пресненская наб.') // улица + ->setHouse('д. 10') // дом + ->setFlat('эт. 41, Пом. I, комн. 6'); // офис +/* Установим объект юридического адреса сабмерчанта в данных анкеты */ +$qstSchema->setLegalAddress($qstLegalAddress); + +/* + * Создадим объект фактического адреса сабмерчанта и отметим, что + * фактический адрес сабмерчанта соответствует юридическому + */ +$qstActualAddress = (new QstSchemaActualAddress())->setChecked(true); +/* Установим объект фактического адреса сабмерчанта в данных анкеты */ +$qstSchema->setActualAddress($qstActualAddress); + + +/* Создадим объект удостоверяющего документа и заполним его паспортными данными руководителя организации */ +$qstCeoIdentityDoc = (new QstSchemaIdentityDoc()) + ->setSeries('1234') // номер паспорта + ->setNumber('123456') // серия + ->setIssueDate('2000-01-30') // дата выдачи + ->setIssuedBy('МВД') // кем выдан + ->setIssuedByKP('123-456'); // к/п + +/* + * Создадим объект руководителя организации. + * Установим в него объект с паспортными данными руководителя. + * Заполним место и дату рождения, адрес регистрации руководителя + */ +$qstCeo = (new QstSchemaCeo()) + ->setIdentityDoc($qstCeoIdentityDoc) // объект с паспортными данными руководителя + ->setBirthDate('1980-01-30') // дата рождения руководителя + ->setBirthPlace('Москва') // место рождения руководителя + ->setRegistrationAddress('г. Москва, ул. Ленина, д. 1, кв. 1'); // адрес регистрации руководителя + +/* Установим объект с данными руководителя организации в данных анкеты */ +$qstSchema->setCeo($qstCeo); + +/* Создадим объект собственника организации, заполним ФИО и долю собственника */ +$qstOwner = (new QstSchemaOwner())->setOwner('Иванов Иван Иванович')->setShare('100'); +/* Добавим объект с данными собственника организации в объект данных анкеты */ +$qstSchema->addOwner($qstOwner); + +/* Создадим и заполним объект с банковскими данными организации */ +$qstBankAccount = (new QstSchemaBankAccount()) + ->setBankBIK('044525700') // БИК + ->setBankCorAccount('30101810200000000700') // кор. счет + ->setBankAccount('40702810100002400756'); // расч. счет +/* Добавим объект с банковскими данными сабмерчанта в данные анкеты */ +$qstSchema->addBankAccount($qstBankAccount); + +/* Заполним дополнительное поле #1 (при наличии) */ +$qstSchema->setAdditionalFieldByKey(1, 'Доп. поле'); + +/* Установим объект с данными анкеты в объект анкеты */ +$qst->setSchema($qstSchema); + +/* Создадим HTTP-запрос к API */ +$apiRequest = new ApiRequest($merchant); + +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +/* Запрос на отправку анкеты */ +$responseData = $apiRequest->sendQstCreateRequest($qst); + +/* Преобразуем ответ из JSON в массив */ +try { + $responseData = json_decode((string) $responseData["response"], true); + if (isset($responseData['id'])) { + echo "Анкета #{$responseData['id']} создана и отправлена на проверку"; + } else { + echo "Анкета не создана, см. причину в ответа от сервера YPMN"; + } +} catch (Exception $exception) { + echo "Ошибка запроса: {$exception->getMessage()}"; + throw new Exception($exception->getMessage()); +} diff --git a/src/Examples/qstList.php b/src/Examples/qstList.php new file mode 100644 index 0000000..28ef173 --- /dev/null +++ b/src/Examples/qstList.php @@ -0,0 +1,19 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +/* Запрос на получение списка анкет */ +$responseData = $apiRequest->sendQstListRequest(); diff --git a/src/Examples/qstPrint.php b/src/Examples/qstPrint.php new file mode 100644 index 0000000..c01d100 --- /dev/null +++ b/src/Examples/qstPrint.php @@ -0,0 +1,22 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +/* id анкеты, полученный при создании анкеты */ +$qstId = 1; + +/* Запрос на получение pdf анкеты */ +$apiRequest->sendQstPrintRequest($qstId); diff --git a/src/Examples/qstStatus.php b/src/Examples/qstStatus.php new file mode 100644 index 0000000..37d4f1d --- /dev/null +++ b/src/Examples/qstStatus.php @@ -0,0 +1,22 @@ +setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) // +$apiRequest->setSandboxMode(); + +/* id анкеты, полученный при создании анкеты */ +$qstId = 1; + +/* Запрос на получение статуса анкеты */ +$responseData = $apiRequest->sendQstStatusRequest($qstId); diff --git a/src/Examples/returnPage.php b/src/Examples/returnPage.php new file mode 100644 index 0000000..f5be6df --- /dev/null +++ b/src/Examples/returnPage.php @@ -0,0 +1,17 @@ +Благодарим за оплатуЧек выслан вам на почту.

'; +} elseif ($statusResponseFromServer) { + echo '

Оплата не прошла

'; + $messageResponseFromServer = (json_decode($_POST['body'], true))['message'] . '

' ?? ''; + echo $messageResponseFromServer; +} + +echo '
$_GET: ' . print_r($_GET, true) . '
'; +echo '
$_POST: ' . print_r($_POST, true) . '
'; diff --git a/src/Examples/secureFields.php b/src/Examples/secureFields.php new file mode 100644 index 0000000..30b87fe --- /dev/null +++ b/src/Examples/secureFields.php @@ -0,0 +1,186 @@ + + + + + + + + +
+ + +
+ +

Форма без использования bootstrap стилей, валидации введенных данных на стороне формы.

+ +

Пример javascript для формы см. по ссылке.

+ +
+ + + + + + + + + + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + +

+ +
+ +
+ + +
+ +
+ +
+ + +
+ +

Форма с использованием bootstrap стилей, валидацией введенных данных на стороне формы в реальном времени.

+ +

Пример javascript для формы см. по ссылке.

+ +
+ + + + + + + + + + +
+ +
+
+
+ Card Number +
+
+
+
+
+
+ Exp. date +
+
+
+
+
+
+ CVV +
+
+
+
+
+
+ Name + +
+
+
+
+
+
+ +
+ + + + + + + + + + +
+ +
+
+ +
+
+ + + diff --git a/src/Examples/secureFields__prepend.php b/src/Examples/secureFields__prepend.php new file mode 100644 index 0000000..f77766c --- /dev/null +++ b/src/Examples/secureFields__prepend.php @@ -0,0 +1,31 @@ +setSandboxMode(); +//$apiRequest->setDebugMode(); + +try { + $session = $apiRequest->sendSessionRequest($sessionRequest); + + $response = json_decode($session['response']); + + $merchantCode = $merchant->getCode(); + $sessionId = $response->sessionId; + $sandboxMode = $apiRequest->getSandboxMode(); +} catch (PaymentException $e) { +} diff --git a/src/Examples/simpleGetPaymentLink.php b/src/Examples/simpleGetPaymentLink.php new file mode 100644 index 0000000..7dd50cf --- /dev/null +++ b/src/Examples/simpleGetPaymentLink.php @@ -0,0 +1,100 @@ + 'Заказ №' . $merchantPaymentReference, + 'sku' => $merchantPaymentReference, + 'unitPrice' => 200.42, + 'quantity' => 1, +]); + +// Опишем Биллинговую (платёжную) информацию +$billing = new Billing; +// Установим Код страны +$billing->setCountryCode('RU'); +// Установим Имя Плательщика +$billing->setFirstName('Иван'); +// Установим Фамилия Плательщика +$billing->setLastName('Петров'); +// Установим Email Плательщика +$billing->setEmail('test1@ypmn.ru'); +// Установим Телефон Плательщика +$billing->setPhone('+7-800-555-35-35'); +// Установим Город +$billing->setCity('Москва'); + +// Создадим клиентское подключение +$client = new Client; +// Установим биллинг +$client->setBilling($billing); + +// Создадим платёж +$payment = new Payment; +// Установим позиции +$payment->addProduct($orderAsProduct); +// Установим валюту +$payment->setCurrency('RUB'); +// Создадим и установим авторизацию по типу платежа +$payment->setAuthorization(new Authorization('CCVISAMC',true)); +// Установим номер заказа (должен быть уникальным в вашей системе) +$payment->setMerchantPaymentReference($merchantPaymentReference); +// Установим адрес перенаправления пользователя после оплаты +$payment->setReturnUrl('https://test.u2go.ru/php-api-client/?function=returnPage'); +// Установим клиентское подключение +$payment->setClient($client); + +// Создадим HTTP-запрос к API +$apiRequest = new ApiRequest($merchant); +// Включить режим отладки (закомментируйте или удалите в рабочей программе!) +$apiRequest->setDebugMode(); +// Переключиться на тестовый сервер (закомментируйте или удалите в рабочей программе!) +$apiRequest->setSandboxMode(); +// Отправим запрос +$responseData = $apiRequest->sendAuthRequest($payment, $merchant); +// Преобразуем ответ из JSON в массив +try { + $responseData = json_decode((string) $responseData["response"], true); + + if ($responseData) { + // Выведем кнопку оплаты + echo Std::drawYpmnButton([ + 'url' => $responseData["paymentResult"]['url'], + 'sum' => $payment->sumProductsAmount(), + ]); + + // .. или сделаем редирект на форму оплаты (опционально) + // Std::redirect($responseData["paymentResult"]['url']); + } +} catch (Exception $exception) { + //TODO: обработка исключения + echo Std::alert([ + 'text' => ' + Извините, платёжный метод временно недоступен.
+ Вы можете попробовать другой способ оплаты, либо свяжитесь с продавцом.
+
+
' . $exception->getMessage() . '
', + 'type' => 'danger', + ]); + + throw new PaymentException('Платёжный метод временно недоступен'); +} diff --git a/src/Examples/start.php b/src/Examples/start.php new file mode 100644 index 0000000..b6b5d32 --- /dev/null +++ b/src/Examples/start.php @@ -0,0 +1,16 @@ +setNumber($number) ->setType($type); diff --git a/src/IdentityDocumentInterface.php b/src/IdentityDocumentInterface.php index 903bfab..76efb8b 100644 --- a/src/IdentityDocumentInterface.php +++ b/src/IdentityDocumentInterface.php @@ -1,6 +1,6 @@ setMerchantCode($merchantCode); + if (null !== $amount) { + $this->setAmount($amount); + } + } + + /** @inheritDoc */ + public function setMerchantCode(string $merchantCode): self + { + $this->merchantCode = $merchantCode; + + return $this; + } + + /** @inheritDoc */ + public function getMerchantCode(): string + { + return $this->merchantCode; + } + + /** @inheritDoc + * @throws PaymentException + */ + public function setAmount(float $amount): self + { + if ($amount <= 0) { + throw new PaymentException('Отрицательные суммы не принимаются'); + } + + $this->amount = $amount; + + return $this; + } + + /** @inheritDoc */ + public function getAmount(): float + { + return $this->amount; + } +} diff --git a/src/MarketplaceSubmerchantInterface.php b/src/MarketplaceSubmerchantInterface.php new file mode 100644 index 0000000..ccf00dd --- /dev/null +++ b/src/MarketplaceSubmerchantInterface.php @@ -0,0 +1,35 @@ +cvv; } /** @inheritDoc */ - public function setCvv(int $cvv): MerchantToken + public function setCvv(string $cvv): MerchantToken { $this->cvv = $cvv; return $this; @@ -52,6 +55,20 @@ public function setOwner(string $owner): MerchantToken return $this; } + /** @inheritDoc */ + public function getBindingId(): string + { + return $this->bindingId; + } + + /** @inheritDoc */ + public function setBindingId(string $bindingId): self + { + $this->bindingId = $bindingId; + return $this; + } + + /** @inheritDoc */ public function toArray() : array { @@ -76,4 +93,27 @@ public function toArray() : array return $resultArray; } -} \ No newline at end of file + + /** + * @return mixed + * @throws PaymentException + */ + public function jsonSerialize() + { + if(empty($this->tokenHash) && empty($this->bindingId)){ + throw new PaymentException("Не хватает токена"); + } + + if (!empty($this->bindingId)) { + $resultArray = [ + 'bindingId' => $this->bindingId, + ]; + } else { + $resultArray = [ + 'tokenHash' => $this->tokenHash, + ]; + } + + return json_encode($resultArray, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); + } +} diff --git a/src/MerchantTokenInterface.php b/src/MerchantTokenInterface.php index 3d3ed29..533eab4 100644 --- a/src/MerchantTokenInterface.php +++ b/src/MerchantTokenInterface.php @@ -1,6 +1,6 @@ setToken($token); + $this->setSessionId($sessionId); + } + + /** + * @return string + */ + public function getToken(): string + { + return $this->token; + } + + /** + * @param string $token + * @return OneTimeUseToken + */ + public function setToken(string $token): self + { + $this->token = $token; + + return $this; + } + + /** + * @return string + */ + public function getSessionId(): string + { + return $this->sessionId; + } + + /** + * @param string $sessionId + * @return OneTimeUseToken + */ + public function setSessionId(string $sessionId): self + { + $this->sessionId = $sessionId; + + return $this; + } + + public function toArray() + { + return [ + 'token' => $this->getToken(), + 'sessionId' => $this->getSessionId(), + ]; + } +} diff --git a/src/OrderData.php b/src/OrderData.php index f922667..6a8796a 100644 --- a/src/OrderData.php +++ b/src/OrderData.php @@ -1,14 +1,14 @@ payuPaymentReference; + return $this->ypmnPaymentReference; } /** @inheritDoc */ - public function setPayuPaymentReference(string $payuPaymentReference): self + public function setYpmnPaymentReference(string $ypmnPaymentReference): self { - $this->payuPaymentReference = $payuPaymentReference; + $this->ypmnPaymentReference = $ypmnPaymentReference; return $this; } + /** @inheritDoc */ + public function getYpmnPaymentReference(): string + { + return $this->ypmnPaymentReference; + } + /** @inheritDoc */ public function getMerchantPaymentReference(): string { @@ -79,7 +85,6 @@ public function getStatus(): string return $this->status; } - /** @inheritDoc */ public function setStatus(string $status): self { @@ -169,4 +174,4 @@ public function setLoyaltyPointsDetails(array $loyaltyPointsDetails): self } return $this; } -} \ No newline at end of file +} diff --git a/src/OrderDataInterface.php b/src/OrderDataInterface.php index 46d8202..45c8678 100644 --- a/src/OrderDataInterface.php +++ b/src/OrderDataInterface.php @@ -1,6 +1,6 @@ authorization; } + /** @inheritDoc */ + public function getStoredCredentials(): StoredCredentialsInterface + { + if (empty($this->storedCredentials)) { + $this->storedCredentials = new StoredCredentials(); + } + + return $this->storedCredentials; + } + + /** @inheritDoc */ + public function setStoredCredentials(StoredCredentialsInterface $storedCredentials): self + { + $this->storedCredentials = $storedCredentials; + + return $this; + } + /** @inheritDoc */ public function setClient(ClientInterface $client) : self { @@ -140,17 +161,30 @@ public function sumProductsAmount() : float } #[\ReturnTypeWillChange] - public function jsonSerialize(): string|bool + public function jsonSerialize() { + $storedCredentials = $this->getStoredCredentials()->arraySerialize(); + //TODO: проверка необходимых параметров - $requestData = [ - 'merchantPaymentReference' => $this->getMerchantPaymentReference(), - 'currency' => $this->getCurrency(), - 'returnUrl' => $this->getReturnUrl(), - 'authorization' => $this->getAuthorization()->arraySerialize(), - 'client' => $this->getClient()->arraySerialize(), - 'products' => $this->getProductsArray(), - ]; + $requestData['merchantPaymentReference'] = $this->getMerchantPaymentReference(); + $requestData['currency'] = $this->getCurrency(); + $requestData['returnUrl'] = $this->getReturnUrl(); + $requestData['authorization'] = $this->getAuthorization()->arraySerialize(); + + /* Поле storedCredentials обязательно только при привязке карты */ + if ((bool)$storedCredentials !== false) { + $requestData['storedCredentials'] = $storedCredentials; + /* При создании привязки через СБП необходимо 2 поля consentType и subscriptionPurpose */ + if ( + empty($requestData['storedCredentials']['consentType']) === false && + $requestData['authorization']['paymentMethod'] === Authorization::TYPE_FASTER_PAYMENTS + ) { + $requestData['storedCredentials']['subscriptionPurpose'] = $this->getStoredCredentials()->getSubscriptionPurpose(); + } + } + + $requestData['client'] = $this->getClient()->arraySerialize(); + $requestData['products'] = $this->getProductsArray(); $requestData = Std::removeNullValues($requestData); diff --git a/src/PaymentException.php b/src/PaymentException.php index e0d384f..690b300 100644 --- a/src/PaymentException.php +++ b/src/PaymentException.php @@ -1,6 +1,6 @@ setOrderTimeout($timeoutSeconds); + } + + /** @inheritDoc */ + public function setOrderTimeout(int $timeoutSeconds): self + { + if ($timeoutSeconds < self::MIN_ORDER_TIMEOUT_SECONDS) { + throw new PaymentException($timeoutSeconds . ' -- слишком маленькое время для оплаты заказа (в секундах)'); + } + $this->timeoutSeconds = $timeoutSeconds; + + return $this; + } + + /** @inheritDoc */ + public function getOrderTimeout(): int + { + return $this->timeoutSeconds; + } + + /** @inheritDoc */ + public function toArray(): array + { + return [ + 'orderTimeout' => $this->getOrderTimeout(), + ]; + } +} diff --git a/src/PaymentPageOptionsInterface.php b/src/PaymentPageOptionsInterface.php new file mode 100644 index 0000000..29094f8 --- /dev/null +++ b/src/PaymentPageOptionsInterface.php @@ -0,0 +1,32 @@ +setPaymentReference($paymentReference); + } + + private function setPaymentReference(int $paymentReference) : self + { + $this->paymentReference = $paymentReference; + return $this; + } + + /** + * @throws PaymentException + */ + public function jsonSerialize(): string + { + if(is_null($this->paymentReference)){ + throw new PaymentException("Не хватает номера оплаты для токенизации"); + } + + $resultArray = [ + 'payuPaymentReference' => $this->paymentReference, + ]; + + return json_encode($resultArray, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); + } +} \ No newline at end of file diff --git a/src/PaymentResult.php b/src/PaymentResult.php index 7bc7807..e235ccb 100644 --- a/src/PaymentResult.php +++ b/src/PaymentResult.php @@ -1,6 +1,6 @@ serviceProcessingType = $serviceProcessingType; return $this; } -} \ No newline at end of file +} diff --git a/src/PaymentResultInterface.php b/src/PaymentResultInterface.php index aa42163..0b1a535 100644 --- a/src/PaymentResultInterface.php +++ b/src/PaymentResultInterface.php @@ -1,6 +1,6 @@ merchantPayoutReference = $merchantPayoutReference; + } + + /** @inheritdoc */ + public function getMerchantPayoutReference(): ?string + { + return $this->merchantPayoutReference; + } + + /** @inheritdoc */ + public function setMerchantPayoutReference(?string $merchantPayoutReference): self + { + $this->merchantPayoutReference = $merchantPayoutReference; + return $this; + } + + /** @inheritdoc */ + public function getAmount(): ?Amount + { + return $this->amount; + } + + /** @inheritdoc */ + public function setAmount(?Amount $amount): self + { + $this->amount = $amount; + return $this; + } + + /** @inheritdoc */ + public function getDescription(): ?string + { + return $this->description; + } + + /** @inheritdoc */ + public function setDescription(?string $description): self + { + $this->description = $description; + return $this; + } + + /** @inheritdoc */ + public function getDestination(): ?DestinationInterface + { + return $this->destination; + } + + /** @inheritdoc */ + public function setDestination(?DestinationInterface $destination): self + { + $this->destination = $destination; + return $this; + } + + /** @inheritdoc */ + public function getSource(): ?PayoutSource + { + return $this->source; + } + + /** @inheritdoc */ + public function setSource(?PayoutSource $source): self + { + $this->source = $source; + return $this; + } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + //TODO: проверка необходимых параметров + $requestData = [ + 'merchantPayoutReference' => $this->getMerchantPayoutReference(), + 'amount' => $this->getAmount()->arraySerialize(), + 'description' => $this->getDescription(), + 'destination' => $this->getDestination()->arraySerialize(), + 'source' => $this->getSource()->arraySerialize(), + ]; + + $requestData = Std::removeNullValues($requestData); + + /** + * В некоторых версиях PHP необходима тонкая настройка округления при сериализации + * https://stackoverflow.com/questions/42981409/php7-1-json-encode-float-issue + */ + ini_set('serialize_precision', '14'); + ini_set('precision', '14'); + + return json_encode($requestData, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); + } +} diff --git a/src/PayoutDestination.php b/src/PayoutDestination.php new file mode 100644 index 0000000..ee6acbe --- /dev/null +++ b/src/PayoutDestination.php @@ -0,0 +1,124 @@ +setType($type); + } + + /** @inheritdoc */ + public function getType(): string + { + return $this->type; + } + + /** @inheritdoc */ + public function setType(string $type): self + { + if (!in_array($type, self::AVAILABLE_TYPES)) { + throw new PaymentException('Недопустимый тип выплаты'); + } + + $this->type = $type; + + return $this; + } + + /** @inheritdoc */ + public function getCard(): ?CardDetails + { + return $this->card; + } + + /** @inheritdoc */ + public function setCard(?CardDetails $card): self + { + $this->card = $card; + + return $this; + } + + /** @inheritdoc */ + public function getDetails(): ?DetailsInterface + { + return $this->card; + } + + /** @inheritdoc */ + public function setDetails(?DetailsInterface $details): self + { + $this->card = $details; + + return $this; + } + + /** @inheritdoc */ + public function getRecipient(): ?Billing + { + return $this->recipient; + } + + /** @inheritdoc */ + public function setRecipient(?Billing $recipient): self + { + $this->recipient = $recipient; + return $this; + } + + /** @inheritdoc */ + public function setCardNumber(string $cardNumber): self + { + if ($this->getCard() === null) { + $this->setCard(new CardDetails()); + } + + $this->getCard()->setNumber($cardNumber); + + return $this; + } + + /** + * @return array + */ + public function arraySerialize() : array + { + $address = $this->getRecipient()->getAddressLine1() + . ( $this->getRecipient()->getAddressLine2() ? '' . $this->getRecipient()->getAddressLine2() : null); + + return [ + 'type' => $this->getType(), + 'card' => [ + 'cardNumber' => $this->getCard()->getNumber(), + ], + 'recipient' => [ + 'type' => $this->getRecipient()->getType(), + 'email' => $this->getRecipient()->getEmail(), + 'city' => $this->getRecipient()->getCity(), + 'address' => $address, + 'postalCode' => $this->getRecipient()->getZipCode(), + 'countryCode' => $this->getRecipient()->getCountryCode(), + 'firstName' => $this->getRecipient()->getFirstName(), + 'lastName' => $this->getRecipient()->getLastName() + ], + ]; + } +} diff --git a/src/PayoutDestinationInterface.php b/src/PayoutDestinationInterface.php new file mode 100644 index 0000000..6838cf3 --- /dev/null +++ b/src/PayoutDestinationInterface.php @@ -0,0 +1,26 @@ +setType($type); + } + + /** @inheritdoc */ + public function getType(): string + { + return $this->type; + } + + /** @inheritdoc */ + public function setType(string $type): self + { + if (!in_array($type, self::AVAILABLE_TYPES)) { + throw new PaymentException('Недопустимый тип выплаты'); + } + + $this->type = $type; + + return $this; + } + + /** @inheritdoc */ + public function getDetails(): ?DetailsInterface + { + return $this->details; + } + + /** @inheritdoc */ + public function setDetails(?DetailsInterface $details): self + { + $this->details = $details; + + return $this; + } + + /** @inheritdoc */ + public function getRecipient(): ?Billing + { + return $this->recipient; + } + + /** @inheritdoc */ + public function setRecipient(?Billing $recipient): self + { + $this->recipient = $recipient; + return $this; + } + + /** @inheritdoc */ + public function setPhoneNumber(string $phoneNumber): self + { + if ($this->getDetails() === null) { + $this->setDetails(new PhoneDetails()); + } + + $this->getDetails()->setNumber($phoneNumber); + + return $this; + } + + /** @inheritdoc */ + public function setBankInformation(int $bankId, string $bankName): self + { + if ($this->getDetails() === null) { + $this->setDetails(new PhoneDetails()); + } + + $this->getDetails()->setBankId($bankId); + $this->getDetails()->setBankName($bankName); + + return $this; + } + + /** + * @return array + */ + public function arraySerialize() : array + { + $address = $this->getRecipient()->getAddressLine1() + . ( $this->getRecipient()->getAddressLine2() ? '' . $this->getRecipient()->getAddressLine2() : null); + + return [ + 'type' => $this->getType(), + 'sbp' => [ + 'phoneNumber' => $this->getDetails()->getNumber(), + "bankId" => $this->getDetails()->getBankId(), + "bankName" => $this->getDetails()->getBankName() + ], + 'recipient' => [ + 'type' => $this->getRecipient()->getType(), + 'email' => $this->getRecipient()->getEmail(), + 'city' => $this->getRecipient()->getCity(), + 'address' => $address, + 'postalCode' => $this->getRecipient()->getZipCode(), + 'countryCode' => $this->getRecipient()->getCountryCode(), + 'firstName' => $this->getRecipient()->getFirstName(), + 'lastName' => $this->getRecipient()->getLastName() + ], + ]; + } +} diff --git a/src/PayoutSource.php b/src/PayoutSource.php new file mode 100644 index 0000000..c97e136 --- /dev/null +++ b/src/PayoutSource.php @@ -0,0 +1,57 @@ +type; + } + + /** @inheritdoc */ + public function setType(string $type): self + { + $this->type = $type; + + return $this; + } + + /** @inheritdoc */ + public function getSender(): Billing + { + return $this->sender; + } + + /** @inheritdoc */ + public function setSender(Billing $sender): self + { + $this->sender = $sender; + + return $this; + } + + /** @inheritdoc */ + public function arraySerialize() : array + { + //TODO: проверка параметров перед отправкой + + return [ + 'type' => $this->getType(), + 'sender' => [ + 'firstName' => $this->getSender()->getFirstName(), + 'lastName' => $this->getSender()->getLastName(), + 'email' => $this->getSender()->getEmail(), + 'phone' => $this->getSender()->getPhone(), + ], + ]; + } +} diff --git a/src/PayoutSourceInterface.php b/src/PayoutSourceInterface.php new file mode 100644 index 0000000..d18be8e --- /dev/null +++ b/src/PayoutSourceInterface.php @@ -0,0 +1,37 @@ +number; + } + + public function setNumber(string $number): self + { + $this->number = $number; + + return $this; + } + + public function getBankId(): int + { + return $this->bankId; + } + + public function setBankId(int $bankId): self + { + $this->bankId = $bankId; + + return $this; + } + + public function getBankName(): string + { + return $this->bankName; + } + + public function setBankName(string $bankName): self + { + $this->bankName = $bankName; + + return $this; + } +} diff --git a/src/PodeliMerchant.php b/src/PodeliMerchant.php new file mode 100644 index 0000000..951ed7a --- /dev/null +++ b/src/PodeliMerchant.php @@ -0,0 +1,187 @@ +login; + } + + public function setLogin(string $login): PodeliMerchant + { + $this->login = $login; + + return $this; + } + + public function getName(): string + { + return $this->name; + } + + public function setName(string $name): PodeliMerchant + { + $this->name = $name; + + return $this; + } + + public function getLegalEntity(): string + { + return $this->legalEntity; + } + + public function setLegalEntity(string $legalEntity): PodeliMerchant + { + $this->legalEntity = $legalEntity; + + return $this; + } + + public function getInn(): string + { + return $this->inn; + } + + public function setInn(string $inn): PodeliMerchant + { + $this->inn = $inn; + + return $this; + } + + public function getMcc(): string + { + return $this->mcc; + } + + public function setMcc(string $mcc): PodeliMerchant + { + $this->mcc = $mcc; + + return $this; + } + + public function getEmail(): string + { + return $this->email; + } + + public function setEmail(string $email): PodeliMerchant + { + $this->email = $email; + + return $this; + } + + public function getSiteUrl(): string + { + return $this->siteUrl; + } + + public function setSiteUrl(string $siteUrl): PodeliMerchant + { + $this->siteUrl = $siteUrl; + + return $this; + } + + public function getAgreementSignDate(): string + { + return $this->agreementSignDate; + } + + public function setAgreementSignDate(string $agreementSignDate): PodeliMerchant + { + $this->agreementSignDate = $agreementSignDate; + + return $this; + } + + public function getBankDetails(): PodeliMerchantBankDetails + { + return $this->requisite; + } + + public function setBankDetails(PodeliMerchantBankDetails $requisite): PodeliMerchant + { + $this->requisite = $requisite; + + return $this; + } + + public function getAddress(): PodeliMerchantAddress + { + return $this->address; + } + + public function setAddress(PodeliMerchantAddress $address): PodeliMerchant + { + $this->address = $address; + + return $this; + } + + public function getAsArray(): array + { + return array_filter([ + 'login' => $this->login, + 'name' => $this->name, + 'legalEntity' => $this->legalEntity, + 'inn' => $this->inn, + 'mcc' => $this->mcc, + 'email' => $this->email, + 'siteUrl' => $this->siteUrl, + 'agreementSignDate' => $this->agreementSignDate, + 'requisite' => $this->requisite->getAsArray(), + 'address' => $this->address->getAsArray(), + ], fn($value) => !is_null($value)); + } + + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return json_encode($this->getAsArray(), JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); + } +} diff --git a/src/PodeliMerchantAddress.php b/src/PodeliMerchantAddress.php new file mode 100644 index 0000000..3a8e29c --- /dev/null +++ b/src/PodeliMerchantAddress.php @@ -0,0 +1,44 @@ +index; + } + + public function setIndex(string $index): PodeliMerchantAddress + { + $this->index = $index; + + return $this; + } + + public function getCity(): string + { + return $this->city; + } + + public function setCity(string $city): PodeliMerchantAddress + { + $this->city = $city; + + return $this; + } + + public function getAsArray(): array + { + return array_filter([ + 'index' => $this->index, + 'city' => $this->city, + ], fn($value) => !is_null($value)); + } +} diff --git a/src/PodeliMerchantBankDetails.php b/src/PodeliMerchantBankDetails.php new file mode 100644 index 0000000..913b27f --- /dev/null +++ b/src/PodeliMerchantBankDetails.php @@ -0,0 +1,76 @@ +bankName; + } + + public function setBankName(string $bankName): PodeliMerchantBankDetails + { + $this->bankName = $bankName; + + return $this; + } + + public function getBankIdentifierCode(): string + { + return $this->bankIdentifierCode; + } + + public function setBankIdentifierCode(string $bankIdentifierCode): PodeliMerchantBankDetails + { + $this->bankIdentifierCode = $bankIdentifierCode; + + return $this; + } + + public function getCorrespondingAccount(): string + { + return $this->correspondingAccount; + } + + public function setCorrespondingAccount(string $correspondingAccount): PodeliMerchantBankDetails + { + $this->correspondingAccount = $correspondingAccount; + + return $this; + } + + public function getPaymentAccount(): string + { + return $this->paymentAccount; + } + + public function setPaymentAccount(string $paymentAccount): PodeliMerchantBankDetails + { + $this->paymentAccount = $paymentAccount; + + return $this; + } + + public function getAsArray(): array + { + return array_filter([ + 'bankName' => $this->bankName, + 'bankIdentifierCode' => $this->bankIdentifierCode, + 'correspondingAccount' => $this->correspondingAccount, + 'paymentAccount' => $this->paymentAccount, + ], fn($value) => !is_null($value)); + } +} diff --git a/src/Product.php b/src/Product.php index 159dcff..cdf433c 100644 --- a/src/Product.php +++ b/src/Product.php @@ -1,8 +1,6 @@ -setAmount($params['amount']); } + if (isset($params['merchantCode'])) { + $this->setMarketplaceSubmerchantByCode($params['merchantCode']); + } } /** @inheritDoc */ @@ -103,7 +107,11 @@ public function getUnitPrice(): float /** @inheritDoc */ public function setUnitPrice(float $unitPrice): self { + if ($unitPrice <= 0) { + throw new PaymentException('Нулевая цена не принимается'); + } $this->unitPrice = round($unitPrice, 2, PHP_ROUND_HALF_UP); + return $this; } @@ -116,7 +124,12 @@ public function getQuantity(): int /** @inheritDoc */ public function setQuantity(int $quantity): self { + if ($quantity <= 0) { + throw new PaymentException('Нулевое количество не принимается'); + } + $this->quantity = $quantity; + return $this; } @@ -160,10 +173,18 @@ public function setAdditionalDetails(string $additionalDetails): self return $this; } + /** @inheritDoc */ + public function setMarketplaceSubmerchantByCode(string $merchantCode): self + { + $this->marketplaceSubmerchant = new MarketplaceSubmerchant($merchantCode); + + return $this; + } + /** @inheritDoc */ public function arraySerialize(): array { - return [ + $resultArray = [ 'name' => $this->getName(), 'sku' => $this->getSku(), 'unitPrice' => (null !== $this->getUnitPrice() ? number_format($this->getUnitPrice(), 2,'.','') : null), @@ -172,5 +193,12 @@ public function arraySerialize(): array 'amount' => (null !== $this->getAmount() ? number_format($this->getAmount(), 2,'.','') : null), 'vat' => $this->getVat(), ]; + + if (null !== $this->marketplaceSubmerchant) { + $resultArray['marketplace']['version'] = 1; + $resultArray['marketplace']['merchantCode'] = $this->marketplaceSubmerchant->getMerchantCode(); + } + + return $resultArray; } } diff --git a/src/ProductInterface.php b/src/ProductInterface.php index 5e373d4..f5c55ee 100644 --- a/src/ProductInterface.php +++ b/src/ProductInterface.php @@ -1,111 +1,123 @@ -inn; + } + + /** @inheritdoc */ + public function setInn(string $inn): self + { + $this->inn = $inn; + return $this; + } + + /** @inheritdoc */ + public function getSchema(): QstSchemaInterface + { + return $this->schema; + } + + /** @inheritdoc */ + public function setSchema(QstSchemaInterface $schema): self + { + $this->schema = $schema; + return $this; + } + + /** + * @return string + */ + public function jsonSerialize(): string + { + $requestData = [ + 'inn' => $this->getInn(), + 'schema' => $this->getSchema()->toArray() + ]; + + return json_encode($requestData, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); + } +} diff --git a/src/QstInterface.php b/src/QstInterface.php new file mode 100644 index 0000000..ac4bfb2 --- /dev/null +++ b/src/QstInterface.php @@ -0,0 +1,34 @@ +foreignName; + } + + /** @inheritdoc */ + public function setForeignName(string $foreignName): self + { + $this->foreignName = $foreignName; + return $this; + } + + /** @inheritdoc */ + public function getPhones(): array + { + return $this->phones; + } + + /** @inheritdoc */ + public function addPhone(string $phone): self + { + $this->phones[] = compact('phone'); + return $this; + } + + /** @inheritdoc */ + public function getEmails(): array + { + return $this->emails; + } + + /** @inheritdoc */ + public function addEmail(string $email): self + { + $this->emails[] = compact('email'); + return $this; + } + + /** @inheritdoc */ + public function getLegalAddress(): QstSchemaAddressInterface + { + return $this->legalAddress; + } + + /** @inheritdoc */ + public function setLegalAddress(QstSchemaAddressInterface $legalAddress): self + { + $this->legalAddress = $legalAddress; + return $this; + } + + /** @inheritdoc */ + public function getPostAddress(): ?QstSchemaAddressInterface + { + return $this->postAddress; + } + + /** @inheritdoc */ + public function setPostAddress(QstSchemaAddressInterface $postAddress): self + { + $this->postAddress = $postAddress; + return $this; + } + + /** @inheritdoc */ + public function getActualAddress(): QstSchemaAddressInterface + { + return $this->actualAddress; + } + + /** @inheritdoc */ + public function setActualAddress(QstSchemaAddressInterface $actualAddress): self + { + $this->actualAddress = $actualAddress; + return $this; + } + + /** @inheritdoc */ + public function getCeo(): ?QstSchemaCeoInterface + { + return $this->ceo; + } + + /** @inheritdoc */ + public function setCeo(QstSchemaCeoInterface $ceo): self + { + $this->ceo = $ceo; + return $this; + } + + /** @inheritdoc */ + public function getOwners(): array + { + return $this->owners; + } + + /** @inheritdoc */ + public function addOwner(QstSchemaOwnerInterface $owner): self + { + $this->owners[] = $owner; + return $this; + } + + /** @inheritdoc */ + public function getBoardOfDirectors(): ?string + { + return $this->boardOfDirectors; + } + + /** @inheritdoc */ + public function setBoardOfDirectors(string $boardOfDirectors): self + { + $this->boardOfDirectors = $boardOfDirectors; + return $this; + } + + /** @inheritdoc */ + public function getManagementBoard(): ?string + { + return $this->managementBoard; + } + + /** @inheritdoc */ + public function setManagementBoard(string $managementBoard): self + { + $this->managementBoard = $managementBoard; + return $this; + } + + /** @inheritdoc */ + public function getOtherManagementBodies(): ?string + { + return $this->otherManagementBodies; + } + + /** @inheritdoc */ + public function setOtherManagementBodies(string $otherManagementBodies): self + { + $this->otherManagementBodies = $otherManagementBodies; + return $this; + } + + /** @inheritdoc */ + public function getAddressLocation(): ?string + { + return $this->addressLocation; + } + + /** @inheritdoc */ + public function setAddressLocation(string $addressLocation): self + { + $this->addressLocation = $addressLocation; + return $this; + } + + /** @inheritdoc */ + public function getBirthDate(): ?string + { + return $this->birthDate; + } + + /** @inheritdoc */ + public function setBirthDate(string $birthDate): self + { + $this->birthDate = $birthDate; + return $this; + } + + /** @inheritdoc */ + public function getBirthPlace(): ?string + { + return $this->birthPlace; + } + + /** @inheritdoc */ + public function setBirthPlace(string $birthPlace): self + { + $this->birthPlace = $birthPlace; + return $this; + } + + /** @inheritdoc */ + public function getIdentityDoc(): ?QstSchemaIdentityDocInterface + { + return $this->identityDoc; + } + + /** @inheritdoc */ + public function setIdentityDoc(QstSchemaIdentityDocInterface $identityDoc): self + { + $this->identityDoc = $identityDoc; + return $this; + } + + /** @inheritdoc */ + public function getBankAccounts(): array + { + return $this->bankAccounts; + } + + /** @inheritdoc */ + public function addBankAccount(QstSchemaBankAccountInterface $bankAccount): self + { + $this->bankAccounts[] = $bankAccount; + return $this; + } + + /** @inheritdoc */ + public function getLicense(): ?string + { + return $this->license; + } + + /** @inheritdoc */ + public function setLicense(string $license): self + { + $this->license = $license; + return $this; + } + + /** @inheritdoc */ + public function getActionInFavor(): ?string + { + return $this->actionInFavor; + } + + /** @inheritdoc */ + public function setActionInFavor(string $actionInFavor): self + { + $this->actionInFavor = $actionInFavor; + return $this; + } + + /** @inheritdoc */ + public function getCommission(): ?string + { + return $this->commission; + } + + /** @inheritdoc */ + public function setCommission(string $commission): self + { + $this->commission = $commission; + return $this; + } + + /** @inheritdoc */ + public function getAdditionalFields(): array + { + return $this->additionalFields; + } + + /** @inheritdoc */ + public function getAdditionalFieldByKey(int $key): ?string + { + return $this->additionalFields[$key] ?? null; + } + + /** @inheritdoc */ + public function setAdditionalFieldByKey(int $key, string $value): self + { + $this->additionalFields[$key] = $value; + + return $this; + } + + /** @inheritdoc */ + public function toArray(): array + { + $array = [ + 'foreignName' => $this->getForeignName(), + 'phones' => $this->getPhones(), + 'emails' => $this->getEmails(), + 'legalAddress' => $this->getLegalAddress()->toArray(), + 'postAddress' => $this->getPostAddress() ? $this->getPostAddress()->toArray() : null, + 'actualAddress' => $this->getActualAddress()->toArray(), + 'ceo' => $this->getCeo() ? $this->getCeo()->toArray() : null, + 'owners' => + !empty($this->getOwners()) + ? array_map(static fn (QstSchemaOwnerInterface $owner) => $owner->toArray(), $this->getOwners()) + : null, + 'boardOfDirectors' => $this->getBoardOfDirectors(), + 'managementBoard' => $this->getManagementBoard(), + 'otherManagementBodies' => $this->getOtherManagementBodies(), + 'addressLocation' => $this->getAddressLocation(), + 'birthDate' => $this->getBirthDate(), + 'birthPlace' => $this->getBirthPlace(), + 'identityDoc' => $this->getIdentityDoc() ? $this->getIdentityDoc()->toArray() : null, + 'bankAccounts' => array_map( + static fn (QstSchemaBankAccountInterface $bankAccount) => ['bankAccount' => $bankAccount->toArray()], + $this->getBankAccounts() + ), + 'license' => $this->getLicense(), + 'actionInFavor' => $this->getActionInFavor(), + 'commission' => $this->getCommission(), + ]; + + foreach ($this->additionalFields as $key => $value) { + $array['additionalField' . $key] = $value; + } + + return array_filter($array, static fn ($value) => $value !== null); + } +} diff --git a/src/QstSchemaActualAddress.php b/src/QstSchemaActualAddress.php new file mode 100644 index 0000000..5331a78 --- /dev/null +++ b/src/QstSchemaActualAddress.php @@ -0,0 +1,27 @@ +isChecked()) { + return [ + 'isEqualToLegalAddress' => true + ]; + } + + return parent::toArray(); + } +} diff --git a/src/QstSchemaAddressAbstract.php b/src/QstSchemaAddressAbstract.php new file mode 100644 index 0000000..65b93d0 --- /dev/null +++ b/src/QstSchemaAddressAbstract.php @@ -0,0 +1,111 @@ +zip; + } + + /** @inheritdoc */ + public function setZip(string $zip): self + { + $this->zip = $zip; + return $this; + } + + /** @inheritdoc */ + public function getRegion(): string + { + return $this->region; + } + + /** @inheritdoc */ + public function setRegion(string $region): self + { + $this->region = $region; + return $this; + } + + /** @inheritdoc */ + public function getCity(): string + { + return $this->city; + } + + /** @inheritdoc */ + public function setCity(string $city): self + { + $this->city = $city; + return $this; + } + + /** @inheritdoc */ + public function getStreet(): string + { + return $this->street; + } + + /** @inheritdoc */ + public function setStreet(string $street): self + { + $this->street = $street; + return $this; + } + + /** @inheritdoc */ + public function getHouse(): string + { + return $this->house; + } + + /** @inheritdoc */ + public function setHouse(string $house): self + { + $this->house = $house; + return $this; + } + + /** @inheritdoc */ + public function getFlat(): ?string + { + return $this->flat; + } + + /** @inheritdoc */ + public function setFlat(string $flat): self + { + $this->flat = $flat; + return $this; + } + + /** @inheritdoc */ + public function toArray(): ?array + { + $array = [ + 'zip' => $this->getZip(), + 'region' => $this->getRegion(), + 'city' => $this->getCity(), + 'street' => $this->getStreet(), + 'house' => $this->getHouse(), + 'flat' => $this->getFlat() + ]; + + return array_filter($array, static fn ($value) => $value !== null); + } +} diff --git a/src/QstSchemaAddressInterface.php b/src/QstSchemaAddressInterface.php new file mode 100644 index 0000000..bfbe68d --- /dev/null +++ b/src/QstSchemaAddressInterface.php @@ -0,0 +1,86 @@ +bankBIK; + } + + /** @inheritdoc */ + public function setBankBIK(string $bankBIK): self + { + $this->bankBIK = $bankBIK; + return $this; + } + + /** @inheritdoc */ + public function getBankCorAccount(): string + { + return $this->bankCorAccount; + } + + /** @inheritdoc */ + public function setBankCorAccount(string $bankCorAccount): self + { + $this->bankCorAccount = $bankCorAccount; + return $this; + } + + /** @inheritdoc */ + public function getBankAccount(): string + { + return $this->bankAccount; + } + + /** @inheritdoc */ + public function setBankAccount(string $bankAccount): self + { + $this->bankAccount = $bankAccount; + return $this; + } + + /** @inheritdoc */ + public function toArray(): array + { + return [ + 'bankBIK' => $this->getBankBIK(), + 'bankCorAccount' => $this->getBankCorAccount(), + 'bankAccount' => $this->getBankAccount() + ]; + } +} \ No newline at end of file diff --git a/src/QstSchemaBankAccountInterface.php b/src/QstSchemaBankAccountInterface.php new file mode 100644 index 0000000..7725f42 --- /dev/null +++ b/src/QstSchemaBankAccountInterface.php @@ -0,0 +1,47 @@ +citizenship; + } + + /** @inheritdoc */ + public function setCitizenship(string $citizenship): self + { + $this->citizenship = $citizenship; + return $this; + } + + /** @inheritdoc */ + public function getBirthDate(): string + { + return $this->birthDate; + } + + /** @inheritdoc */ + public function setBirthDate(string $birthDate): self + { + $this->birthDate = $birthDate; + return $this; + } + + /** @inheritdoc */ + public function getBirthPlace(): string + { + return $this->birthPlace; + } + + /** @inheritdoc */ + public function setBirthPlace(string $birthPlace): self + { + $this->birthPlace = $birthPlace; + return $this; + } + + /** @inheritdoc */ + public function getIdentityDoc(): QstSchemaIdentityDocInterface + { + return $this->identityDoc; + } + + /** @inheritdoc */ + public function setIdentityDoc(QstSchemaIdentityDocInterface $identityDoc): self + { + $this->identityDoc = $identityDoc; + return $this; + } + + /** @inheritdoc */ + public function getRegistrationAddress(): string + { + return $this->registrationAddress; + } + + /** @inheritdoc */ + public function setRegistrationAddress(string $registrationAddress): self + { + $this->registrationAddress = $registrationAddress; + return $this; + } + + /** @inheritdoc */ + public function toArray(): ?array + { + $array = [ + 'citizenship' => $this->getCitizenship(), + 'birthDate' => $this->getBirthDate(), + 'birthPlace' => $this->getBirthPlace(), + 'identityDoc' => $this->getIdentityDoc()->toArray(), + 'registrationAddress' => $this->getRegistrationAddress() + ]; + + return array_filter($array, static fn ($value) => $value !== null); + } +} \ No newline at end of file diff --git a/src/QstSchemaCeoInterface.php b/src/QstSchemaCeoInterface.php new file mode 100644 index 0000000..2f44d30 --- /dev/null +++ b/src/QstSchemaCeoInterface.php @@ -0,0 +1,73 @@ +checked; + } + + /** + * Установить свойство isChecked для поля в анкете + * @param bool|null $checked + * @return $this + */ + public function setChecked(bool $checked) + { + $this->checked = $checked; + return $this; + } +} diff --git a/src/QstSchemaIdentityDoc.php b/src/QstSchemaIdentityDoc.php new file mode 100644 index 0000000..cb57624 --- /dev/null +++ b/src/QstSchemaIdentityDoc.php @@ -0,0 +1,116 @@ + 'PASSPORT', + 'other' => 'OTHER' + ]; + + private ?string $type = null; + private string $series; + private string $number; + private string $issueDate; + private string $issuedBy; + private string $issuedByKP; + + /** @inheritdoc */ + public function getType(): ?string + { + return $this->type; + } + + /** @inheritdoc */ + public function setType(string $type): self + { + $this->type = $type; + return $this; + } + + /** @inheritdoc */ + public function getSeries(): string + { + return $this->series; + } + + /** @inheritdoc */ + public function setSeries(string $series): self + { + $this->series = $series; + return $this; + } + + /** @inheritdoc */ + public function getNumber(): string + { + return $this->number; + } + + /** @inheritdoc */ + public function setNumber(string $number): self + { + $this->number = $number; + return $this; + } + + /** @inheritdoc */ + public function getIssueDate(): string + { + return $this->issueDate; + } + + /** @inheritdoc */ + public function setIssueDate(string $issueDate): self + { + $this->issueDate = $issueDate; + return $this; + } + + /** @inheritdoc */ + public function getIssuedBy(): string + { + return $this->issuedBy; + } + + /** @inheritdoc */ + public function setIssuedBy(string $issuedBy): self + { + $this->issuedBy = $issuedBy; + return $this; + } + + /** @inheritdoc */ + public function getIssuedByKP(): string + { + return $this->issuedByKP; + } + + /** @inheritdoc */ + public function setIssuedByKP(string $issuedByKP): self + { + $this->issuedByKP = $issuedByKP; + return $this; + } + + /** @inheritdoc */ + public function toArray(): ?array + { + $array = [ + 'type' => $this->getType(), + 'series' => $this->getSeries(), + 'number' => $this->getNumber(), + 'issueDate' => $this->getIssueDate(), + 'issuedBy' => $this->getIssuedBy(), + 'issuedByKP' => $this->getIssuedByKP() + ]; + + return array_filter($array, static fn ($value) => $value !== null); + } +} \ No newline at end of file diff --git a/src/QstSchemaIdentityDocInterface.php b/src/QstSchemaIdentityDocInterface.php new file mode 100644 index 0000000..aa4da1d --- /dev/null +++ b/src/QstSchemaIdentityDocInterface.php @@ -0,0 +1,90 @@ +owner; + } + + /** @inheritdoc */ + public function setOwner(string $owner): self + { + $this->owner = $owner; + return $this; + } + + /** @inheritdoc */ + public function getShare(): string + { + return $this->share; + } + + /** @inheritdoc */ + public function setShare(string $share): self + { + $this->share = $share; + return $this; + } + + /** @inheritdoc */ + public function toArray(): array + { + return [ + 'owner' => $this->getOwner(), + 'share' => $this->getShare(), + ]; + } +} \ No newline at end of file diff --git a/src/QstSchemaOwnerInterface.php b/src/QstSchemaOwnerInterface.php new file mode 100644 index 0000000..7423f1a --- /dev/null +++ b/src/QstSchemaOwnerInterface.php @@ -0,0 +1,34 @@ +isChecked()) { + return [ + 'isEqualToLegalAddress' => true + ]; + } + + return parent::toArray(); + } +} diff --git a/src/QstToArrayInterface.php b/src/QstToArrayInterface.php new file mode 100644 index 0000000..9aa88f4 --- /dev/null +++ b/src/QstToArrayInterface.php @@ -0,0 +1,13 @@ +payuPaymentReference = $paymentIdString; @@ -62,7 +70,7 @@ public function setPayuPaymentReference(string $paymentIdString): RefundInterfac /** * @inheritDoc */ - public function getPayuPaymentReference(): string + public function getYpmnPaymentReference(): string { return $this->payuPaymentReference; } @@ -126,19 +134,95 @@ public function getCurrency(): string return $this->currency; } + /** + * @return bool + */ + public function isSkipCheckSkuAmount(): bool + { + return $this->skipCheckSkuAmount; + } + + /** + * @param bool $skipCheckSkuAmount + */ + public function setSkipCheckSkuAmount(bool $skipCheckSkuAmount): self + { + $this->skipCheckSkuAmount = $skipCheckSkuAmount; + + return $this; + } + + /** @inheritDoc */ + public function addProduct(ProductInterface $product) : self + { + $this->products[] = $product; + + return $this; + } + + /** @inheritDoc */ + public function getProducts(): array + { + return $this->products; + } + + /** @inheritDoc */ + public function getProductsArray(): array + { + $productsArray = []; + foreach ($this->getProducts() as $product) { + $productsArray[] = [ + 'sku' => $product->getSku(), + 'quantity' => $product->getQuantity(), + 'amount' => $product->getAmount(), + ]; + } + + return $productsArray; + } + /** * @inheritDoc + * @throws PaymentException */ + public function addMarketPlaceSubmerchant(string $merchantCode, float $amount): self + { + $this->marketplaceSubmerchants[$merchantCode] = new MarketplaceSubmerchant($merchantCode, $amount); + + return $this; + } + + /** + * @inheritDoc + */ + #[\ReturnTypeWillChange] public function jsonSerialize() { //TODO: проверка необходимых параметров $requestData = [ - 'payuPaymentReference' => $this->getPayuPaymentReference(), + 'payuPaymentReference' => $this->getYpmnPaymentReference(), 'originalAmount' => $this->getOriginalAmount(), 'amount' => $this->getAmount(), 'currency' => $this->getCurrency() ]; + if ($this->skipCheckSkuAmount) { + $requestData ['skipCheckSkuAmount'] = true; + } + + if (isset($this->products) && count($this->products) > 0) { + $requestData['products'] = $this->getProductsArray(); + } + + if (count($this->marketplaceSubmerchants) > 0) { + foreach ($this->marketplaceSubmerchants as $marketplaceSubmerchant) { + $requestData['marketplaceV1'][] = (object) [ + 'amount' => $marketplaceSubmerchant->getAmount(), + 'merchant' => $marketplaceSubmerchant->getMerchantCode(), + ]; + } + } + return json_encode($requestData, JSON_UNESCAPED_SLASHES|JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_LINE_TERMINATORS); } -} \ No newline at end of file +} diff --git a/src/RefundInterface.php b/src/RefundInterface.php index be3f0d7..4a748ec 100644 --- a/src/RefundInterface.php +++ b/src/RefundInterface.php @@ -1,6 +1,6 @@ lifetimeMinutes = $lifetimeMinutes; + } + + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return json_encode( + [ + 'lifetimeMinutes' => $this->lifetimeMinutes, + ] + ); + } +} diff --git a/src/Std.php b/src/Std.php index b6c6be4..bd5143a 100644 --- a/src/Std.php +++ b/src/Std.php @@ -1,6 +1,6 @@ ' . $params['text'] . ' - +
'; } @@ -64,10 +64,14 @@ public static function removeNullValues(array $array) : array * @return string * @throws PaymentException */ - public static function drawPayuButton(array $params): string + public static function drawYpmnButton(array $params): string { if (!isset($params['url'])) { - throw new PaymentException('Передайте в метод drawPayuButton параметр url'); + throw new PaymentException('Передайте в метод drawYpmnButton параметр url'); + } + + if (!isset($params['sum'])) { + throw new PaymentException('Передайте в метод drawYpmnButton параметр sum'); } $allowedParams = [ @@ -86,38 +90,218 @@ public static function drawPayuButton(array $params): string } return ' - + + +
+ Оплатить
+ ' . number_format($params['sum'], 2, '.', ' ') . ' '. ( isset($params['currency']) ? htmlspecialchars($params['currency']) : '₽' ) . ' +
+ +
+ + +
+ + '; } + + /** + * Транслитерация + * @param $string + * @return string + */ + public static function rus2translit(string $string): string { + $converter = [ + 'а' => 'a', 'б' => 'b', 'в' => 'v', + 'г' => 'g', 'д' => 'd', 'е' => 'e', + 'ё' => 'e', 'ж' => 'zh', 'з' => 'z', + 'и' => 'i', 'й' => 'y', 'к' => 'k', + 'л' => 'l', 'м' => 'm', 'н' => 'n', + 'о' => 'o', 'п' => 'p', 'р' => 'r', + 'с' => 's', 'т' => 't', 'у' => 'u', + 'ф' => 'f', 'х' => 'h', 'ц' => 'c', + 'ч' => 'ch', 'ш' => 'sh', 'щ' => 'sch', + 'ь' => '\'', 'ы' => 'y', 'ъ' => '\'', + 'э' => 'e', 'ю' => 'yu', 'я' => 'ya', + 'А' => 'A', 'Б' => 'B', 'В' => 'V', + 'Г' => 'G', 'Д' => 'D', 'Е' => 'E', + 'Ё' => 'E', 'Ж' => 'Zh', 'З' => 'Z', + 'И' => 'I', 'Й' => 'Y', 'К' => 'K', + 'Л' => 'L', 'М' => 'M', 'Н' => 'N', + 'О' => 'O', 'П' => 'P', 'Р' => 'R', + 'С' => 'S', 'Т' => 'T', 'У' => 'U', + 'Ф' => 'F', 'Х' => 'H', 'Ц' => 'C', + 'Ч' => 'Ch', 'Ш' => 'Sh', 'Щ' => 'Sch', + 'Ь' => '\'', 'Ы' => 'Y', 'Ъ' => '\'', + 'Э' => 'E', 'Ю' => 'Yu', 'Я' => 'Ya', + ]; + + return strtr($string, $converter); + } + + /** + * Преобразование кириллических имён в URI slugs + * Удобно для продуктов, категорий и т.д. + * @param string $str + * @return string + */ + public static function str2uri(string $str) : string { + // Спецсимволы + $str = str_replace( '(', '', $str); + $str = str_replace( '(', '', $str); + $str = str_replace( "'", '', $str); + $str = str_replace( ':', '', $str); + $str = str_replace( 'Ø', '', $str); + $str = str_replace( '@', '', $str); + $str = str_replace( '«', '', $str); + $str = str_replace( '»', '', $str); + + // переводим в транслит + $str = self::rus2translit($str); + + // в нижний регистр + $str = strtolower($str); + + // заменям все ненужное нам на "-" + $str = preg_replace('~[^-a-z0-9_]+~u', '-', $str); + + // удаляем начальные и конечные '-' + $str = trim($str, "-"); + + // тут немного частных случаев для клипов + $str = str_replace( '----', '-', $str); + $str = str_replace( '---', '-', $str); + $str = str_replace( '--', '-', $str); + + return $str; + } + + /** + * Транслитерация JSON + * @param $json_str + * @return array|string|string[] + */ + public static function json_fix_cyr($json_str) { + $cyr_chars = [ + '\u0430' => 'а', '\u0410' => 'А', + '\u0431' => 'б', '\u0411' => 'Б', + '\u0432' => 'в', '\u0412' => 'В', + '\u0433' => 'г', '\u0413' => 'Г', + '\u0434' => 'д', '\u0414' => 'Д', + '\u0435' => 'е', '\u0415' => 'Е', + '\u0451' => 'ё', '\u0401' => 'Ё', + '\u0436' => 'ж', '\u0416' => 'Ж', + '\u0437' => 'з', '\u0417' => 'З', + '\u0438' => 'и', '\u0418' => 'И', + '\u0439' => 'й', '\u0419' => 'Й', + '\u043a' => 'к', '\u041a' => 'К', + '\u043b' => 'л', '\u041b' => 'Л', + '\u043c' => 'м', '\u041c' => 'М', + '\u043d' => 'н', '\u041d' => 'Н', + '\u043e' => 'о', '\u041e' => 'О', + '\u043f' => 'п', '\u041f' => 'П', + '\u0440' => 'р', '\u0420' => 'Р', + '\u0441' => 'с', '\u0421' => 'С', + '\u0442' => 'т', '\u0422' => 'Т', + '\u0443' => 'у', '\u0423' => 'У', + '\u0444' => 'ф', '\u0424' => 'Ф', + '\u0445' => 'х', '\u0425' => 'Х', + '\u0446' => 'ц', '\u0426' => 'Ц', + '\u0447' => 'ч', '\u0427' => 'Ч', + '\u0448' => 'ш', '\u0428' => 'Ш', + '\u0449' => 'щ', '\u0429' => 'Щ', + '\u044a' => 'ъ', '\u042a' => 'Ъ', + '\u044b' => 'ы', '\u042b' => 'Ы', + '\u044c' => 'ь', '\u042c' => 'Ь', + '\u044d' => 'э', '\u042d' => 'Э', + '\u044e' => 'ю', '\u042e' => 'Ю', + '\u044f' => 'я', '\u042f' => 'Я', + ]; + + foreach ($cyr_chars as $cyr_char_key => $cyr_char) { + $json_str = str_replace($cyr_char_key, $cyr_char, $json_str); + } + return $json_str; + } } diff --git a/src/StoredCredentials.php b/src/StoredCredentials.php index 3e14485..960caf0 100644 --- a/src/StoredCredentials.php +++ b/src/StoredCredentials.php @@ -1,6 +1,6 @@ useId = $useId; return $this; } + + /** @inheritDoc */ + public function getConsentType(): string + { + return $this->consentType; + } + + /** @inheritDoc */ + public function setConsentType(string $consentType): self + { + $this->consentType = $consentType; + return $this; + } + + /** @inheritDoc */ + public function getSubscriptionPurpose(): string + { + return $this->subscriptionPurpose ?? "For future use"; + } + + /** @inheritDoc */ + public function setSubscriptionPurpose(string $subscriptionPurpose): self + { + $this->subscriptionPurpose = $subscriptionPurpose; + return $this; + } + + /** @inheritDoc */ + public function arraySerialize(): array + { + $returnArray = []; + + if (empty($this->useType) === false) { + $returnArray["useType"] = $this->useType; + } + + if (empty($this->useId) === false) { + $returnArray["useId"] = $this->useId; + } + + if (empty($this->consentType) === false) { + $returnArray["consentType"] = $this->consentType; + } + + if (empty($this->subscriptionPurpose) === false) { + $returnArray["subscriptionPurpose"] = $this->subscriptionPurpose; + } + + return $returnArray; + } } diff --git a/src/StoredCredentialsInterface.php b/src/StoredCredentialsInterface.php index 4babb1a..8519e6d 100644 --- a/src/StoredCredentialsInterface.php +++ b/src/StoredCredentialsInterface.php @@ -1,6 +1,6 @@ orderData = new OrderData; $this->orderData->setOrderDate($request['orderData']['orderDate']); - $this->orderData->setPayuPaymentReference($request['orderData']['payuPaymentReference']); + $this->orderData->setYpmnPaymentReference($request['orderData']['ypmnPaymentReference']); $this->orderData->setMerchantPaymentReference($request['orderData']['merchantPaymentReference']); $this->orderData->setStatus($request['orderData']['status']); $this->orderData->setCurrency($request['orderData']['currency']); diff --git a/src/WebhookInterface.php b/src/WebhookInterface.php index 6c98c61..0588196 100644 --- a/src/WebhookInterface.php +++ b/src/WebhookInterface.php @@ -1,11 +1,11 @@