fix: harden payment provider handling
This commit is contained in:
@@ -240,7 +240,6 @@ class HandleSbpWithdrawalWalletEventCommand:
|
|||||||
withdraw_id=withdraw_id,
|
withdraw_id=withdraw_id,
|
||||||
phone=str(withdrawal.phone or ''),
|
phone=str(withdrawal.phone or ''),
|
||||||
bank_id=str(withdrawal.bank_id or ''),
|
bank_id=str(withdrawal.bank_id or ''),
|
||||||
# amount=Decimal(str(withdrawal.rub_amount or '0')),
|
|
||||||
amount=Decimal('1'),
|
amount=Decimal('1'),
|
||||||
trace_id=str(withdrawal.trace_id or self._logger.get_trace_id()),
|
trace_id=str(withdrawal.trace_id or self._logger.get_trace_id()),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -147,5 +147,5 @@ class ClaudeKassirClient(IReceipt):
|
|||||||
return body
|
return body
|
||||||
except ApplicationException:
|
except ApplicationException:
|
||||||
raise
|
raise
|
||||||
except aiohttp.ClientError:
|
except (TimeoutError,aiohttp.ClientError):
|
||||||
raise ReceiptProviderException(message='Receipt provider unreachable')
|
raise ReceiptProviderException(message='Receipt provider unreachable')
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ class Settings(BaseSettings):
|
|||||||
CSRF_COOKIE_PATH: str = "/"
|
CSRF_COOKIE_PATH: str = "/"
|
||||||
CSRF_COOKIE_DOMAIN: str | None = None
|
CSRF_COOKIE_DOMAIN: str | None = None
|
||||||
|
|
||||||
|
CORS_ALLOW_ORIGIN_REGEX: str = r'https?://([a-z0-9-]+\.)*elcsa\.ru(:\d+)?$'
|
||||||
|
|
||||||
DOCS_USERNAME: str = "admin"
|
DOCS_USERNAME: str = "admin"
|
||||||
DOCS_PASSWORD: str = "admin"
|
DOCS_PASSWORD: str = "admin"
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import orjson
|
import orjson
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from decimal import Decimal
|
from decimal import Decimal,InvalidOperation
|
||||||
from typing import Any
|
from typing import Any
|
||||||
import aiohttp
|
import aiohttp
|
||||||
from aiohttp import BasicAuth, ClientTimeout
|
from aiohttp import BasicAuth, ClientTimeout
|
||||||
@@ -72,8 +72,10 @@ class ItPayClient(IItPayService):
|
|||||||
body_raw = response_json.get('data')
|
body_raw = response_json.get('data')
|
||||||
body = body_raw if isinstance(body_raw, dict) else response_json
|
body = body_raw if isinstance(body_raw, dict) else response_json
|
||||||
|
|
||||||
status = str(body['status']).strip().lower()
|
status = str(body.get('status') or '').strip().lower()
|
||||||
itpay_id = str(body['id'])
|
itpay_id = str(body.get('id') or '').strip()
|
||||||
|
if not status or not itpay_id:
|
||||||
|
raise PaymentProviderException(message='Payment provider response invalid')
|
||||||
|
|
||||||
if status == 'cancelled':
|
if status == 'cancelled':
|
||||||
return replace(order, status=OrderStatus.CANCELLED, itpay_id=itpay_id)
|
return replace(order, status=OrderStatus.CANCELLED, itpay_id=itpay_id)
|
||||||
@@ -82,19 +84,23 @@ class ItPayClient(IItPayService):
|
|||||||
if status == 'error':
|
if status == 'error':
|
||||||
return replace(order, status=OrderStatus.ERROR, itpay_id=itpay_id)
|
return replace(order, status=OrderStatus.ERROR, itpay_id=itpay_id)
|
||||||
|
|
||||||
qrc_id = str(body['qrc_id'])
|
qrc_id = str(body.get('qrc_id') or '').strip()
|
||||||
itpay_amount = Decimal(str(body['amount']))
|
if not qrc_id:
|
||||||
|
raise PaymentProviderException(message='Payment provider response invalid')
|
||||||
|
itpay_amount = Decimal(str(body.get('amount')))
|
||||||
|
|
||||||
created_norm = str(body['created']).replace('Z', '+00:00')
|
created_norm = str(body.get('created') or '').replace('Z', '+00:00')
|
||||||
itpay_created_at = datetime.fromisoformat(created_norm)
|
itpay_created_at = datetime.fromisoformat(created_norm)
|
||||||
|
|
||||||
payment_qr_urls = body['payment_qr_urls']
|
payment_qr_urls = body.get('payment_qr_urls')
|
||||||
if isinstance(payment_qr_urls, str):
|
if isinstance(payment_qr_urls, str):
|
||||||
payment_qr_urls = orjson.loads(payment_qr_urls)
|
payment_qr_urls = orjson.loads(payment_qr_urls)
|
||||||
|
|
||||||
payment_qr_images = body['payment_qr_images']
|
payment_qr_images = body.get('payment_qr_images')
|
||||||
if isinstance(payment_qr_images, str):
|
if isinstance(payment_qr_images, str):
|
||||||
payment_qr_images = orjson.loads(payment_qr_images)
|
payment_qr_images = orjson.loads(payment_qr_images)
|
||||||
|
if not isinstance(payment_qr_urls,dict) or not isinstance(payment_qr_images,dict):
|
||||||
|
raise PaymentProviderException(message='Payment provider response invalid')
|
||||||
|
|
||||||
return replace(
|
return replace(
|
||||||
order,
|
order,
|
||||||
@@ -111,5 +117,7 @@ class ItPayClient(IItPayService):
|
|||||||
)
|
)
|
||||||
except ApplicationException:
|
except ApplicationException:
|
||||||
raise
|
raise
|
||||||
except aiohttp.ClientError:
|
except (TimeoutError,aiohttp.ClientError):
|
||||||
raise PaymentProviderException(message='Payment provider unreachable')
|
raise PaymentProviderException(message='Payment provider unreachable')
|
||||||
|
except (KeyError,ValueError,TypeError,InvalidOperation,orjson.JSONDecodeError) as exception:
|
||||||
|
raise PaymentProviderException(message='Payment provider response invalid') from exception
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class MozenSbpClient(IMozenSbpService):
|
|||||||
return banks
|
return banks
|
||||||
except PaymentProviderException:
|
except PaymentProviderException:
|
||||||
raise
|
raise
|
||||||
except aiohttp.ClientError:
|
except (TimeoutError,aiohttp.ClientError):
|
||||||
raise PaymentProviderException(message='Mozen SBP banks provider unreachable')
|
raise PaymentProviderException(message='Mozen SBP banks provider unreachable')
|
||||||
|
|
||||||
|
|
||||||
@@ -111,5 +111,5 @@ class MozenSbpClient(IMozenSbpService):
|
|||||||
)
|
)
|
||||||
except PaymentProviderException:
|
except PaymentProviderException:
|
||||||
raise
|
raise
|
||||||
except aiohttp.ClientError:
|
except (TimeoutError,aiohttp.ClientError):
|
||||||
raise PaymentProviderException(message='Mozen SBP withdrawal provider unreachable')
|
raise PaymentProviderException(message='Mozen SBP withdrawal provider unreachable')
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ class HashService(IHashService):
|
|||||||
|
|
||||||
async def hash(self, value: str) -> str:
|
async def hash(self, value: str) -> str:
|
||||||
hashed_value = bcrypt.hashpw(value.encode(), bcrypt.gensalt())
|
hashed_value = bcrypt.hashpw(value.encode(), bcrypt.gensalt())
|
||||||
self._logger.info(f'Hash value {hashed_value.decode()}')
|
|
||||||
return hashed_value.decode()
|
return hashed_value.decode()
|
||||||
|
|
||||||
async def verify(self, hashed_value: str, plain_value: str) -> bool:
|
async def verify(self, hashed_value: str, plain_value: str) -> bool:
|
||||||
self._logger.info(f'Hash value {hashed_value[:10]}')
|
|
||||||
return bcrypt.checkpw(plain_value.encode(), hashed_value.encode())
|
return bcrypt.checkpw(plain_value.encode(), hashed_value.encode())
|
||||||
@@ -66,9 +66,14 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|||||||
|
|
||||||
app.state.jwt_key_store = jwt_store
|
app.state.jwt_key_store = jwt_store
|
||||||
app.state.jwt_keys_scheduler = jwt_scheduler
|
app.state.jwt_keys_scheduler = jwt_scheduler
|
||||||
|
try:
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
|
sched = getattr(app.state,'jwt_keys_scheduler',None)
|
||||||
|
if sched:
|
||||||
|
sched.shutdown(wait=False)
|
||||||
await app.state.redis_remote.aclose()
|
await app.state.redis_remote.aclose()
|
||||||
logger.info(f'Users service instance ended with id {instance_id}')
|
logger.info(f'Pay service instance ended with id {instance_id}')
|
||||||
|
|
||||||
|
|
||||||
app: FastAPI = FastAPI(
|
app: FastAPI = FastAPI(
|
||||||
@@ -94,7 +99,7 @@ app.include_router(sbp_withdrawal_messaging_router)
|
|||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=[],
|
allow_origins=[],
|
||||||
allow_origin_regex='.*',
|
allow_origin_regex=settings.CORS_ALLOW_ORIGIN_REGEX,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=['*'],
|
allow_methods=['*'],
|
||||||
allow_headers=['*'],
|
allow_headers=['*'],
|
||||||
|
|||||||
Reference in New Issue
Block a user