from __future__ import annotations
from datetime import date, datetime
from decimal import Decimal
from typing import Annotated, Dict, List, Literal, Optional, Union
from pydantic import BaseModel, ConfigDict, Field, PlainSerializer
from pydantic.alias_generators import to_camel
from tbank.core.models import Rubles
from tbank.nominal_accounts.enums import (
AddressType,
DealStatus,
PaymentStatus,
StepStatus,
TransferType,
)
# Суммы на запись: Decimal у пользователя, но в JSON уходит числом (T-API ждёт number).
WriteRubles = Annotated[
Decimal, PlainSerializer(lambda v: float(v), return_type=float, when_used="json")
]
[документация]
class NominalModel(BaseModel):
"""Базовая модель домена: snake_case в Python, camelCase на проводе."""
model_config = ConfigDict(
alias_generator=to_camel,
populate_by_name=True,
extra="ignore",
)
class _Paged(NominalModel):
"""Общие поля страничного ответа."""
offset: int
limit: int
size: int
total: int
# --- Документы бенефициара (дискриминатор `type`) ---
[документация]
class Passport(NominalModel):
type: Literal["PASSPORT"] = "PASSPORT"
serial: str
number: str
issued_on: date = Field(alias="date")
division: str
organization: Optional[str] = None
[документация]
class ForeignPassport(NominalModel):
type: Literal["FOREIGN_PASSPORT"] = "FOREIGN_PASSPORT"
number: str
issued_on: date = Field(alias="date")
organization: str
[документация]
class ForeignPassportOfForeignCitizens(NominalModel):
type: Literal["FOREIGN_PASSPORT_OF_FOREIGN_CITIZENS"] = (
"FOREIGN_PASSPORT_OF_FOREIGN_CITIZENS"
)
number: str
issued_on: date = Field(alias="date")
organization: str
[документация]
class OfficialPassport(NominalModel):
type: Literal["OFFICIAL_PASSPORT"] = "OFFICIAL_PASSPORT"
number: str
issued_on: date = Field(alias="date")
organization: str
[документация]
class DiplomaticPassport(NominalModel):
type: Literal["DIPLOMATIC_PASSPORT"] = "DIPLOMATIC_PASSPORT"
number: str
issued_on: date = Field(alias="date")
organization: str
[документация]
class MigrationCard(NominalModel):
type: Literal["MIGRATION_CARD"] = "MIGRATION_CARD"
number: str
issued_on: date = Field(alias="date")
expire_date: date
[документация]
class TemporaryResidencePermit(NominalModel):
type: Literal["TEMPORARY_RESIDENCE_PERMIT"] = "TEMPORARY_RESIDENCE_PERMIT"
number: str
issued_on: date = Field(alias="date")
expire_date: date
[документация]
class Visa(NominalModel):
type: Literal["VISA"] = "VISA"
number: str
issued_on: date = Field(alias="date")
expire_date: date
[документация]
class Patent(NominalModel):
type: Literal["PATENT"] = "PATENT"
number: str
issued_on: date = Field(alias="date")
expire_date: date
[документация]
class ResidencePermit(NominalModel):
type: Literal["RESIDENCE_PERMIT"] = "RESIDENCE_PERMIT"
number: str
issued_on: date = Field(alias="date")
serial: Optional[str] = None
expire_date: Optional[date] = None
[документация]
class Contract(NominalModel):
type: Literal["CONTRACT"] = "CONTRACT"
number: str
issued_on: date = Field(alias="date")
expire_date: Optional[date] = None
[документация]
class ContractGPD(NominalModel):
type: Literal["CONTRACT_GPD"] = "CONTRACT_GPD"
number: str
issued_on: date = Field(alias="date")
expire_date: Optional[date] = None
Document = Annotated[
Union[
Passport,
ForeignPassport,
ForeignPassportOfForeignCitizens,
OfficialPassport,
DiplomaticPassport,
MigrationCard,
TemporaryResidencePermit,
Visa,
Patent,
ResidencePermit,
Contract,
ContractGPD,
],
Field(discriminator="type"),
]
[документация]
class Address(NominalModel):
type: AddressType
address: str
# --- Бенефициары: запросы (дискриминатор `type`) ---
class _FlBeneficiaryBase(NominalModel):
first_name: str
last_name: str
is_self_employed: bool
birth_date: date
citizenship: str
documents: List[Document]
addresses: List[Address]
middle_name: Optional[str] = None
birth_place: Optional[str] = None
phone_number: Optional[str] = None
email: Optional[str] = None
inn: Optional[str] = None
[документация]
class BeneficiaryFlResidentRequest(_FlBeneficiaryBase):
type: Literal["FL_RESIDENT"] = "FL_RESIDENT"
snils: Optional[str] = None
[документация]
class BeneficiaryFlNonresidentRequest(_FlBeneficiaryBase):
type: Literal["FL_NONRESIDENT"] = "FL_NONRESIDENT"
class _IpBeneficiaryRequestBase(NominalModel):
first_name: str
last_name: str
birth_date: date
citizenship: str
documents: List[Document]
addresses: List[Address]
registration_date: date
inn: str
middle_name: Optional[str] = None
birth_place: Optional[str] = None
phone_number: Optional[str] = None
email: Optional[str] = None
ogrn: Optional[str] = None
[документация]
class BeneficiaryIpResidentRequest(_IpBeneficiaryRequestBase):
type: Literal["IP_RESIDENT"] = "IP_RESIDENT"
[документация]
class BeneficiaryIpNonresidentRequest(_IpBeneficiaryRequestBase):
type: Literal["IP_NONRESIDENT"] = "IP_NONRESIDENT"
[документация]
class BeneficiaryUlResidentRequest(NominalModel):
type: Literal["UL_RESIDENT"] = "UL_RESIDENT"
inn: str
name: Optional[str] = None
phone_number: Optional[str] = None
email: Optional[str] = None
addresses: Optional[List[Address]] = None
registration_date: Optional[date] = None
opf: Optional[str] = None
ogrn: Optional[str] = None
[документация]
class BeneficiaryUlNonresidentRequest(NominalModel):
type: Literal["UL_NONRESIDENT"] = "UL_NONRESIDENT"
name: str
addresses: List[Address]
registration_date: date
registration_number: str
phone_number: Optional[str] = None
email: Optional[str] = None
nza: Optional[str] = None
opf: Optional[str] = None
inn: Optional[str] = None
kio: Optional[str] = None
BeneficiaryRequest = Annotated[
Union[
BeneficiaryFlResidentRequest,
BeneficiaryFlNonresidentRequest,
BeneficiaryIpResidentRequest,
BeneficiaryIpNonresidentRequest,
BeneficiaryUlResidentRequest,
BeneficiaryUlNonresidentRequest,
BeneficiaryLiteContactRequest,
],
Field(discriminator="type"),
]
# --- Бенефициары: ответы (дискриминатор `type`) ---
[документация]
class BeneficiaryFlResidentResponse(NominalModel):
type: Literal["FL_RESIDENT"] = "FL_RESIDENT"
beneficiary_id: str
first_name: str
last_name: str
is_self_employed: bool
birth_date: date
middle_name: Optional[str] = None
birth_place: Optional[str] = None
citizenship: Optional[str] = None
phone_number: Optional[str] = None
email: Optional[str] = None
documents: Optional[List[Document]] = None
addresses: Optional[List[Address]] = None
inn: Optional[str] = None
snils: Optional[str] = None
[документация]
class BeneficiaryFlNonresidentResponse(NominalModel):
type: Literal["FL_NONRESIDENT"] = "FL_NONRESIDENT"
beneficiary_id: str
first_name: str
last_name: str
is_self_employed: bool
birth_date: date
middle_name: Optional[str] = None
birth_place: Optional[str] = None
citizenship: Optional[str] = None
phone_number: Optional[str] = None
email: Optional[str] = None
documents: Optional[List[Document]] = None
addresses: Optional[List[Address]] = None
inn: Optional[str] = None
class _IpBeneficiaryResponseBase(NominalModel):
beneficiary_id: str
first_name: str
last_name: str
birth_date: date
registration_date: date
middle_name: Optional[str] = None
birth_place: Optional[str] = None
citizenship: Optional[str] = None
phone_number: Optional[str] = None
email: Optional[str] = None
documents: Optional[List[Document]] = None
addresses: Optional[List[Address]] = None
inn: Optional[str] = None
ogrn: Optional[str] = None
[документация]
class BeneficiaryIpResidentResponse(_IpBeneficiaryResponseBase):
type: Literal["IP_RESIDENT"] = "IP_RESIDENT"
[документация]
class BeneficiaryIpNonresidentResponse(_IpBeneficiaryResponseBase):
type: Literal["IP_NONRESIDENT"] = "IP_NONRESIDENT"
[документация]
class BeneficiaryUlResidentResponse(NominalModel):
type: Literal["UL_RESIDENT"] = "UL_RESIDENT"
beneficiary_id: str
inn: str
name: Optional[str] = None
phone_number: Optional[str] = None
email: Optional[str] = None
addresses: Optional[List[Address]] = None
registration_date: Optional[date] = None
opf: Optional[str] = None
ogrn: Optional[str] = None
[документация]
class BeneficiaryUlNonresidentResponse(NominalModel):
type: Literal["UL_NONRESIDENT"] = "UL_NONRESIDENT"
beneficiary_id: str
name: str
registration_date: date
registration_number: str
phone_number: Optional[str] = None
email: Optional[str] = None
addresses: Optional[List[Address]] = None
nza: Optional[str] = None
opf: Optional[str] = None
inn: Optional[str] = None
kio: Optional[str] = None
BeneficiaryResponse = Annotated[
Union[
BeneficiaryFlResidentResponse,
BeneficiaryFlNonresidentResponse,
BeneficiaryIpResidentResponse,
BeneficiaryIpNonresidentResponse,
BeneficiaryUlResidentResponse,
BeneficiaryUlNonresidentResponse,
BeneficiaryLiteContactResponse,
],
Field(discriminator="type"),
]
[документация]
class BeneficiaryListResponse(_Paged):
results: List[BeneficiaryResponse] = Field(default_factory=list)
# --- Бенефициары: скоринг (дискриминатор `status`) ---
[документация]
class BeneficiaryScoringError(NominalModel):
code: str
description: str
[документация]
class ScoringInProgress(NominalModel):
status: Literal["IN_PROGRESS"] = "IN_PROGRESS"
beneficiary_id: str
[документация]
class ScoringSucceeded(NominalModel):
status: Literal["SUCCEEDED"] = "SUCCEEDED"
beneficiary_id: str
warnings: Optional[List[BeneficiaryScoringError]] = None
[документация]
class ScoringFailed(NominalModel):
status: Literal["FAILED"] = "FAILED"
beneficiary_id: str
warnings: Optional[List[BeneficiaryScoringError]] = None
errors: Optional[List[BeneficiaryScoringError]] = None
BeneficiaryScoringInfo = Annotated[
Union[ScoringInProgress, ScoringSucceeded, ScoringFailed],
Field(discriminator="status"),
]
[документация]
class BeneficiaryScoringListResponse(_Paged):
results: List[BeneficiaryScoringInfo] = Field(default_factory=list)
# --- Реквизиты для добавления карты (дискриминатор `status`) ---
[документация]
class PendingAddCardResponse(NominalModel):
status: Literal["PENDING"] = "PENDING"
beneficiary_id: str
add_card_request_id: str
terminal_key: str
add_card_url: str
[документация]
class ReadyAddCardResponse(NominalModel):
status: Literal["READY"] = "READY"
beneficiary_id: str
add_card_request_id: str
terminal_key: str
bank_details_id: str
[документация]
class FailedAddCardResponse(NominalModel):
status: Literal["FAILED"] = "FAILED"
beneficiary_id: str
add_card_request_id: str
terminal_key: str
error_message: str
AddCardRequestResponse = Annotated[
Union[PendingAddCardResponse, ReadyAddCardResponse, FailedAddCardResponse],
Field(discriminator="status"),
]
# --- Банковские реквизиты бенефициара (дискриминатор `type`) ---
[документация]
class RkcBankDetailsRequest(NominalModel):
type: Literal["PAYMENT_DETAILS"] = "PAYMENT_DETAILS"
bik: str
bank_name: str
account_number: str
corr_account_number: str
is_default: Optional[bool] = None
kpp: Optional[str] = None
inn: Optional[str] = None
name: Optional[str] = None
[документация]
class CardBankDetailsRequest(NominalModel):
type: Literal["CARD"] = "CARD"
terminal_key: str
card_data: str
is_default: Optional[bool] = None
[документация]
class SbpBankDetailsRequest(NominalModel):
type: Literal["SBP"] = "SBP"
terminal_key: str
phone_number: str
bank_id: str
is_default: Optional[bool] = None
BankDetailsRequest = Annotated[
Union[RkcBankDetailsRequest, CardBankDetailsRequest, SbpBankDetailsRequest],
Field(discriminator="type"),
]
[документация]
class RkcBankDetailsResponse(NominalModel):
type: Literal["PAYMENT_DETAILS"] = "PAYMENT_DETAILS"
beneficiary_id: str
bank_details_id: str
bik: str
bank_name: str
account_number: str
corr_account_number: str
is_default: Optional[bool] = None
kpp: Optional[str] = None
inn: Optional[str] = None
name: Optional[str] = None
[документация]
class CardBankDetailsResponse(NominalModel):
type: Literal["CARD"] = "CARD"
beneficiary_id: str
bank_details_id: str
card_id: str
terminal_key: str
is_default: Optional[bool] = None
[документация]
class SbpBankDetailsResponse(NominalModel):
type: Literal["SBP"] = "SBP"
beneficiary_id: str
bank_details_id: str
phone_number: str
bank_id: str
terminal_key: str
is_default: Optional[bool] = None
BankDetailsResponse = Annotated[
Union[RkcBankDetailsResponse, CardBankDetailsResponse, SbpBankDetailsResponse],
Field(discriminator="type"),
]
[документация]
class BankDetailsListResponse(_Paged):
results: List[BankDetailsResponse] = Field(default_factory=list)
# --- Банковские реквизиты в карточке платежа (дискриминатор `type`) ---
[документация]
class RkcBankDetails(NominalModel):
type: Literal["PAYMENT_DETAILS"] = "PAYMENT_DETAILS"
bik: str
bank_name: str
account_number: str
corr_account_number: str
kpp: Optional[str] = None
inn: Optional[str] = None
name: Optional[str] = None
[документация]
class CardBankDetails(NominalModel):
type: Literal["CARD"] = "CARD"
card_id: str
terminal_key: str
[документация]
class SbpBankDetails(NominalModel):
type: Literal["SBP"] = "SBP"
phone_number: str
bank_id: str
terminal_key: str
BankDetails = Annotated[
Union[RkcBankDetails, CardBankDetails, SbpBankDetails],
Field(discriminator="type"),
]
# --- Сделки ---
[документация]
class DealResponse(NominalModel):
deal_id: str
account_number: str
status: DealStatus
[документация]
class DealListResponse(_Paged):
results: List[DealResponse] = Field(default_factory=list)
[документация]
class DealValidityReason(NominalModel):
code: Optional[str] = None
description: Optional[str] = None
details: Optional[Dict[str, str]] = None
[документация]
class DealValidity(NominalModel):
is_valid: bool
reasons: Optional[List[DealValidityReason]] = None
# --- Этапы сделки ---
[документация]
class StepResponse(NominalModel):
deal_id: str
step_id: str
step_number: int
description: str
status: StepStatus
[документация]
class StepListResponse(_Paged):
results: List[StepResponse] = Field(default_factory=list)
# --- Депоненты ---
[документация]
class DeponentRequest(NominalModel):
amount: WriteRubles
[документация]
class DeponentResponse(NominalModel):
deal_id: str
step_id: str
beneficiary_id: str
amount: Rubles
[документация]
class DeponentListResponse(_Paged):
results: List[DeponentResponse] = Field(default_factory=list)
# --- Реципиенты ---
[документация]
class RecipientRequest(NominalModel):
beneficiary_id: str
amount: WriteRubles
tax: Optional[WriteRubles] = None
purpose: Optional[str] = None
bank_details_id: Optional[str] = None
keep_on_virtual_account: Optional[bool] = None
[документация]
class RecipientResponse(NominalModel):
deal_id: str
step_id: str
beneficiary_id: str
recipient_id: str
amount: Rubles
tax: Optional[Rubles] = None
purpose: Optional[str] = None
bank_details_id: Optional[str] = None
keep_on_virtual_account: Optional[bool] = None
[документация]
class RecipientListResponse(_Paged):
results: List[RecipientResponse] = Field(default_factory=list)
[документация]
class UpdateRecipientBankDetailsRequest(NominalModel):
bank_details_id: str
# --- Платежи (биллинг), дискриминатор `type` ---
[документация]
class TaxPaymentParameters(NominalModel):
payer_status: str
kbk: str
oktmo: str
evidence: str
period: str
doc_number: str
doc_date: str
third_party: Optional[TaxThirdParty] = None
[документация]
class CreateRegularPaymentRequest(NominalModel):
type: Literal["REGULAR"] = "REGULAR"
beneficiary_id: str
account_number: str
amount: WriteRubles
purpose: str
bank_details_id: Optional[str] = None
[документация]
class CreateTaxPaymentRequest(NominalModel):
type: Literal["TAX"] = "TAX"
beneficiary_id: str
account_number: str
bank_details: BankDetails
amount: WriteRubles
purpose: str
uin: str
tax: TaxPaymentParameters
CreatePaymentRequest = Annotated[
Union[CreateRegularPaymentRequest, CreateTaxPaymentRequest],
Field(discriminator="type"),
]
[документация]
class RegularPaymentResponse(NominalModel):
type: Literal["REGULAR"] = "REGULAR"
payment_id: str
beneficiary_id: str
account_number: str
bank_details: BankDetails
amount: Rubles
status: PaymentStatus
purpose: str
deal_id: Optional[str] = None
step_id: Optional[str] = None
recipient_id: Optional[str] = None
error_message: Optional[str] = None
operation_id: Optional[str] = None
[документация]
class TaxPaymentResponse(NominalModel):
type: Literal["TAX"] = "TAX"
payment_id: str
beneficiary_id: str
account_number: str
bank_details: BankDetails
amount: Rubles
status: PaymentStatus
purpose: str
uin: str
tax: TaxPaymentParameters
error_message: Optional[str] = None
operation_id: Optional[str] = None
PaymentResponse = Annotated[
Union[RegularPaymentResponse, TaxPaymentResponse],
Field(discriminator="type"),
]
[документация]
class PaymentListResponse(_Paged):
results: List[PaymentResponse] = Field(default_factory=list)
[документация]
class RetryPaymentResponse(NominalModel):
retry_payment_id: str
# --- Неидентифицированные пополнения ---
[документация]
class IncomingTransactionListItem(NominalModel):
account_number: str
operation_id: str
amount: Rubles
currency: Optional[str] = None
operation_amount: Optional[Rubles] = None
operation_currency: Optional[str] = None
payer_bik: Optional[str] = None
payer_kpp: Optional[str] = None
payer_inn: Optional[str] = None
payer_bank_name: Optional[str] = None
payer_bank_swift_code: Optional[str] = None
payer_account_number: Optional[str] = None
payer_corr_account_number: Optional[str] = None
payer_name: Optional[str] = None
payment_purpose: Optional[str] = None
document_number: Optional[str] = None
charge_date: Optional[datetime] = None
authorization_date: Optional[datetime] = None
transaction_date: Optional[datetime] = None
draw_date: Optional[datetime] = None
[документация]
class IncomingTransactionListResponse(_Paged):
results: List[IncomingTransactionListItem] = Field(default_factory=list)
[документация]
class AmountDistributionItem(NominalModel):
beneficiary_id: str
amount: WriteRubles
[документация]
class IdentifyIncomingTransactionRequest(NominalModel):
amount_distribution: Optional[List[AmountDistributionItem]] = None
# --- Виртуальные счета: балансы и холды ---
[документация]
class BalanceListItem(NominalModel):
beneficiary_id: str
account_number: str
amount: Rubles
amount_on_hold: Rubles
[документация]
class BalanceListResponse(_Paged):
results: List[BalanceListItem] = Field(default_factory=list)
[документация]
class HoldListItem(NominalModel):
beneficiary_id: str
account_number: str
hold_id: str
amount: Rubles
deal_id: Optional[str] = None
step_id: Optional[str] = None
recipient_id: Optional[str] = None
payment_id: Optional[str] = None
[документация]
class HoldListResponse(_Paged):
results: List[HoldListItem] = Field(default_factory=list)
# --- Виртуальные счета: переводы ---
[документация]
class CreateTransferRequest(NominalModel):
account_number: str
from_: TransferParty = Field(alias="from")
to: TransferParty
amount: WriteRubles
purpose: Optional[str] = None
[документация]
class TransferResponse(NominalModel):
transfer_id: str
account_number: str
from_: TransferParty = Field(alias="from")
to: TransferParty
amount: Rubles
type: Optional[TransferType] = None
purpose: Optional[str] = None
[документация]
class TransferListResponse(_Paged):
results: List[TransferResponse] = Field(default_factory=list)