Skip to content

Commit

Permalink
✨ Up API version, multiuser, token manager and MORE docs
Browse files Browse the repository at this point in the history
  • Loading branch information
WhiteApfel committed Jun 9, 2023
1 parent 1038deb commit 8e08a90
Show file tree
Hide file tree
Showing 27 changed files with 1,091 additions and 309 deletions.
197 changes: 171 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
# 🎉 Tochka API

**Simple Tochka Bank API wrapper**
**Удобная обёртка над открытым API Банка Точка**

## 📥 Installation
## 📥 Установка

### 📦 From pip:
### 📦 Из pip:

```shell
python -m pip install -u tochka_api
```

### 🏗 From git:
### 🏗 Из репозитория:

```shell
git clone https://github.com/WhiteApfel/tochka_api.git
cd tochka_api
python setup.py install
```

### 🚧 Dev progress
### 🚧 Прогресс разработки

* [x] Auth
* [x] Balances
* [x] Accounts
* [x] Авторизация
* [x] Балансы
* [x] Счета
* [ ] Webhooks
* [ ] Statements
* [ ] Cards
* [ ] Clients
* [ ] Payments
* [ ] Consents
* [ ] Special accounts
* [x] SBP
* [ ] Выписки
* [ ] Карты
* [ ] Клиенты
* [ ] Платежи
* [ ] Разрешения
* [ ] Специальные счета
* [x] СБП
* [x] QR
* [x] Merchants
* [x] Legal
* [x] Refunds
* [x] Account
* [x] ТСП (Merchant)
* [x] Компании (Legal)
* [x] Возвраты

### 🧑‍🏫 How to use
### 🧑‍🏫 Как использовать

**💰 Уточнения по типу данных для суммы**

* ``Decimal`` and ``str`` - amount in rubles
* ``int`` - amount in kopecks

**Различия user_code и customer_code**

* ``user_code`` это код клиента, который имеет доступ к компании
* ``customer_code`` это код компании(ИП), на которую открыты счета

