diff --git a/CHANGELOG.md b/CHANGELOG.md index c1a0eae..aaab02a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### v2.1.0 от 21.04.2021 +* Добавлены примеры работы с SDK + ### v2.0.1 от 03.12.2020 * Поправлен setup diff --git a/README.en.md b/README.en.md index 9163770..ed67490 100644 --- a/README.en.md +++ b/README.en.md @@ -88,3 +88,31 @@ Configuration.configure_user_agent( 3. Call the required API method. [More details in our documentation for the YooKassa API](https://yookassa.ru/en/developers/api) +## Examples of using the API SDK + +#### [YooKassa SDK Settings](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md) +* [Authentication](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Аутентификация) +* [Statistics about the environment used](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Статистические-данные-об-используемом-окружении) +* [Getting information about the store](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Получение-информации-о-магазине) +* [Working with Webhook](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Работа-с-Webhook) +* [Notifications](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Входящие-уведомления) + +#### [Working with payments](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md) +* [Request to create a payment](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-создание-платежа) +* [Request to create a payment via the builder](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-создание-платежа-через-билдер) +* [Request for partial payment confirmation](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-частичное-подтверждение-платежа) +* [Request to cancel an incomplete payment](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-отмену-незавершенного-платежа) +* [Get payment information](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Получить-информацию-о-платеже) +* [Get a list of payments with filtering](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Получить-список-платежей-с-фильтрацией) + +#### [Working with refunds](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md) +* [Request to create a refund](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Запрос-на-создание-возврата) +* [Request to create a refund via the builder](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Запрос-на-создание-возврата-через-билдер) +* [Get refund information](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Получить-информацию-о-возврате) +* [Get a list of returns with filtering](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Получить-список-возвратов-с-фильтрацией) + +#### [Working with receipts](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md) +* [Request to create a receipt](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Запрос-на-создание-чека) +* [Request to create a receipt via the builder](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Запрос-на-создание-чека-через-билдер) +* [Get information about the receipt](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Получить-информацию-о-чеке) +* [Get a list of receipts with filtering](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Получить-список-чеков-с-фильтрацией) diff --git a/README.md b/README.md index 97dc6b3..6d5d42d 100644 --- a/README.md +++ b/README.md @@ -86,4 +86,31 @@ Configuration.configure_user_agent( 3. Вызовите нужный метод API. [Подробнее в документации к API ЮKassa](https://yookassa.ru/developers/api) - +## Примеры использования SDK + +#### [Настройки SDK API ЮKassa](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md) +* [Аутентификация](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Аутентификация) +* [Статистические данные об используемом окружении](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Статистические-данные-об-используемом-окружении) +* [Получение информации о магазине](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Получение-информации-о-магазине) +* [Работа с Webhook](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Работа-с-Webhook) +* [Входящие уведомления](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/01-configuration.md#Входящие-уведомления) + +#### [Работа с платежами](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md) +* [Запрос на создание платежа](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-создание-платежа) +* [Запрос на создание платежа через билдер](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-создание-платежа-через-билдер) +* [Запрос на частичное подтверждение платежа](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-частичное-подтверждение-платежа) +* [Запрос на отмену незавершенного платежа](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Запрос-на-отмену-незавершенного-платежа) +* [Получить информацию о платеже](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Получить-информацию-о-платеже) +* [Получить список платежей с фильтрацией](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/02-payments.md#Получить-список-платежей-с-фильтрацией) + +#### [Работа с возвратами](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md) +* [Запрос на создание возврата](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Запрос-на-создание-возврата) +* [Запрос на создание возврата через билдер](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Запрос-на-создание-возврата-через-билдер) +* [Получить информацию о возврате](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Получить-информацию-о-возврате) +* [Получить список возвратов с фильтрацией](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/03-refunds.md#Получить-список-возвратов-с-фильтрацией) + +#### [Работа с чеками](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md) +* [Запрос на создание чека](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Запрос-на-создание-чека) +* [Запрос на создание чека через билдер](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Запрос-на-создание-чека-через-билдер) +* [Получить информацию о чеке](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Получить-информацию-о-чеке) +* [Получить список чеков с фильтрацией](https://github.com/yoomoney/yookassa-sdk-python/blob/master/docs/examples/04-receipts.md#Получить-список-чеков-с-фильтрацией) \ No newline at end of file diff --git a/docs/examples/01-configuration.md b/docs/examples/01-configuration.md new file mode 100644 index 0000000..bf24144 --- /dev/null +++ b/docs/examples/01-configuration.md @@ -0,0 +1,218 @@ +## Настройки SDK API ЮKassa + +[Справочник API ЮKassa](https://yookassa.ru/developers/api) + +С помощью этого SDK вы можете работать с онлайн-платежами: отправлять запросы на оплату, +сохранять платежную информацию для повторных списаний, совершать возвраты и многое другое. + +* [Аутентификация](#Аутентификация) +* [Статистические данные об используемом окружении](#Статистические-данные-об-используемом-окружении) +* [Получение информации о магазине](#Получение-информации-о-магазине) +* [Работа с Webhook](#Работа-с-Webhook) +* [Входящие уведомления](#Входящие-уведомления) + +--- + +### Аутентификация + +Для работы с API необходимо прописать в конфигурации данные аутентификации. Существует два способа аутентификации: +- shopId + секретный ключ +- OAuth-токен. [Подробнее в документации к API](https://yookassa.ru/developers/partners-api/basics) + +```python +from yookassa import Configuration +# shopId + секретный ключ +Configuration.configure('XXXXXX', 'test_XXXXXXXX') +# или OAuth-токен +Configuration.configure_auth_token('token-XXXXXXXX') +``` + +--- + +### Статистические данные об используемом окружении + +Для поддержки качества, а также быстром реагировании на ошибки, SDK передает статистику в запросах к API ЮKassa. + +По молчанию, SDK передает в запросах версию операционной системы, версию Python, а также версию SDK. +Но вы можете передать дополнительные данные об используемом фреймворке, CMS, а также модуле в CMS. + +Например, это может выглядеть так: +```python +from yookassa import Configuration +from yookassa.domain.common.user_agent import Version + +Configuration.configure_user_agent( + framework=Version('Django', '2.2.3'), + cms=Version('Wagtail', '2.6.2'), + module=Version('Y.CMS', '0.0.1') +) +``` + +--- + +### Получение информации о магазине + +После установки конфигурации можно проверить корректность данных, а также получить информацию о магазине. + +```python +import var_dump as var_dump +from yookassa import Settings + +me = Settings.get_account_settings() +var_dump.var_dump(me) +``` +В результате мы увидим примерно следующее: +``` +#0 dict(5) + ['account_id'] => str(6) "XXXXXX" + ['test'] => bool(True) + ['fiscalization_enabled'] => bool(True) + ['payment_methods'] => list(2) + [0] => str(9) "yoo_money" + [1] => str(9) "bank_card" + ['status'] => str(7) "enabled" +``` +[Подробнее в документации к API](https://yookassa.ru/developers/api?lang=python#me_object) + +--- + +### Работа с Webhook + +Если вы подключаетесь к API через Oauth-токен, то можете настроить получение уведомлений о смене статуса платежа или возврата. + +Например, ЮKassa может сообщить, когда объект платежа, созданный в вашем приложении, перейдет в статус `waiting_for_capture`. + +В данном примере мы устанавливаем вебхуки для succeeded и canceled уведомлений. +А так же проверяем, не установлены ли уже вебхуки. И если установлены на неверный адрес, удаляем. + +```python +import var_dump as var_dump +from yookassa import Webhook +from yookassa.domain.notification import WebhookNotificationEventType + +whUrl = 'https://merchant-site.ru/payment-notification' +needWebhookList = [ + WebhookNotificationEventType.PAYMENT_SUCCEEDED, + WebhookNotificationEventType.PAYMENT_CANCELED +] + +whList = Webhook.list() + +for event in needWebhookList: + hookIsSet = False + for wh in whList.items: + if wh.event != event: + continue + if wh.url != whUrl: + Webhook.remove(wh.id) + else: + hookIsSet = True + + if not hookIsSet: + Webhook.add({"event": event, "url": whUrl}) + +var_dump.var_dump(Webhook.list()) +``` + +В результате мы увидим примерно следующее: +``` +#0 object(WebhookList) (2) + _WebhookList__items => list(2) + [0] => object(WebhookResponse) (3) + _WebhookResponse__id => str(39) "wh-52e51c6e-29a2-4a0d-800b-01cf022b5613" + _WebhookResponse__event => str(16) "payment.canceled" + _WebhookResponse__url => str(66) "https://merchant-site.ru/payment-notification" + [1] => object(WebhookResponse) (3) + _WebhookResponse__id => str(39) "wh-c331b3ee-fb65-428d-b008-1b837d9c4d93" + _WebhookResponse__event => str(17) "payment.succeeded" + _WebhookResponse__url => str(66) "https://merchant-site.ru/payment-notification" + _WebhookList__type => str(4) "list" +``` +[Подробнее в документации к API](https://yookassa.ru/developers/api?lang=python#webhook) + +### Входящие уведомления + +Если вы хотите отслеживать состояние платежей и возвратов, вы можете подписаться на уведомления ([webhook](#Работа-с-Webhook), callback). + +Уведомления пригодятся в тех случаях, когда объект API изменяется без вашего участия. +Например, если пользователю нужно подтвердить платеж, процесс оплаты может занять от нескольких минут до нескольких часов. +Вместо того чтобы всё это время периодически отправлять GET-запросы, чтобы узнать статус платежа, вы можете просто дожидаться уведомления от ЮKassa. + +[Входящие уведомления в документации](https://yookassa.ru/developers/using-api/webhooks?lang=python) + +#### Использование + +Как только произойдет событие, на которое вы подписались, на URL, который вы указали при настройке, придет уведомление. +В нем будут все данные об объекте на момент, когда произошло событие. + +Вам нужно подтвердить, что вы получили уведомление. Для этого ответьте HTTP-кодом 200. ЮKassa проигнорирует всё, +что будет находиться в теле или заголовках ответа. Ответы с любыми другими HTTP-кодами будут считаться невалидными, +и ЮKassa продолжит доставлять уведомление в течение 24 часов, начиная с момента, когда событие произошло. + +#### Пример обработки уведомления с помощью SDK + +```python +import json +from django.http import HttpResponse +from yookassa import Configuration, Payment +from yookassa.domain.notification import WebhookNotificationEventType, WebhookNotification + +def my_webhook_handler(request): + # Извлечение JSON объекта из тела запроса + event_json = json.loads(request.body) + try: + # Создание объекта класса уведомлений в зависимости от события + notification_object = WebhookNotification(event_json) + response_object = notification_object.object + if notification_object.event == WebhookNotificationEventType.PAYMENT_SUCCEEDED: + some_data = { + 'paymentId': response_object.id, + 'paymentStatus': response_object.status, + } + # Специфичная логика + # ... + elif notification_object.event == WebhookNotificationEventType.PAYMENT_WAITING_FOR_CAPTURE: + some_data = { + 'paymentId': response_object.id, + 'paymentStatus': response_object.status, + } + # Специфичная логика + # ... + elif notification_object.event == WebhookNotificationEventType.PAYMENT_CANCELED: + some_data = { + 'paymentId': response_object.id, + 'paymentStatus': response_object.status, + } + # Специфичная логика + # ... + elif notification_object.event == WebhookNotificationEventType.REFUND_SUCCEEDED: + some_data = { + 'refundId': response_object.id, + 'refundStatus': response_object.status, + 'paymentId': response_object.payment_id, + } + # Специфичная логика + # ... + else: + # Обработка ошибок + return HttpResponse(status=400) # Сообщаем кассе об ошибке + + # Специфичная логика + # ... + Configuration.configure('XXXXXX', 'test_XXXXXXXX') + # Получим актуальную информацию о платеже + payment_info = Payment.find_one(some_data['paymentId']) + if payment_info: + payment_status = payment_info.status + # Специфичная логика + # ... + else: + # Обработка ошибок + return HttpResponse(status=400) # Сообщаем кассе об ошибке + + except Exception: + # Обработка ошибок + return HttpResponse(status=400) # Сообщаем кассе об ошибке + + return HttpResponse(status=200) # Сообщаем кассе, что все хорошо +``` \ No newline at end of file diff --git a/docs/examples/02-payments.md b/docs/examples/02-payments.md new file mode 100644 index 0000000..cc34c50 --- /dev/null +++ b/docs/examples/02-payments.md @@ -0,0 +1,266 @@ +## Работа с платежами + +SDK позволяет создавать, подтверждать, отменять платежи, а также получать информацию о них. + +Объект платежа `PaymentResponse` содержит всю информацию о платеже, актуальную на текущий момент времени. +Он формируется при создании платежа и приходит в ответ на любой запрос, связанный с платежами. + +* [Запрос на создание платежа](#Запрос-на-создание-платежа) +* [Запрос на создание платежа через билдер](#Запрос-на-создание-платежа-через-билдер) +* [Запрос на частичное подтверждение платежа](#Запрос-на-частичное-подтверждение-платежа) +* [Запрос на отмену незавершенного платежа](#Запрос-на-отмену-незавершенного-платежа) +* [Получить информацию о платеже](#Получить-информацию-о-платеже) +* [Получить список платежей с фильтрацией](#Получить-список-платежей-с-фильтрацией) + +--- + +### Запрос на создание платежа + +[Создание платежа в документации](https://yookassa.ru/developers/api?lang=python#create_payment) + +Чтобы принять оплату, необходимо создать объект платежа — `PaymentRequest`. Он содержит всю необходимую информацию +для проведения оплаты (сумму, валюту и статус). У платежа линейный жизненный цикл, +он последовательно переходит из статуса в статус. + +В ответ на запрос придет объект платежа - `PaymentResponse` в актуальном статусе. + +```python +import var_dump as var_dump +from yookassa import Payment + +res = Payment.create( + { + "amount": { + "value": 1000, + "currency": "RUB" + }, + "confirmation": { + "type": "redirect", + "return_url": "https://merchant-site.ru/return_url" + }, + "capture": True, + "description": "Заказ №72", + "metadata": { + 'orderNumber': '72' + }, + "receipt": { + "customer": { + "full_name": "Ivanov Ivan Ivanovich", + "email": "email@email.ru", + "phone": "79211234567", + "inn": "6321341814" + }, + "items": [ + { + "description": "Переносное зарядное устройство Хувей", + "quantity": "1.00", + "amount": { + "value": 1000, + "currency": "RUB" + }, + "vat_code": "2", + "payment_mode": "full_payment", + "payment_subject": "commodity", + "country_of_origin_code": "CN", + "product_code": "44 4D 01 00 21 FA 41 00 23 05 41 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12 00 AB 00", + "customs_declaration_number": "10714040/140917/0090376", + "excise": "20.00", + "supplier": { + "name": "string", + "phone": "string", + "inn": "string" + } + }, + ] + } + } +) + +var_dump.var_dump(res) +``` +--- + +### Запрос на создание платежа через билдер + +[Создание платежа в документации](https://yookassa.ru/developers/api?lang=python#create_payment) + +Билдер позволяет создать объект платежа — `PaymentRequest` программным способом, через объекты. + +```python +import var_dump as var_dump +from yookassa import Payment +from yookassa.domain.models.currency import Currency +from yookassa.domain.models.receipt import Receipt +from yookassa.domain.models.receipt_item import ReceiptItem +from yookassa.domain.common.confirmation_type import ConfirmationType +from yookassa.domain.request.payment_request_builder import PaymentRequestBuilder + +receipt = Receipt() +receipt.customer = {"phone": "79990000000", "email": "test@email.com"} +receipt.tax_system_code = 1 +receipt.items = [ + ReceiptItem({ + "description": "Product 1", + "quantity": 2.0, + "amount": { + "value": 250.0, + "currency": Currency.RUB + }, + "vat_code": 2 + }), + { + "description": "Product 2", + "quantity": 1.0, + "amount": { + "value": 100.0, + "currency": Currency.RUB + }, + "vat_code": 2 + } +] + +builder = PaymentRequestBuilder() +builder.set_amount({"value": 1000, "currency": Currency.RUB}) \ + .set_confirmation({"type": ConfirmationType.REDIRECT, "return_url": "https://merchant-site.ru/return_url"}) \ + .set_capture(False) \ + .set_description("Заказ №72") \ + .set_metadata({"orderNumber": "72"}) \ + .set_receipt(receipt) + +request = builder.build() +# Можно что-то поменять, если нужно +request.client_ip = '1.2.3.4' +res = Payment.create(request) + +var_dump.var_dump(res) +``` +--- + +### Запрос на частичное подтверждение платежа + +[Подтверждение платежа в документации](https://yookassa.ru/developers/api?lang=python#capture_payment) + +Подтверждает вашу готовность принять платеж. После подтверждения платеж перейдет в статус succeeded. +Это значит, что вы можете выдать товар или оказать услугу пользователю. + +Подтвердить можно только платеж в статусе `waiting_for_capture` и только в течение определенного времени +(зависит от способа оплаты). Если вы не подтвердите платеж в отведенное время, он автоматически перейдет +в статус `canceled`, и деньги вернутся пользователю. + +В ответ на запрос придет объект платежа в актуальном статусе. +```python +import var_dump as var_dump +from yookassa import Payment + +payment_id = '21b23b5b-000f-5061-a000-0674e49a8c10' +res = Payment.capture(payment_id, { + "amount": { + "value": "1000.00", + "currency": "RUB" + }, + "transfers": [ + { + "account_id": "123", + "amount": { + "value": "300.00", + "currency": "RUB" + } + }, + { + "account_id": "456", + "amount": { + "value": "700.00", + "currency": "RUB" + } + } + ] +}) + +var_dump.var_dump(res) +``` +[Подробнее о подтверждении и отмене платежей](https://yookassa.ru/developers/payments/payment-process#capture-and-cancel) + +--- + +### Запрос на отмену незавершенного платежа + +[Отмена платежа в документации](https://yookassa.ru/developers/api?lang=python#cancel_payment) + +Отменяет платеж, находящийся в статусе `waiting_for_capture`. Отмена платежа значит, что вы не готовы +выдать пользователю товар или оказать услугу. Как только вы отменяете платеж, мы начинаем возвращать деньги на счет плательщика. Для платежей банковскими картами или из кошелька ЮMoney отмена происходит мгновенно. Для остальных способов оплаты возврат может занимать до нескольких дней. + +В ответ на запрос придет объект платежа в актуальном статусе. +```python +import var_dump as var_dump +from yookassa import Payment + +res = Payment.cancel('21b23b5b-000f-5061-a000-0674e49a8c10') + +var_dump.var_dump(res) +``` +[Подробнее о подтверждении и отмене платежей](https://yookassa.ru/developers/payments/payment-process#capture-and-cancel) + +--- + +### Получить информацию о платеже + +[Информация о платеже в документации](https://yookassa.ru/developers/api?lang=python#get_payment) + +Запрос позволяет получить информацию о текущем состоянии платежа по его уникальному идентификатору. + +В ответ на запрос придет объект платежа в актуальном статусе. + +```python +import var_dump as var_dump +from yookassa import Payment + +res = Payment.find_one('21b23b5b-000f-5061-a000-0674e49a8c10') + +var_dump.var_dump(res) +``` +--- + +### Получить список платежей с фильтрацией + +[Список платежей в документации](https://yookassa.ru/developers/api?lang=python#get_payments_list) + +Запрос позволяет получить список платежей, отфильтрованный по заданным критериям. + +В ответ на запрос вернется список платежей с учетом переданных параметров. В списке будет информация о платежах, +созданных за последние 3 года. Список будет отсортирован по времени создания платежей в порядке убывания. + +Если результатов больше, чем задано в `limit`, список будет выводиться фрагментами. В этом случае в ответе на запрос +вернется фрагмент списка и параметр `next_cursor` с указателем на следующий фрагмент. + +```python +import var_dump as var_dump +from yookassa import Payment + +cursor = None +data = { + "limit": 2, # Ограничиваем размер выборки + "payment_method": "yoo_money", # Выбираем только оплату через кошелек + "created_at.gte": "2020-08-08T00:00:00.000Z", # Созданы начиная с 2020-08-08 + "created_at.lt": "2020-10-20T00:00:00.000Z" # И до 2020-10-20 +} + +while True: + params = data + if cursor: + params['cursor'] = cursor + try: + res = Payment.list(params) + print(" items: " + str(len(res.items))) # Количество платежей в выборке + print("cursor: " + str(res.next_cursor)) # Указательна следующую страницу + var_dump.var_dump(res) + + if not res.next_cursor: + break + else: + cursor = res.next_cursor + except Exception as e: + print(" Error: " + str(e)) + break + +``` +[Подробнее о работе со списками](https://yookassa.ru/developers/using-api/lists) \ No newline at end of file diff --git a/docs/examples/03-refunds.md b/docs/examples/03-refunds.md new file mode 100644 index 0000000..86de4c8 --- /dev/null +++ b/docs/examples/03-refunds.md @@ -0,0 +1,156 @@ +## Работа с возвратами + +С помощью SDK можно возвращать платежи — полностью или частично. Порядок возврата зависит от способа оплаты +(`payment_method`) исходного платежа. При оплате банковской картой деньги возвращаются на карту, +которая была использована для проведения платежа. [Как проводить возвраты](https://yookassa.ru/developers/payments/refunds) + +Часть способов оплаты (например, наличные) не поддерживают возвраты. [Какие платежи можно вернуть](https://yookassa.ru/developers/payment-methods/overview#all) + +* [Запрос на создание возврата](#Запрос-на-создание-возврата) +* [Запрос на создание возврата через билдер](#Запрос-на-создание-возврата-через-билдер) +* [Получить информацию о возврате](#Получить-информацию-о-возврате) +* [Получить список возвратов с фильтрацией](#Получить-список-возвратов-с-фильтрацией) + +--- + +### Запрос на создание возврата + +[Создание возврата в документации](https://yookassa.ru/developers/api?lang=python#create_refund) + +Создает возврат успешного платежа на указанную сумму. Платеж можно вернуть только в течение трех лет с момента его создания. +Комиссия ЮKassa за проведение платежа не возвращается. + +В ответ на запрос придет объект возврата - `RefundResponse` в актуальном статусе. + +```python +import var_dump as var_dump +from yookassa import Refund + +res = Refund.create({ + "payment_id": "24e89cb0-000f-5000-9000-1de77fa0d6df", + "description": "Не подошел размер", + "amount": { + "value": "9000.00", + "currency": "RUB" + }, + "sources": [ + { + "account_id": "456", + "amount": { + "value": "9000.00", + "currency": "RUB" + } + } + ] +}) + +var_dump.var_dump(res) +``` + +--- + +### Запрос на создание возврата через билдер + +[Информация о создании возврата в документации](https://yookassa.ru/developers/api?lang=python#create_refund) + +Билдер позволяет создать объект платежа — `RefundRequest` программным способом, через объекты. + +```python +import var_dump as var_dump +from yookassa import Refund +from yookassa.domain.models.currency import Currency +from yookassa.domain.models.refund_source import RefundSource +from yookassa.domain.request.refund_request_builder import RefundRequestBuilder + +builder = RefundRequestBuilder() +builder.set_payment_id('24e89cb0-000f-5000-9000-1de77fa0d6df') \ + .set_description("Не подошел размер") \ + .set_amount({"value": 9000.0, "currency": Currency.RUB}) \ + .set_sources([ + RefundSource({ + 'account_id': 456, + 'amount': { + 'value': 9000.0, + 'currency': Currency.RUB + }, + "platform_fee_amount": { + "value": 10.01, + "currency": Currency.RUB + } + }) + ]) + +request = builder.build() +# Можно что-то поменять, если нужно +request.description = "Не подошел цвет и размер" + +res = Refund.create(request) + +var_dump.var_dump(res) +``` + +--- + +### Получить информацию о возврате + +[Информация о возврате в документации](https://yookassa.ru/developers/api?lang=python#get_refund) + +Запрос позволяет получить информацию о текущем состоянии возврата по его уникальному идентификатору. + +В ответ на запрос придет объект возврата - `RefundResponse` в актуальном статусе. + +```python +import var_dump as var_dump +from yookassa import Refund + +res = Refund.find_one('7894e5e2-a22e-434b-b6c1-e355ff096d1c') + +var_dump.var_dump(res) +``` + +--- + +### Получить список возвратов с фильтрацией + +[Список возвратов в документации](https://yookassa.ru/developers/api?lang=python#get_refunds_list) + +Запрос позволяет получить список возвратов, отфильтрованный по заданным критериям. + +В ответ на запрос вернется список возвратов с учетом переданных параметров. В списке будет информация о возвратах, +созданных за последние 3 года. Список будет отсортирован по времени создания возвратов в порядке убывания. + +Если результатов больше, чем задано в `limit`, список будет выводиться фрагментами. В этом случае в ответе на запрос +вернется фрагмент списка и параметр `next_cursor` с указателем на следующий фрагмент. + +```python +import var_dump as var_dump +from yookassa import Refund + +cursor = None +data = { + "limit": 2, # Ограничиваем размер выборки + "payment_id": "21b23b5b-000f-5061-a000-0674e49a8c10", # Выбираем только по конкретному платежу + "created_at.gte": "2020-08-08T00:00:00.000Z", # Созданы начиная с 2020-08-08 + "created_at.lt": "2020-10-20T00:00:00.000Z" # И до 2020-10-20 +} + +while True: + params = data + if cursor: + params['cursor'] = cursor + try: + res = Refund.list(params) + print(" items: " + str(len(res.items))) # Количество возвратов в выборке + print("cursor: " + str(res.next_cursor)) # Указательна следующую страницу + var_dump.var_dump(res) + + if not res.next_cursor: + break + else: + cursor = res.next_cursor + except Exception as e: + print(" Error: " + str(e)) + break + +``` +[Подробнее о работе со списками](https://yookassa.ru/developers/using-api/lists) \ No newline at end of file diff --git a/docs/examples/04-receipts.md b/docs/examples/04-receipts.md new file mode 100644 index 0000000..bc6bc49 --- /dev/null +++ b/docs/examples/04-receipts.md @@ -0,0 +1,207 @@ +## Работа с чеками + +> Для тех, кто использует [решение ЮKassa для 54-ФЗ](https://yookassa.ru/developers/54fz/basics). + +С помощью SDK можно получать информацию о чеках, для которых вы отправили данные через ЮKassa. + +* [Запрос на создание чека](#Запрос-на-создание-чека) +* [Запрос на создание чека через билдер](#Запрос-на-создание-чека-через-билдер) +* [Получить информацию о чеке](#Получить-информацию-о-чеке) +* [Получить список чеков с фильтрацией](#Получить-список-чеков-с-фильтрацией) + +--- + +### Запрос на создание чека + +[Информация о создании чека в документации](https://yookassa.ru/developers/api?lang=python#create_receipt) + +Запрос позволяет передать онлайн-кассе данные для формирования [чека зачета предоплаты](https://yookassa.ru/developers/54fz/payments#settlement-receipt). + +Если вы работаете по сценарию [Сначала платеж, потом чек](https://yookassa.ru/developers/54fz/basics#receipt-after-payment), +в запросе также нужно передавать данные для формирования чека прихода и чека возврата прихода. + +```python +from yookassa import Receipt + +res = Receipt.create({ + "customer": { + "full_name": "Ivanov Ivan Ivanovich", + "email": "email@email.ru", + "phone": "+79211234567", + "inn": "6321341814" + }, + "payment_id": "24b94598-000f-5000-9000-1b68e7b15f3f", + "type": "payment", + "send": True, + "items": [ + { + "description": "Наименование товара 1", + "quantity": 1.000, + "amount": { + "value": "14000.00", + "currency": "RUB" + }, + "vat_code": "2", + "payment_mode": "full_payment", + "payment_subject": "commodity", + "country_of_origin_code": "CN", + }, + { + "description": "Наименование товара 2", + "quantity": 1.000, + "amount": { + "value": "1000.00", + "currency": "RUB" + }, + "vat_code": "2", + "payment_mode": "full_payment", + "payment_subject": "commodity", + "country_of_origin_code": "CN", + }, + ], + "settlements": [ + { + "type": "prepayment", + "amount": { + "value": "8000.00", + "currency": "RUB" + }, + }, + { + "type": "prepayment", + "amount": { + "value": "7000.00", + "currency": "RUB" + }, + } + ] +}) +``` + +--- + +### Запрос на создание чека через билдер + +[Информация о создании чека в документации](https://yookassa.ru/developers/api?lang=python#create_receipt) + +Билдер позволяет создать объект платежа — `ReceiptRequest` программным способом, через объекты. + +```python +import var_dump as var_dump +from yookassa import Receipt +from yookassa.domain.common.receipt_type import ReceiptType +from yookassa.domain.models.currency import Currency +from yookassa.domain.models.receipt_item import ReceiptItem +from yookassa.domain.models.settlement import Settlement, SettlementType +from yookassa.domain.request.receipt_request_builder import ReceiptRequestBuilder + +builder = ReceiptRequestBuilder() +builder.set_type(ReceiptType.PAYMENT) \ + .set_payment_id('215d8da0-000f-50be-b000-0003308c89be') \ + .set_customer({'phone': '79990000000', 'email': 'test@email.com'}) \ + .set_tax_system_code(1) \ + .set_items([ + { + "description": "Product 1", + "quantity": 2.0, + "amount": { + "value": 250.0, + "currency": Currency.RUB + }, + "vat_code": 2 + }, + ReceiptItem({ + "description": "Product 2", + "quantity": 1.0, + "amount": { + "value": 100.0, + "currency": Currency.RUB + }, + "vat_code": 2 + }) + ]) \ + .set_settlements([ + Settlement({ + 'type': SettlementType.CASHLESS, + 'amount': { + 'value': 350.0, + 'currency': Currency.RUB + } + }) + ]) + +request = builder.build() +# Можно что-то поменять, если нужно +request.on_behalf_of = 123456 + +res = Receipt.create(request) + +var_dump.var_dump(res) +``` + +--- + +### Получить информацию о чеке + +[Информация о чеке в документации](https://yookassa.ru/developers/api?lang=python#get_receipt) + +Запрос позволяет получить информацию о текущем состоянии чека по его уникальному идентификатору. + +В ответ на запрос придет объект чека - `ReceiptResponse` в актуальном статусе. + +```python +import var_dump as var_dump +from yookassa import Receipt + +res = Receipt.find_one('rt-2da5c87d-0384-50e8-a7f3-8d5646dd9e10') + +var_dump.var_dump(res) +``` + +--- + +### Получить список чеков с фильтрацией + +[Список чеков в документации](https://yookassa.ru/developers/api?lang=python#get_receipts_list) + +Запрос позволяет получить список чеков, отфильтрованный по заданным критериям. +Можно запросить чеки по конкретному платежу, чеки по конкретному возврату или все чеки магазина. + +В ответ на запрос вернется список чеков с учетом переданных параметров. В списке будет информация о чеках, +созданных за последние 3 года. Список будет отсортирован по времени создания чеков в порядке убывания. + +Если результатов больше, чем задано в `limit`, список будет выводиться фрагментами. +В этом случае в ответе на запрос вернется фрагмент списка и параметр `next_cursor` с указателем на следующий фрагмент. + +```python +import var_dump as var_dump +from yookassa import Receipt + +cursor = None +data = { + "limit": 2, # Ограничиваем размер выборки + "payment_id": "21b23b5b-000f-5061-a000-0674e49a8c10", # Выбираем только по конкретному платежу + "created_at.gte": "2020-08-08T00:00:00.000Z", # Созданы начиная с 2020-08-08 + "created_at.lt": "2020-10-20T00:00:00.000Z" # И до 2020-10-20 +} + +while True: + params = data + if cursor: + params['cursor'] = cursor + try: + res = Receipt.list(params) + print(" items: " + str(len(res.items))) # Количество чеков в выборке + print("cursor: " + str(res.next_cursor)) # Указательна следующую страницу + var_dump.var_dump(res) + + if not res.next_cursor: + break + else: + cursor = res.next_cursor + except Exception as e: + print(" Error: " + str(e)) + break + +``` +[Подробнее о работе со списками](https://yookassa.ru/developers/using-api/lists) \ No newline at end of file diff --git a/docs/examples/readme.md b/docs/examples/readme.md new file mode 100644 index 0000000..049da70 --- /dev/null +++ b/docs/examples/readme.md @@ -0,0 +1,28 @@ +## Примеры использования SDK + +#### [Настройки SDK API ЮKassa](01-configuration.md) +* [Аутентификация](01-configuration.md#Аутентификация) +* [Статистические данные об используемом окружении](01-configuration.md#Статистические-данные-об-используемом-окружении) +* [Получение информации о магазине](01-configuration.md#Получение-информации-о-магазине) +* [Работа с Webhook](01-configuration.md#Работа-с-Webhook) +* [Входящие уведомления](01-configuration.md#Входящие-уведомления) + +#### [Работа с платежами](02-payments.md) +* [Запрос на создание платежа](02-payments.md#Запрос-на-создание-платежа) +* [Запрос на создание платежа через билдер](02-payments.md#Запрос-на-создание-платежа-через-билдер) +* [Запрос на частичное подтверждение платежа](02-payments.md#Запрос-на-частичное-подтверждение-платежа) +* [Запрос на отмену незавершенного платежа](02-payments.md#Запрос-на-отмену-незавершенного-платежа) +* [Получить информацию о платеже](02-payments.md#Получить-информацию-о-платеже) +* [Получить список платежей с фильтрацией](02-payments.md#Получить-список-платежей-с-фильтрацией) + +#### [Работа с возвратами](03-refunds.md) +* [Запрос на создание возврата](03-refunds.md#Запрос-на-создание-возврата) +* [Запрос на создание возврата через билдер](03-refunds.md#Запрос-на-создание-возврата-через-билдер) +* [Получить информацию о возврате](03-refunds.md#Получить-информацию-о-возврате) +* [Получить список возвратов с фильтрацией](03-refunds.md#Получить-список-возвратов-с-фильтрацией) + +#### [Работа с чеками](04-receipts.md) +* [Запрос на создание чека](04-receipts.md#Запрос-на-создание-чека) +* [Запрос на создание чека через билдер](04-receipts.md#Запрос-на-создание-чека-через-билдер) +* [Получить информацию о чеке](04-receipts.md#Получить-информацию-о-чеке) +* [Получить список чеков с фильтрацией](04-receipts.md#Получить-список-чеков-с-фильтрацией) \ No newline at end of file diff --git a/src/yookassa/__init__.py b/src/yookassa/__init__.py index 3cda3d3..a023167 100644 --- a/src/yookassa/__init__.py +++ b/src/yookassa/__init__.py @@ -10,4 +10,4 @@ __author__ = "YooMoney" __email__ = 'cms@yoomoney.ru' -__version__ = '2.0.1' +__version__ = '2.1.0' diff --git a/src/yookassa/domain/common/base_object.py b/src/yookassa/domain/common/base_object.py index 4c0d549..1ab5014 100644 --- a/src/yookassa/domain/common/base_object.py +++ b/src/yookassa/domain/common/base_object.py @@ -36,4 +36,4 @@ def __iter__(self): yield prop_name, prop_value def json(self): - return json.dumps(dict(self), default=str) + return json.dumps(dict(self), default=str, ensure_ascii=False) diff --git a/src/yookassa/domain/models/amount.py b/src/yookassa/domain/models/amount.py index fbd4ece..90f41cd 100644 --- a/src/yookassa/domain/models/amount.py +++ b/src/yookassa/domain/models/amount.py @@ -21,7 +21,7 @@ def value(self): @value.setter def value(self, value): - self.__value = Decimal(round(float(value), 2)) + self.__value = Decimal(str(round(float(value), 2))) @property def currency(self): diff --git a/src/yookassa/domain/models/receipt_item.py b/src/yookassa/domain/models/receipt_item.py index 83eb334..006fdfc 100644 --- a/src/yookassa/domain/models/receipt_item.py +++ b/src/yookassa/domain/models/receipt_item.py @@ -48,7 +48,7 @@ def quantity(self): @quantity.setter def quantity(self, value): - self.__quantity = Decimal(float(value)) + self.__quantity = Decimal(str(float(value))) @property def amount(self): @@ -120,7 +120,7 @@ def excise(self): @excise.setter def excise(self, value): - self.__excise = Decimal(float(value)) + self.__excise = Decimal(str(value)) class PaymentMode(object): diff --git a/src/yookassa/domain/request/receipt_item_request.py b/src/yookassa/domain/request/receipt_item_request.py index 3ad61a1..a86c5a2 100644 --- a/src/yookassa/domain/request/receipt_item_request.py +++ b/src/yookassa/domain/request/receipt_item_request.py @@ -53,7 +53,7 @@ def quantity(self): @quantity.setter def quantity(self, value): - self.__quantity = Decimal(float(value)) + self.__quantity = Decimal(str(float(value))) @property def amount(self): @@ -125,7 +125,7 @@ def excise(self): @excise.setter def excise(self, value): - self.__excise = Decimal(float(value)) + self.__excise = Decimal(str(float(value))) @property def supplier(self): diff --git a/src/yookassa/domain/response/receipt_item_response.py b/src/yookassa/domain/response/receipt_item_response.py index 4ae7f53..3d5b907 100644 --- a/src/yookassa/domain/response/receipt_item_response.py +++ b/src/yookassa/domain/response/receipt_item_response.py @@ -40,7 +40,7 @@ def quantity(self): @quantity.setter def quantity(self, value): - self.__quantity = Decimal(float(value)) + self.__quantity = Decimal(str(float(value))) @property def amount(self): diff --git a/test/unit/test_amount.py b/test/unit/test_amount.py index 45093e6..26380b5 100644 --- a/test/unit/test_amount.py +++ b/test/unit/test_amount.py @@ -13,7 +13,7 @@ def test_amount_cast(self): amount.currency = Currency.RUB self.assertEqual({'value': 0.1, 'currency': Currency.RUB}, dict(amount)) - self.assertEqual(0.1, amount.value) + self.assertEqual(0.1, float(amount.value)) def test_amount_value(self): amount = Amount({ @@ -22,4 +22,4 @@ def test_amount_value(self): }) self.assertEqual({"value": 100.01, "currency": Currency.RUB}, dict(amount)) - self.assertEqual(amount.value, 100.01) + self.assertEqual(float(amount.value), 100.01) diff --git a/test/unit/test_receipt_response.py b/test/unit/test_receipt_response.py index 20f273e..df7257b 100644 --- a/test/unit/test_receipt_response.py +++ b/test/unit/test_receipt_response.py @@ -109,5 +109,5 @@ def test_response_cast_refund(self): self.assertEqual(response.items.count(ReceiptItemResponse), 0) self.assertEqual(response.settlements[0].type, SettlementType.CASHLESS) - self.assertEqual(response.settlements[0].amount.value, 45.67) + self.assertEqual(float(response.settlements[0].amount.value), 45.67) self.assertEqual(response.settlements[0].amount.currency, Currency.RUB) diff --git a/test/unit/test_refund_source.py b/test/unit/test_refund_source.py index ce77ce3..227feee 100644 --- a/test/unit/test_refund_source.py +++ b/test/unit/test_refund_source.py @@ -28,7 +28,7 @@ def test_refund_source_cast(self): self.assertEqual('79990000000', src.account_id) self.assertEqual({"value": 100.01, "currency": Currency.RUB}, dict(src.amount)) - self.assertEqual(src.amount.value, 100.01) + self.assertEqual(float(src.amount.value), 100.01) with self.assertRaises(TypeError): src.amount = 'invalid type' diff --git a/test/unit/test_transfer.py b/test/unit/test_transfer.py index d13900d..065d895 100644 --- a/test/unit/test_transfer.py +++ b/test/unit/test_transfer.py @@ -28,7 +28,7 @@ def test_receipt_cast(self): self.assertEqual('79990000000', transfer.account_id) self.assertEqual({"value": 100.01, "currency": Currency.RUB}, dict(transfer.amount)) - self.assertEqual(transfer.amount.value, 100.01) + self.assertEqual(float(transfer.amount.value), 100.01) with self.assertRaises(TypeError): transfer.amount = 'invalid type' diff --git a/test/unit/test_transfer_response.py b/test/unit/test_transfer_response.py index 4c4fff5..d495baf 100644 --- a/test/unit/test_transfer_response.py +++ b/test/unit/test_transfer_response.py @@ -30,7 +30,7 @@ def test_transfer_cast(self): self.assertEqual(TransferStatus.SUCCEEDED, transfer.status) self.assertEqual({"value": 100.01, "currency": Currency.RUB}, dict(transfer.amount)) - self.assertEqual(transfer.amount.value, 100.01) + self.assertEqual(float(transfer.amount.value), 100.01) with self.assertRaises(TypeError): transfer.amount = 'invalid type' @@ -59,4 +59,4 @@ def test_transfer_value(self): self.assertEqual(TransferStatus.SUCCEEDED, transfer.status) self.assertEqual({"value": 100.01, "currency": Currency.RUB}, dict(transfer.amount)) - self.assertEqual(transfer.amount.value, 100.01) + self.assertEqual(float(transfer.amount.value), 100.01)