feat: add withdraw
This commit is contained in:
7
alter_sbp_withdrawals_drop_wallet_addresses.sql
Normal file
7
alter_sbp_withdrawals_drop_wallet_addresses.sql
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
ALTER TABLE sbp_withdrawals
|
||||||
|
DROP COLUMN IF EXISTS wallet_address,
|
||||||
|
DROP COLUMN IF EXISTS sender_wallet_address;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
from datetime import datetime,timezone
|
from datetime import datetime,timezone
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
import re
|
||||||
from ulid import ULID
|
from ulid import ULID
|
||||||
from src.application.abstractions import IUnitOfWork
|
from src.application.abstractions import IUnitOfWork
|
||||||
from src.application.contracts import IQueueMessanger,ILogger,IMozenSbpService,SbpBank
|
from src.application.contracts import IQueueMessanger,ILogger,IMozenSbpService,SbpBank
|
||||||
@@ -48,14 +49,30 @@ class CreateSbpWithdrawalCommand:
|
|||||||
raise BadRequestException(message='SBP bank not found')
|
raise BadRequestException(message='SBP bank not found')
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize_russian_phone(phone: str | None) -> str:
|
||||||
|
digits=re.sub(r'\D','',phone or '')
|
||||||
|
if len(digits)!=11:
|
||||||
|
raise BadRequestException(message='Russian phone number is required for SBP withdrawal')
|
||||||
|
if digits.startswith('8'):
|
||||||
|
digits=f'7{digits[1:]}'
|
||||||
|
if not digits.startswith('7'):
|
||||||
|
raise BadRequestException(message='Russian phone number is required for SBP withdrawal')
|
||||||
|
return f'+{digits}'
|
||||||
|
|
||||||
|
|
||||||
@transactional
|
@transactional
|
||||||
async def __call__(self,*,user_id: str,bank_id: str,phone: str,usdt_amount: Decimal) -> SbpWithdrawalEntity:
|
async def __call__(self,*,user_id: str,bank_id: str,usdt_amount: Decimal) -> SbpWithdrawalEntity:
|
||||||
user=await self._unit_of_work.user_repository.get(user_id)
|
user=await self._unit_of_work.user_repository.get(user_id)
|
||||||
if user is None:
|
if user is None:
|
||||||
raise NotFoundException(message='User not found')
|
raise NotFoundException(message='User not found')
|
||||||
wallet_address=str(settings.SBP_WITHDRAWAL_WALLET_ADDRESS or '').strip()
|
phone=self._normalize_russian_phone(user.phone)
|
||||||
|
wallet_address=str(settings.CRYPTO_USDT_CONTRACT_ADDRESS or '').strip()
|
||||||
if not wallet_address:
|
if not wallet_address:
|
||||||
raise ServiceUnavailableException(message='SBP withdrawal wallet address unavailable')
|
raise ServiceUnavailableException(message='USDT contract address unavailable')
|
||||||
|
sender_wallet_address=str(user.erc20 or '').strip()
|
||||||
|
if not sender_wallet_address:
|
||||||
|
raise BadRequestException(message='User ERC20 wallet address is required for SBP withdrawal')
|
||||||
trace_id=self._logger.get_trace_id()
|
trace_id=self._logger.get_trace_id()
|
||||||
withdraw_id=str(ULID())
|
withdraw_id=str(ULID())
|
||||||
bank_name=await self._bank_name(bank_id)
|
bank_name=await self._bank_name(bank_id)
|
||||||
@@ -66,7 +83,6 @@ class CreateSbpWithdrawalCommand:
|
|||||||
bank_id=bank_id,
|
bank_id=bank_id,
|
||||||
bank_name=bank_name,
|
bank_name=bank_name,
|
||||||
phone=phone,
|
phone=phone,
|
||||||
wallet_address=wallet_address,
|
|
||||||
usdt_amount=quote.usdt_amount,
|
usdt_amount=quote.usdt_amount,
|
||||||
rub_amount=quote.rub_amount,
|
rub_amount=quote.rub_amount,
|
||||||
usdt_exchange_rate=quote.usdt_exchange_rate,
|
usdt_exchange_rate=quote.usdt_exchange_rate,
|
||||||
@@ -83,6 +99,8 @@ class CreateSbpWithdrawalCommand:
|
|||||||
'usdt_amount':str(quote.usdt_amount),
|
'usdt_amount':str(quote.usdt_amount),
|
||||||
'trace_id':trace_id,
|
'trace_id':trace_id,
|
||||||
'wallet_address':wallet_address,
|
'wallet_address':wallet_address,
|
||||||
|
'sender_wallet_address':sender_wallet_address,
|
||||||
|
'receiver_wallet_address':wallet_address,
|
||||||
'withdraw_id':withdraw_id,
|
'withdraw_id':withdraw_id,
|
||||||
'message_id':message_id,
|
'message_id':message_id,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class SbpWithdrawalEntity:
|
|||||||
bank_id: str | None = None
|
bank_id: str | None = None
|
||||||
bank_name: str | None = None
|
bank_name: str | None = None
|
||||||
phone: str | None = None
|
phone: str | None = None
|
||||||
wallet_address: str | None = None
|
|
||||||
usdt_amount: Decimal | None = None
|
usdt_amount: Decimal | None = None
|
||||||
rub_amount: Decimal | None = None
|
rub_amount: Decimal | None = None
|
||||||
usdt_exchange_rate: Decimal | None = None
|
usdt_exchange_rate: Decimal | None = None
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class Settings(BaseSettings):
|
|||||||
MOZEN_MERCHANT_ID: str = ''
|
MOZEN_MERCHANT_ID: str = ''
|
||||||
MOZEN_PARK_ID: str = ''
|
MOZEN_PARK_ID: str = ''
|
||||||
MOZEN_PARK_TOKEN: str = ''
|
MOZEN_PARK_TOKEN: str = ''
|
||||||
SBP_WITHDRAWAL_WALLET_ADDRESS: str = ''
|
CRYPTO_USDT_CONTRACT_ADDRESS: str = ''
|
||||||
|
|
||||||
CLOUD_KASSIR_PUBLIC_ID: str = ''
|
CLOUD_KASSIR_PUBLIC_ID: str = ''
|
||||||
CLOUD_KASSIR_API_SECRET: str = ''
|
CLOUD_KASSIR_API_SECRET: str = ''
|
||||||
@@ -240,8 +240,13 @@ class Settings(BaseSettings):
|
|||||||
extgw_set('merchant_id', 'MOZEN_MERCHANT_ID')
|
extgw_set('merchant_id', 'MOZEN_MERCHANT_ID')
|
||||||
extgw_set('park_id', 'MOZEN_PARK_ID')
|
extgw_set('park_id', 'MOZEN_PARK_ID')
|
||||||
extgw_set('park_token', 'MOZEN_PARK_TOKEN')
|
extgw_set('park_token', 'MOZEN_PARK_TOKEN')
|
||||||
extgw_set('wallet_address', 'SBP_WITHDRAWAL_WALLET_ADDRESS')
|
|
||||||
extgw_set('usdt_wallet_address', 'SBP_WITHDRAWAL_WALLET_ADDRESS')
|
crypto = read_secret_optional('crypto')
|
||||||
|
if crypto:
|
||||||
|
crypto_ci = {str(k).lower(): v for k, v in crypto.items()}
|
||||||
|
usdt_contract_address = crypto_ci.get('usdt_contract_address')
|
||||||
|
if usdt_contract_address is not None and str(usdt_contract_address).strip():
|
||||||
|
data['CRYPTO_USDT_CONTRACT_ADDRESS'] = str(usdt_contract_address).strip()
|
||||||
|
|
||||||
keydb = read_secret('keydb')
|
keydb = read_secret('keydb')
|
||||||
k_ci = {str(k).lower(): v for k, v in keydb.items()}
|
k_ci = {str(k).lower(): v for k, v in keydb.items()}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ class SbpWithdrawal(Base,UlidPrimaryKeyMixin,AuditTimestampsMixin):
|
|||||||
bank_id: Mapped[str]=mapped_column(String(32),nullable=False,index=True)
|
bank_id: Mapped[str]=mapped_column(String(32),nullable=False,index=True)
|
||||||
bank_name: Mapped[str]=mapped_column(String(255),nullable=False)
|
bank_name: Mapped[str]=mapped_column(String(255),nullable=False)
|
||||||
phone: Mapped[str]=mapped_column(String(32),nullable=False)
|
phone: Mapped[str]=mapped_column(String(32),nullable=False)
|
||||||
wallet_address: Mapped[str]=mapped_column(String(255),nullable=False)
|
|
||||||
usdt_amount: Mapped[Decimal]=mapped_column(Numeric(38,2),nullable=False)
|
usdt_amount: Mapped[Decimal]=mapped_column(Numeric(38,2),nullable=False)
|
||||||
rub_amount: Mapped[Decimal]=mapped_column(Numeric(38,2),nullable=False)
|
rub_amount: Mapped[Decimal]=mapped_column(Numeric(38,2),nullable=False)
|
||||||
usdt_exchange_rate: Mapped[Decimal]=mapped_column(Numeric(38,2),nullable=False)
|
usdt_exchange_rate: Mapped[Decimal]=mapped_column(Numeric(38,2),nullable=False)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ class SbpWithdrawalRepository(ISbpWithdrawalRepository):
|
|||||||
bank_id=model.bank_id,
|
bank_id=model.bank_id,
|
||||||
bank_name=model.bank_name,
|
bank_name=model.bank_name,
|
||||||
phone=model.phone,
|
phone=model.phone,
|
||||||
wallet_address=model.wallet_address,
|
|
||||||
usdt_amount=model.usdt_amount,
|
usdt_amount=model.usdt_amount,
|
||||||
rub_amount=model.rub_amount,
|
rub_amount=model.rub_amount,
|
||||||
usdt_exchange_rate=model.usdt_exchange_rate,
|
usdt_exchange_rate=model.usdt_exchange_rate,
|
||||||
@@ -48,7 +47,6 @@ class SbpWithdrawalRepository(ISbpWithdrawalRepository):
|
|||||||
'bank_id':withdrawal.bank_id,
|
'bank_id':withdrawal.bank_id,
|
||||||
'bank_name':withdrawal.bank_name,
|
'bank_name':withdrawal.bank_name,
|
||||||
'phone':withdrawal.phone,
|
'phone':withdrawal.phone,
|
||||||
'wallet_address':withdrawal.wallet_address,
|
|
||||||
'usdt_amount':withdrawal.usdt_amount,
|
'usdt_amount':withdrawal.usdt_amount,
|
||||||
'rub_amount':withdrawal.rub_amount,
|
'rub_amount':withdrawal.rub_amount,
|
||||||
'usdt_exchange_rate':withdrawal.usdt_exchange_rate,
|
'usdt_exchange_rate':withdrawal.usdt_exchange_rate,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from src.presentation.dependencies.commands import get_crypto_transfer_completed
|
|||||||
from src.presentation.dependencies.logger import get_logger
|
from src.presentation.dependencies.logger import get_logger
|
||||||
|
|
||||||
|
|
||||||
crypto_transfer_router=RabbitRouter(settings.RABBIT_URL)
|
crypto_transfer_router=RabbitRouter(settings.RABBIT_URL,schema_url=None)
|
||||||
|
|
||||||
|
|
||||||
class CryptoTransferCompletedMessage(BaseModel):
|
class CryptoTransferCompletedMessage(BaseModel):
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from src.presentation.dependencies.commands import get_sbp_withdrawal_wallet_eve
|
|||||||
from src.presentation.dependencies.logger import get_logger
|
from src.presentation.dependencies.logger import get_logger
|
||||||
|
|
||||||
|
|
||||||
sbp_withdrawal_messaging_router=RabbitRouter(settings.RABBIT_URL)
|
sbp_withdrawal_messaging_router=RabbitRouter(settings.RABBIT_URL,schema_url=None)
|
||||||
|
|
||||||
|
|
||||||
class SbpWithdrawalWalletEventMessage(BaseModel):
|
class SbpWithdrawalWalletEventMessage(BaseModel):
|
||||||
|
|||||||
@@ -119,7 +119,6 @@ def _withdrawal_operation_response(withdrawal: SbpWithdrawalEntity) -> SbpWithdr
|
|||||||
bank_id=withdrawal.bank_id,
|
bank_id=withdrawal.bank_id,
|
||||||
bank_name=withdrawal.bank_name,
|
bank_name=withdrawal.bank_name,
|
||||||
phone=withdrawal.phone,
|
phone=withdrawal.phone,
|
||||||
wallet_address=withdrawal.wallet_address,
|
|
||||||
usdt_amount=str(withdrawal.usdt_amount) if withdrawal.usdt_amount is not None else None,
|
usdt_amount=str(withdrawal.usdt_amount) if withdrawal.usdt_amount is not None else None,
|
||||||
rub_amount=str(withdrawal.rub_amount) if withdrawal.rub_amount is not None else None,
|
rub_amount=str(withdrawal.rub_amount) if withdrawal.rub_amount is not None else None,
|
||||||
usdt_exchange_rate=str(withdrawal.usdt_exchange_rate) if withdrawal.usdt_exchange_rate is not None else None,
|
usdt_exchange_rate=str(withdrawal.usdt_exchange_rate) if withdrawal.usdt_exchange_rate is not None else None,
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ def _withdrawal_response(withdrawal: SbpWithdrawalEntity) -> SbpWithdrawalRespon
|
|||||||
bank_id=withdrawal.bank_id,
|
bank_id=withdrawal.bank_id,
|
||||||
bank_name=withdrawal.bank_name,
|
bank_name=withdrawal.bank_name,
|
||||||
phone=withdrawal.phone,
|
phone=withdrawal.phone,
|
||||||
wallet_address=withdrawal.wallet_address,
|
|
||||||
usdt_amount=str(withdrawal.usdt_amount) if withdrawal.usdt_amount is not None else None,
|
usdt_amount=str(withdrawal.usdt_amount) if withdrawal.usdt_amount is not None else None,
|
||||||
rub_amount=str(withdrawal.rub_amount) if withdrawal.rub_amount is not None else None,
|
rub_amount=str(withdrawal.rub_amount) if withdrawal.rub_amount is not None else None,
|
||||||
usdt_exchange_rate=str(withdrawal.usdt_exchange_rate) if withdrawal.usdt_exchange_rate is not None else None,
|
usdt_exchange_rate=str(withdrawal.usdt_exchange_rate) if withdrawal.usdt_exchange_rate is not None else None,
|
||||||
@@ -84,7 +83,6 @@ async def create_sbp_withdrawal(
|
|||||||
withdrawal=await command(
|
withdrawal=await command(
|
||||||
user_id=auth.user_id,
|
user_id=auth.user_id,
|
||||||
bank_id=payload.bank_id,
|
bank_id=payload.bank_id,
|
||||||
phone=payload.phone,
|
|
||||||
usdt_amount=payload.usdt_amount,
|
usdt_amount=payload.usdt_amount,
|
||||||
)
|
)
|
||||||
return CreateSbpWithdrawalResponse(status_code=201,withdrawal=_withdrawal_response(withdrawal))
|
return CreateSbpWithdrawalResponse(status_code=201,withdrawal=_withdrawal_response(withdrawal))
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ class SbpWithdrawalOperationResponse(BaseModel):
|
|||||||
bank_id: str | None = None
|
bank_id: str | None = None
|
||||||
bank_name: str | None = None
|
bank_name: str | None = None
|
||||||
phone: str | None = None
|
phone: str | None = None
|
||||||
wallet_address: str | None = None
|
|
||||||
usdt_amount: str | None = None
|
usdt_amount: str | None = None
|
||||||
rub_amount: str | None = None
|
rub_amount: str | None = None
|
||||||
usdt_exchange_rate: str | None = None
|
usdt_exchange_rate: str | None = None
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ class SbpBanksResponse(BaseModel):
|
|||||||
|
|
||||||
class CreateSbpWithdrawal(BaseModel):
|
class CreateSbpWithdrawal(BaseModel):
|
||||||
bank_id: str = Field(min_length=1,max_length=32)
|
bank_id: str = Field(min_length=1,max_length=32)
|
||||||
phone: str = Field(min_length=5,max_length=32)
|
|
||||||
usdt_amount: Decimal = Field(ge=Decimal('100'),le=Decimal('5000'),decimal_places=2,max_digits=20)
|
usdt_amount: Decimal = Field(ge=Decimal('100'),le=Decimal('5000'),decimal_places=2,max_digits=20)
|
||||||
|
|
||||||
|
|
||||||
@@ -27,7 +26,6 @@ class SbpWithdrawalResponse(BaseModel):
|
|||||||
bank_id: str | None = None
|
bank_id: str | None = None
|
||||||
bank_name: str | None = None
|
bank_name: str | None = None
|
||||||
phone: str | None = None
|
phone: str | None = None
|
||||||
wallet_address: str | None = None
|
|
||||||
usdt_amount: str | None = None
|
usdt_amount: str | None = None
|
||||||
rub_amount: str | None = None
|
rub_amount: str | None = None
|
||||||
usdt_exchange_rate: str | None = None
|
usdt_exchange_rate: str | None = None
|
||||||
|
|||||||
Reference in New Issue
Block a user