```python
import asyncio

from tochka_api import TochkaAPI
from decimal import Decimal

from tochka_api import TochkaAPI, context_user_code
from tochka_api.models import PermissionsEnum

client_id = "<<client_id>>"
Expand All @@ -52,12 +63,146 @@ redirect_uri = "https://tochka-api.pfel.cc/"
tochka = TochkaAPI(client_id, client_secret, redirect_uri=redirect_uri)


async def add_user():
consents_token, consents_expires_in = await tochka.get_consents_token()
consents_request = await tochka.create_consents(
consents_token, PermissionsEnum.all()
)
auth_url = tochka.generate_auth_url(consent_id=consents_request.consent_id)
print("Auth:", auth_url)

code = input("Code >>> ")
token_id = input("Token_id >>> ")
tokens = await tochka.get_access_token(
code=code, token_id=token_id
)

print(f"User {tokens.user_code=} are authorized.")
context_user_code.set(tokens.user_code)

asyncio.create_task(get_accounts())
merchant_id = await register_merchant(user_code=tokens.user_code)
await register_qr(merchant_id=merchant_id)

async def get_accounts():
# user_code будет унаследован из context_user_code
# Это thread-safe и loop-safe
for _ in range(25):
await tochka.get_accounts()
await asyncio.sleep(2)

async def register_merchant(user_code) -> str:
# Будет использоваться указанный user_code
# Даже если в context_user_code было задано другое значение
accounts = await tochka.get_accounts(user_code=user_code)

customer_info = await tochka.sbp_get_customer_info(
customer_code=accounts[0].customer_code
)
legal_entity = await tochka.sbp_get_legal_entity(customer_info.legal_id)

merchant = await tochka.sbp_register_merchant(
legal_id=legal_entity.legal_id,
name="TochkaExample",
mcc="7277",
address=" 1-й Вешняковский проезд, д. 1, стр. 8, этаж 1, помещ. 43",
city="Москва",
region_code="45",
zip_code="109456",
phone_number="+78002000024",
)

print(f"New merchant {merchant.merchant_id=}")
print(
*(await tochka.sbp_get_merchants(legal_id=legal_entity.legal_id)).merchants,
sep="\n",
)

return merchant.merchant_id

async def register_qr(merchant_id):
# user_code будет унаследован из context_user_code
# Это thread-safe и loop-safe
accounts = await tochka.get_accounts()

customer_info = await tochka.sbp_get_customer_info(
customer_code=accounts[0].customer_code
)
legal_entity = await tochka.sbp_get_legal_entity(customer_info.legal_id)
sbp_accounts = await tochka.sbp_get_accounts(legal_entity.legal_id)

qr = await tochka.sbp_register_qr(
merchant_id=merchant_id,
account=sbp_accounts[0].account,
is_static=True,
purpose="Перечисление по договору минета",
media_type="image/svg+xml",
)
print("Статичный без суммы:\n",qr.image.content)

qr = await tochka.sbp_register_qr(
merchant_id=merchant_id,
account=sbp_accounts[0].account,
is_static=True,
amount=Decimal("100.00"), # или Decimal(100), или 10000
purpose="Оплата аренды борделя",
media_type="image/svg+xml",
)
print("Статичный с суммой:\n",qr.image.content)

qr = await tochka.sbp_register_qr(
merchant_id=merchant_id,
account=sbp_accounts[0].account,
is_static=False,
amount=10000, # или Decimal(100)
ttl=60, # 0 - максимально возможное ограничение, иначе в минутах
purpose="Оплата поставки презервативов",
media_type="image/svg+xml",
)
print("Динамический с суммой:\n",qr.image.content)

async def refund():
accounts = await tochka.get_accounts()

customer_info = await tochka.sbp_get_customer_info(
customer_code=accounts[0].customer_code
)
sbp_accounts = await tochka.sbp_get_accounts(customer_info.legal_id)

payments = await tochka.sbp_get_payments(
customer_info.customer_code, from_date=1,
)
print(payments)

last_payment = payments.payments[0]
refund_response = await tochka.sbp_start_refund(
account=sbp_accounts[0].account,
amount=Decimal("1.25"), # или 125
qrc_id=last_payment.qrc_id,
trx_id=last_payment.trx_id,
)
print("Refund: ", refund_response)

async def main():
if tochka.tokens.access_token is None:
await tochka.get_consents_token()
consents_request = await tochka.create_consents(PermissionsEnum.all())
print(tochka.generate_auth_url(consent_id=consents_request.consent_id))
await tochka.get_access_token(code=input("Code >>> "))
# Добавить два пользователя.
# Это могут быть бухгалтер и владелец одной компании
# или совершенно разные люди из разных компаний.
# Приложение может работать с несколькими пользователями
# либо в однопользовательском режиме, тогда надо указать
# tochka = TochkaAPI(client_id, client_secret, redirect_uri=redirect_uri, one_customer_mode=True)
# и тогда после добавления одного пользователя, система его запомнит.
# Указывать context_user_code не придётся
print("Введите что-либо, чтобы добавить пользователей")
print("Для пропуска нажмите Enter")
if input(">>> "):
await add_user()
await add_user()
else:
# можно не указывать, если one_customer_mode=True
context_user_code.set("212332030")

await refund()


balances = await tochka.get_balances()
print(balances[0].amount)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def requirements():
description="Simple Tochka Bank Open API client",
install_requires=requirements(),
project_urls={
"Source code": "https://github.com/WhiteApfel/pyQiwiP2P",
"Source code": "https://github.com/WhiteApfel/tochka-api",
"Write me": "https://t.me/whiteapfel",
},
long_description=read("README.md"),
Expand Down
26 changes: 13 additions & 13 deletions tests/sandbox/disable_test_sbp_legal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
SbpAccountsResponse,
SbpCustomerInfoResponse,
SbpLegalEntityResponse,
SbpRegisterLegalEntity,
SbpRegisterLegalEntityResponse,
TochkaBooleanResponse,
)

Expand Down Expand Up @@ -31,30 +31,30 @@ async def test_balance_response(tochka_client):


# async def sbp_set_account_status(
# self, legal_id: str, account_id: str, active: str | bool = True
# self, legal_id: str, account: str, is_active: str | bool = True
# ) -> TochkaBooleanResponse:
# data = {
# "Data": {
# "status": ("Active" if active else "Suspended")
# if type(active) is bool
# else active,
# "status": ("Active" if is_active else "Suspended")
# if type(is_active) is bool
# else is_active,
# }
# }
# return await self.request(
# method="POST",
# url=f"/sbp/v1.0/account/{legal_id}/{account_id}",
# url=f"/sbp/v1.0/account/{legal_id}/{account}",
# data=data,
# )
#
#
# async def sbp_set_legal_entity_status(
# self, legal_id: str, active: str | bool = True
# self, legal_id: str, is_active: str | bool = True
# ) -> TochkaBooleanResponse:
# data = {
# "Data": {
# "status": ("Active" if active else "Suspended")
# if type(active) is bool
# else active,
# "status": ("Active" if is_active else "Suspended")
# if type(is_active) is bool
# else is_active,
# }
# }
# return await self.request(
Expand All @@ -64,7 +64,7 @@ async def test_balance_response(tochka_client):
# )
#
#
# async def sbp_register_legal_entity(self, customer_code: str) -> SbpRegisterLegalEntity:
# async def sbp_register_legal_entity(self, customer_code: str) -> SbpRegisterLegalEntityResponse:
# data = {
# "Data": {
# "customerCode": customer_code,
Expand All @@ -78,10 +78,10 @@ async def test_balance_response(tochka_client):
# )
#
#
# async def sbp_get_account(self, legal_id: str, account_id: str) -> SbpAccountsResponse:
# async def sbp_get_account(self, legal_id: str, account: str) -> SbpAccountsResponse:
# return await self.request(
# method="GET",
# url=f"sbp/v1.0/account/{legal_id}/{account_id}"
# url=f"sbp/v1.0/account/{legal_id}/{account}"
# )
#
#
Expand Down
2 changes: 1 addition & 1 deletion tests/sandbox/test_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ async def test_accounts_response(tochka_client):
async def test_account_response(tochka_client):
accounts = await tochka_client.get_accounts()
for account in accounts:
response = await tochka_client.get_account(account.account_id)
response = await tochka_client.get_account(account.account)
assert isinstance(response, AccountsResponse)
2 changes: 1 addition & 1 deletion tests/sandbox/test_balances.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ async def test_balances_response(tochka_client):
async def test_balance_response(tochka_client):
accounts = await tochka_client.get_accounts()
for account in accounts:
response = await tochka_client.get_balance(account_id=account.account_id)
response = await tochka_client.get_balance(account=account.account)
assert isinstance(response, BalanceResponse)
4 changes: 3 additions & 1 deletion tochka-api.pfel.cc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
},
"devDependencies": {
"nuxt": "3.0.0-rc.8",
"wrangler": "^2.0.28"
"wrangler": "^2.0.28",
"@rollup/plugin-inject": "^5.0.2npm a"
},
"dependencies": {
"node-fetch": "^3.3.0",
"yarn": "^1.22.19"
}
}
2 changes: 1 addition & 1 deletion tochka-api.pfel.cc/wrangler.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name = "tochka_api"
account_id = "05e1669d8f5321ed189bc14b2cc0d0c5"
account = "05e1669d8f5321ed189bc14b2cc0d0c5"
workers_dev = true
route = ""
main = "./.output/server/index.mjs"
Expand Down
1 change: 1 addition & 0 deletions tochka_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from modules import TochkaAPI
from modules.base import context_user_code
2 changes: 1 addition & 1 deletion tochka_api/models/responses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .sbp_legal import (
SbpLegalEntityResponse,
SbpCustomerInfoResponse,
SbpRegisterLegalEntity,
SbpRegisterLegalEntityResponse,
SbpAccountsResponse,
)
from .sbp_refunds import SbpPaymentsResponse, SbpRefundResponse
Expand Down
2 changes: 1 addition & 1 deletion tochka_api/models/responses/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class AccountDetails(BaseModel):

class Account(BaseModel):
customer_code: str = Field(..., alias="customerCode")
account_id: str = Field(..., alias="accountId")
account: str = Field(..., alias="accountId")
transit_account: str | None = Field(None, alias="transitAccount")
status: Literal["Enabled", "Disabled", "Deleted", "ProForma", "Pending"]
status_updated_at: datetime = Field(..., alias="statusUpdateDateTime")
Expand Down
2 changes: 1 addition & 1 deletion tochka_api/models/responses/balances.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Amount(BaseModel):


class Balance(BaseModel):
account_id: str = Field(..., alias="accountId")
account: str = Field(..., alias="accountId")
indicator: Literal["Credit", "Debit"] = Field(..., alias="creditDebitIndicator")
created_at: datetime = Field(..., alias="dateTime")
currency: str
Expand Down
3 changes: 1 addition & 2 deletions tochka_api/models/responses/sbp_accounts.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from models.responses import TochkaBaseResponse
from pydantic import BaseModel, Field, root_validator

from models.responses.sbp_legal import SbpAccount
from pydantic import BaseModel, Field, root_validator


class SbpAccountsResponse(TochkaBaseResponse):
Expand Down
Loading

0 comments on commit 8e08a90

Please sign in to comment.