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/.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/README.md b/README.md index f8f8d4b..e5abb5b 100644 --- a/README.md +++ b/README.md @@ -1,378 +1,55 @@ -# php-payu4 -Примеры использования PayU API v4. - -PayU - многофункциональная платёжная система, поддерживающая не только простые платежи с банковских карт, но и множество -форм оплаты, а также подписки и выплаты на карты. - -Данный репозиторий написан по принципам SOLID, и каждый программный интерфейс снабжен подробной документацией на -русском языке. - -Репозиторий также содержит примеры по принципу "одна строка кода - одна строка документации". - -Репозиторий опубликован в виде [пакета Composer](https://packagist.org/packages/payuru/php-payu4) и может использоваться со всеми современными -фреймворками: Laravel, Symfony, Yii и другими. - -Для работы рекомендуется использовать любую современную IDE (VS Code, Intellij Idea/PHPStorm, -Eclipse, Netbeans, etc), чтобы получать подробные подсказки прямо во время редактирования кода. -![IDE screenshot](screenshot.jpg "IDE screenshot") +# Твои Платежи, интеграция на PHP +PHP SDK, готовый клиент для нашего API + примеры использования платёжной системы +![](https://repository-images.githubusercontent.com/638835276/ff494b04-d65b-4843-8759-e85c689a7e80) + +Эта библиотека содержит подробные [примеры](src/Examples/) с комментариями на русском языке +и предназначена для быстрой интеграции. Подходит для сайтов, платформ и приложений. -## Требования -Актуальные требования для использования пакета можно посмотреть -в файле [composer.json](https://github.com/payuru/php-payu4/blob/main/composer.json) -в секции "require" +Репозиторий опубликован в виде [пакета Composer](https://packagist.org/packages/yourpayments/php-api-client) и может +использоваться с любыми фреймворками и CMS. + +Требования: [PHP 7.4 и выше](https://github.com/yourpayments/php-api-client/blob/main/composer.json) ## Установка ### Composer -[Composer](https://getcomposer.org/) - это инструмент для управления зависимостями в PHP. Он позволяет вам объявить -библиотеки, от которых зависит ваш проект, и он будет управлять ими (устанавливать/обновлять) за вас. ```shell -composer require payuru/php-payu4 +$ composer require yourpayments/php-api-client ``` ```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; + 'Заказ №' . $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, -]); - -// Опишем Биллинговую (платёжную) информацию -$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); -``` -### Отмена платежа (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); -``` +Клонируйте или скачайте, а затем подключите ([require](https://www.php.net/manual/ru/function.require.php)) файлы этого репозитория. + +## Документация: Примеры + комментарии +1. [Начало работы (настройка интеграции)](src/Examples/start.php) +2. [Cамый простой платёж](src/Examples/simpleGetPaymentLink.php) +3. [Подробный платёж](src/Examples/getPaymentLink.php) +4. [Платёж со сплитом](src/Examples/getPaymentLinkMarketplace.php) +5. [Токенизация карты (чтобы запомнить карту клиента и не вводить повторно)](src/Examples/getToken.php) +6. [Оплата токеном](src/Examples/paymentByToken.php) +7. [Списание средств](src/Examples/paymentCapture.php) +8. [Возврат средств](src/Examples/paymentRefund.php) +9. [Возврат средств со сплитом](src/Examples/paymentRefundMarketplace.php) +10. [Проверка статуса платежа](src/Examples/paymentGetStatus.php) +11. [Выплаты на банковские карты](src/Examples/payoutCreate.php) +12. [Создание сессии](src/Examples/getSession.php) +13. [Оплата одноразовым токеном](src/Examples/oneTimeTokenPayment.php) +14. [Страница после оплаты](src/Examples/returnPage.php) +15. [Безопасные поля (Secure fields)](src/Examples/secureFields.php) +16. [Запрос отчёта в формате Json](src/Examples/getReportGeneral.php) +17. [Запрос отчёта в виде графика](src/Examples/getReportChart.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://dev.payu.ru/ru/documents/rest-api/testing/#menu-2) +- [Задать вопрос или сообщить о проблеме](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/example.php b/example.php index b9aeee5..1e72202 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': + require './src/Examples/start.php'; + @include './src/Examples/'.$_GET['function'] . '__prepend.php'; + require './src/Examples/'.$_GET['function'] . '.php'; break; default: @@ -342,5 +62,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..097611d --- /dev/null +++ b/example_list.php @@ -0,0 +1,115 @@ + [ + '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' => '', + ], + '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' => '', + ], +]; 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; } + public function getHost() : string + { + if ($this->localModeIsOn) { + return self::LOCAL_HOST; + } else { + return ($this->getSandboxMode() ? self::SANDBOX_HOST : self::HOST); + } + } + + /** @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', 'orderStatus', 'timeStamp'); + $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; + } + + 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,7 +197,7 @@ private function sendGetRequest(string $api): array 'X-Header-Signature:' . $this->getSignature( $this->merchant, $date, - $urlToPostTo, + $this->getHost() . $api, $requestHttpVerb, md5(''), ) @@ -74,6 +210,45 @@ private function sendGetRequest(string $api): array $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)); + } else { + $this->echoDebugMessage($response); + } + + 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]; } @@ -81,7 +256,7 @@ private function sendGetRequest(string $api): array * Отправка POST-запроса * @param JsonSerializable $data запрос * @param string $api адрес API (URI) - * @return array ответ сервера PayU + * @return array ответ сервера Ypmn * @throws PaymentException */ private function sendPostRequest(JsonSerializable $data, string $api): array @@ -92,11 +267,10 @@ private function sendPostRequest(JsonSerializable $data, string $api): array $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, + CURLOPT_URL => $this->getHost() . $api, CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => '', CURLOPT_MAXREDIRS => 10, @@ -112,7 +286,7 @@ private function sendPostRequest(JsonSerializable $data, string $api): array 'X-Header-Signature:' . $this->getSignature( $this->merchant, $date, - $urlToPostTo, + $this->getHost() . $api, $requestHttpVerb, $encodedJsonDataHash ) @@ -124,26 +298,39 @@ private function sendPostRequest(JsonSerializable $data, string $api): array curl_close($curl); if (true === $this->getDebugMode()) { - $this->echoDebugMessage('Запрос к серверу PayU:'); + $this->echoDebugMessage('POST-Запрос к серверу Ypmn:'); $this->echoDebugMessage($encodedJsonData); - $this->echoDebugMessage('Ответ от сервера PayU:'); + $this->echoDebugMessage('Ответ от сервера Ypmn:'); $this->echoDebugMessage(json_encode(json_decode($response), JSON_PRETTY_PRINT)); 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 +338,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 +374,65 @@ 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)); } /** @@ -210,7 +467,28 @@ public function getSandboxMode(): bool /** @inheritdoc */ public function setSandboxMode(bool $sandboxModeIsOn = true): self { + if ($sandboxModeIsOn) { + $this->setLocalMode(false); + } $this->sandboxModeIsOn = $sandboxModeIsOn; + + 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; + return $this; } @@ -227,6 +505,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 +539,5 @@ class="w-100 d-block" >'.print_r($mixedInput, true).''; } } + } diff --git a/src/ApiRequestInterface.php b/src/ApiRequestInterface.php index 8f74a30..8376cbb 100644 --- a/src/ApiRequestInterface.php +++ b/src/ApiRequestInterface.php @@ -1,6 +1,6 @@ setPaymentMethod($paymentMethodType); - $this->setUsePaymentPage($isUsed); + $this->setUsePaymentPage($isPaymentPageUsed); + echo 'конструирую'; } /** @@ -83,13 +92,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 +115,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 +139,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 +167,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 @@ -setName('Синий Квадрат'); +// Установим Артикул +$product1->setSku('ball-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->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'); + +// Создадим авторизацию по типу платежа +$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/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..cfc8250 --- /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/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/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; @@ -76,4 +76,20 @@ public function toArray() : array return $resultArray; } -} \ No newline at end of file + + /** + * @return mixed + */ + public function jsonSerialize() + { + if(is_null($this->tokenHash)){ + throw new PaymentException("Не хватает токена"); + } + + $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..c13e67e 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 @@ 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/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 @@ -payuPaymentReference = $paymentIdString; @@ -62,7 +65,7 @@ public function setPayuPaymentReference(string $paymentIdString): RefundInterfac /** * @inheritDoc */ - public function getPayuPaymentReference(): string + public function getYpmnPaymentReference(): string { return $this->payuPaymentReference; } @@ -128,17 +131,38 @@ public function getCurrency(): string /** * @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 (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..7e715c5 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,181 @@ 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) { + $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); + } + + /** + * Транслитерация 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..c52eb65 100644 --- a/src/StoredCredentials.php +++ b/src/StoredCredentials.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 @@