feat: integrate Overpay payment provider (pay.overpay.io)

Full integration across bot, cabinet, admin panel and landing pages:

- Config: 16 OVERPAY_* settings (API URL, credentials, P12 cert path,
  project ID, currency, amount limits, webhook path, payment methods)
- Service: overpay_service.py with httpx mTLS (P12 cert) + Basic Auth,
  create_payment, get_payment, refund_payment methods
- Payment mixin: OverpayPaymentMixin with create, webhook processing,
  finalize (balance credit + notifications), status check
- Model: OverpayPayment table + OVERPAY enum value
- CRUD: 9 standard operations (create, get_by_*, for_update, link)
- Bot handler: start_overpay_topup + process_overpay_payment_amount
- Handler registration: route_payment_by_method + callback registration
- Webhook: POST /overpay-webhook with DB-based anti-spoofing validation
- Cabinet: balance topup + guest payment dispatch
- Keyboard: payment method button in bot
- Admin: settings category + test payment button
- Infrastructure: payment_method_config_service, system_settings_service,
  payment_verification_service, payment_search_service
- Migration 0064: create overpay_payments table

Overpay API specifics: amount as string "100.00" (not kopeks),
success status "charged", HPP redirect via resultUrl
This commit is contained in:
Fringg
2026-04-23 04:36:56 +03:00
parent 6f87563789
commit 2c3ffc8c8a
19 changed files with 1654 additions and 1 deletions
+62
View File
@@ -34,6 +34,7 @@ from app.services.payment.aurapay import AuraPayPaymentMixin
from app.services.payment.cloudpayments import CloudPaymentsPaymentMixin
from app.services.payment.freekassa import FreekassaPaymentMixin
from app.services.payment.kassa_ai import KassaAiPaymentMixin
from app.services.payment.overpay import OverpayPaymentMixin
from app.services.payment.paypear import PayPearPaymentMixin
from app.services.payment.riopay import RioPayPaymentMixin
from app.services.payment.rollypay import RollyPayPaymentMixin
@@ -408,6 +409,44 @@ async def link_rollypay_payment_to_transaction(*args, **kwargs):
return await rollypay_crud.link_rollypay_payment_to_transaction(*args, **kwargs)
# --- Overpay CRUD wrappers ---
async def create_overpay_payment(*args, **kwargs):
overpay_crud = import_module('app.database.crud.overpay')
return await overpay_crud.create_overpay_payment(*args, **kwargs)
async def get_overpay_payment_by_order_id(*args, **kwargs):
overpay_crud = import_module('app.database.crud.overpay')
return await overpay_crud.get_overpay_payment_by_order_id(*args, **kwargs)
async def get_overpay_payment_by_overpay_id(*args, **kwargs):
overpay_crud = import_module('app.database.crud.overpay')
return await overpay_crud.get_overpay_payment_by_overpay_id(*args, **kwargs)
async def get_overpay_payment_by_id(*args, **kwargs):
overpay_crud = import_module('app.database.crud.overpay')
return await overpay_crud.get_overpay_payment_by_id(*args, **kwargs)
async def get_overpay_payment_by_id_for_update(*args, **kwargs):
overpay_crud = import_module('app.database.crud.overpay')
return await overpay_crud.get_overpay_payment_by_id_for_update(*args, **kwargs)
async def update_overpay_payment_status(*args, **kwargs):
overpay_crud = import_module('app.database.crud.overpay')
return await overpay_crud.update_overpay_payment_status(*args, **kwargs)
async def link_overpay_payment_to_transaction(*args, **kwargs):
overpay_crud = import_module('app.database.crud.overpay')
return await overpay_crud.link_overpay_payment_to_transaction(*args, **kwargs)
async def create_aurapay_payment(*args, **kwargs):
aurapay_crud = import_module('app.database.crud.aurapay')
return await aurapay_crud.create_aurapay_payment(*args, **kwargs)
@@ -468,6 +507,7 @@ class PaymentService(
SeverPayPaymentMixin,
PayPearPaymentMixin,
RollyPayPaymentMixin,
OverpayPaymentMixin,
AuraPayPaymentMixin,
):
"""Основной интерфейс платежей, делегирующий работу специализированным mixin-ам."""
@@ -932,6 +972,28 @@ class PaymentService(
}
return None
# --- Overpay ----------------------------------------------------------
if payment_method == 'overpay':
if not settings.is_overpay_enabled():
logger.warning('Overpay is not enabled, cannot create guest payment')
return None
result = await self.create_overpay_payment(
db=db,
user_id=None,
amount_kopeks=amount_kopeks,
description=description,
return_url=return_url,
)
if result:
await _patch_guest_metadata(result['local_payment_id'], 'overpay')
return {
'payment_url': result.get('payment_url'),
'payment_id': result.get('overpay_payment_id') or result.get('order_id'),
'provider': 'overpay',
}
return None
# --- AuraPay ----------------------------------------------------------
if payment_method == 'aurapay':
if not settings.is_aurapay_enabled():