feat: implement three-tier module classification and framework layer
Module Classification: - Core (4): core, tenancy, cms, customers - always enabled - Optional (7): payments, billing, inventory, orders, marketplace, analytics, messaging - Internal (2): dev-tools, monitoring - admin-only Key Changes: - Rename platform-admin module to tenancy - Promote CMS and Customers to core modules - Create new payments module (gateway abstractions) - Add billing→payments and orders→payments dependencies - Mark dev-tools and monitoring as internal modules New Infrastructure: - app/modules/events.py: Module event bus (ENABLED, DISABLED, STARTUP, SHUTDOWN) - app/modules/migrations.py: Module-specific migration discovery - app/core/observability.py: Health checks, Prometheus metrics, Sentry integration Enhanced ModuleDefinition: - version, is_internal, permissions - config_schema, default_config - migrations_path - Lifecycle hooks: on_enable, on_disable, on_startup, health_check New Registry Functions: - get_optional_module_codes(), get_internal_module_codes() - is_core_module(), is_internal_module() - get_modules_by_tier(), get_module_tier() Migrations: - zc*: Rename platform-admin to tenancy - zd*: Ensure CMS and Customers enabled for all platforms Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
32
app/modules/payments/__init__.py
Normal file
32
app/modules/payments/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# app/modules/payments/__init__.py
|
||||
"""
|
||||
Payments Module - Payment gateway integrations.
|
||||
|
||||
This module provides low-level payment gateway abstractions:
|
||||
- Gateway integrations (Stripe, PayPal, Bank Transfer, etc.)
|
||||
- Payment processing and refunds
|
||||
- Payment method storage and management
|
||||
- Transaction records
|
||||
|
||||
This module is used by:
|
||||
- billing: Platform subscriptions and vendor invoices
|
||||
- orders: Customer checkout and order payments
|
||||
|
||||
Routes:
|
||||
- Admin: /api/v1/admin/payments/*
|
||||
- Vendor: /api/v1/vendor/payments/*
|
||||
|
||||
Menu Items:
|
||||
- Admin: payments (payment configuration)
|
||||
- Vendor: payment-methods (stored payment methods)
|
||||
"""
|
||||
|
||||
from app.modules.payments.definition import payments_module
|
||||
|
||||
|
||||
def get_payments_module():
|
||||
"""Lazy getter to avoid circular imports."""
|
||||
return payments_module
|
||||
|
||||
|
||||
__all__ = ["payments_module", "get_payments_module"]
|
||||
79
app/modules/payments/definition.py
Normal file
79
app/modules/payments/definition.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# app/modules/payments/definition.py
|
||||
"""
|
||||
Payments module definition.
|
||||
|
||||
Defines the payments module including its features, menu items,
|
||||
and route configurations.
|
||||
|
||||
The payments module provides gateway abstractions that can be used by:
|
||||
- billing module: For platform subscriptions and invoices
|
||||
- orders module: For customer checkout payments
|
||||
|
||||
This separation allows:
|
||||
1. Using payments standalone (e.g., one-time payments without subscriptions)
|
||||
2. Billing without orders (platform subscription only)
|
||||
3. Orders without billing (customer payments only)
|
||||
"""
|
||||
|
||||
from app.modules.base import ModuleDefinition
|
||||
from models.database.admin_menu_config import FrontendType
|
||||
|
||||
|
||||
def _get_admin_router():
|
||||
"""Lazy import of admin router to avoid circular imports."""
|
||||
from app.modules.payments.routes.admin import admin_router
|
||||
|
||||
return admin_router
|
||||
|
||||
|
||||
def _get_vendor_router():
|
||||
"""Lazy import of vendor router to avoid circular imports."""
|
||||
from app.modules.payments.routes.vendor import vendor_router
|
||||
|
||||
return vendor_router
|
||||
|
||||
|
||||
# Payments module definition
|
||||
payments_module = ModuleDefinition(
|
||||
code="payments",
|
||||
name="Payment Gateways",
|
||||
description=(
|
||||
"Payment gateway integrations for Stripe, PayPal, and bank transfers. "
|
||||
"Provides payment processing, refunds, and payment method management."
|
||||
),
|
||||
version="1.0.0",
|
||||
features=[
|
||||
"payment_processing", # Process payments
|
||||
"payment_refunds", # Issue refunds
|
||||
"payment_methods", # Store payment methods
|
||||
"stripe_gateway", # Stripe integration
|
||||
"paypal_gateway", # PayPal integration
|
||||
"bank_transfer", # Bank transfer support
|
||||
"transaction_history", # Transaction records
|
||||
],
|
||||
menu_items={
|
||||
FrontendType.ADMIN: [
|
||||
"payment-gateways", # Configure payment gateways
|
||||
],
|
||||
FrontendType.VENDOR: [
|
||||
"payment-methods", # Manage stored payment methods
|
||||
],
|
||||
},
|
||||
is_core=False,
|
||||
is_internal=False,
|
||||
)
|
||||
|
||||
|
||||
def get_payments_module_with_routers() -> ModuleDefinition:
|
||||
"""
|
||||
Get payments module with routers attached.
|
||||
|
||||
This function attaches the routers lazily to avoid circular imports
|
||||
during module initialization.
|
||||
"""
|
||||
payments_module.admin_router = _get_admin_router()
|
||||
payments_module.vendor_router = _get_vendor_router()
|
||||
return payments_module
|
||||
|
||||
|
||||
__all__ = ["payments_module", "get_payments_module_with_routers"]
|
||||
11
app/modules/payments/models/__init__.py
Normal file
11
app/modules/payments/models/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# app/modules/payments/models/__init__.py
|
||||
"""
|
||||
Payments module database models.
|
||||
|
||||
Note: These models will be created in a future migration.
|
||||
For now, payment data may be stored in the billing module's tables.
|
||||
"""
|
||||
|
||||
# TODO: Add Payment, PaymentMethod, Transaction models
|
||||
|
||||
__all__: list[str] = []
|
||||
7
app/modules/payments/routes/__init__.py
Normal file
7
app/modules/payments/routes/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# app/modules/payments/routes/__init__.py
|
||||
"""Payments module routes."""
|
||||
|
||||
from app.modules.payments.routes.admin import admin_router
|
||||
from app.modules.payments.routes.vendor import vendor_router
|
||||
|
||||
__all__ = ["admin_router", "vendor_router"]
|
||||
44
app/modules/payments/routes/admin.py
Normal file
44
app/modules/payments/routes/admin.py
Normal file
@@ -0,0 +1,44 @@
|
||||
# app/modules/payments/routes/admin.py
|
||||
"""
|
||||
Admin routes for payments module.
|
||||
|
||||
Provides routes for:
|
||||
- Payment gateway configuration
|
||||
- Transaction monitoring
|
||||
- Refund management
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
admin_router = APIRouter(prefix="/payments", tags=["Payments (Admin)"])
|
||||
|
||||
|
||||
@admin_router.get("/gateways")
|
||||
async def list_gateways():
|
||||
"""List configured payment gateways."""
|
||||
# TODO: Implement gateway listing
|
||||
return {
|
||||
"gateways": [
|
||||
{"code": "stripe", "name": "Stripe", "enabled": True},
|
||||
{"code": "paypal", "name": "PayPal", "enabled": False},
|
||||
{"code": "bank_transfer", "name": "Bank Transfer", "enabled": True},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@admin_router.get("/transactions")
|
||||
async def list_transactions():
|
||||
"""List recent transactions across all gateways."""
|
||||
# TODO: Implement transaction listing
|
||||
return {"transactions": [], "total": 0}
|
||||
|
||||
|
||||
@admin_router.post("/refunds/{transaction_id}")
|
||||
async def issue_refund(transaction_id: str, amount: float | None = None):
|
||||
"""Issue a refund for a transaction."""
|
||||
# TODO: Implement refund logic
|
||||
return {
|
||||
"status": "pending",
|
||||
"transaction_id": transaction_id,
|
||||
"refund_amount": amount,
|
||||
}
|
||||
40
app/modules/payments/routes/vendor.py
Normal file
40
app/modules/payments/routes/vendor.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# app/modules/payments/routes/vendor.py
|
||||
"""
|
||||
Vendor routes for payments module.
|
||||
|
||||
Provides routes for:
|
||||
- Payment method management
|
||||
- Transaction history
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter
|
||||
|
||||
vendor_router = APIRouter(prefix="/payments", tags=["Payments (Vendor)"])
|
||||
|
||||
|
||||
@vendor_router.get("/methods")
|
||||
async def list_payment_methods():
|
||||
"""List saved payment methods for the vendor."""
|
||||
# TODO: Implement payment method listing
|
||||
return {"payment_methods": []}
|
||||
|
||||
|
||||
@vendor_router.post("/methods")
|
||||
async def add_payment_method():
|
||||
"""Add a new payment method."""
|
||||
# TODO: Implement payment method creation
|
||||
return {"status": "created", "id": "pm_xxx"}
|
||||
|
||||
|
||||
@vendor_router.delete("/methods/{method_id}")
|
||||
async def remove_payment_method(method_id: str):
|
||||
"""Remove a saved payment method."""
|
||||
# TODO: Implement payment method deletion
|
||||
return {"status": "deleted", "id": method_id}
|
||||
|
||||
|
||||
@vendor_router.get("/transactions")
|
||||
async def list_vendor_transactions():
|
||||
"""List transactions for the vendor."""
|
||||
# TODO: Implement transaction listing
|
||||
return {"transactions": [], "total": 0}
|
||||
93
app/modules/payments/schemas/__init__.py
Normal file
93
app/modules/payments/schemas/__init__.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# app/modules/payments/schemas/__init__.py
|
||||
"""
|
||||
Payments module Pydantic schemas.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
|
||||
class PaymentRequest(BaseModel):
|
||||
"""Request to process a payment."""
|
||||
|
||||
amount: int = Field(..., gt=0, description="Amount in cents")
|
||||
currency: str = Field(default="EUR", max_length=3)
|
||||
payment_method_id: str | None = None
|
||||
gateway: str = Field(default="stripe")
|
||||
description: str | None = None
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class PaymentResponse(BaseModel):
|
||||
"""Response from a payment operation."""
|
||||
|
||||
success: bool
|
||||
transaction_id: str | None = None
|
||||
gateway: str | None = None
|
||||
status: str
|
||||
amount: int
|
||||
currency: str
|
||||
error_message: str | None = None
|
||||
created_at: datetime | None = None
|
||||
|
||||
|
||||
class RefundRequest(BaseModel):
|
||||
"""Request to issue a refund."""
|
||||
|
||||
transaction_id: str
|
||||
amount: int | None = Field(None, gt=0, description="Amount in cents, None for full refund")
|
||||
reason: str | None = None
|
||||
|
||||
|
||||
class RefundResponse(BaseModel):
|
||||
"""Response from a refund operation."""
|
||||
|
||||
success: bool
|
||||
refund_id: str | None = None
|
||||
transaction_id: str
|
||||
amount: int
|
||||
status: str
|
||||
error_message: str | None = None
|
||||
|
||||
|
||||
class PaymentMethodCreate(BaseModel):
|
||||
"""Request to create a payment method."""
|
||||
|
||||
gateway: str = "stripe"
|
||||
token: str # Gateway-specific token
|
||||
is_default: bool = False
|
||||
|
||||
|
||||
class PaymentMethodResponse(BaseModel):
|
||||
"""Response for a payment method."""
|
||||
|
||||
id: str
|
||||
gateway: str
|
||||
type: str # card, bank_account, etc.
|
||||
last4: str | None = None
|
||||
is_default: bool
|
||||
created_at: datetime
|
||||
|
||||
|
||||
class GatewayResponse(BaseModel):
|
||||
"""Response for gateway info."""
|
||||
|
||||
code: str
|
||||
name: str
|
||||
status: str
|
||||
enabled: bool
|
||||
supports_refunds: bool
|
||||
supports_recurring: bool
|
||||
supported_currencies: list[str]
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PaymentRequest",
|
||||
"PaymentResponse",
|
||||
"RefundRequest",
|
||||
"RefundResponse",
|
||||
"PaymentMethodCreate",
|
||||
"PaymentMethodResponse",
|
||||
"GatewayResponse",
|
||||
]
|
||||
13
app/modules/payments/services/__init__.py
Normal file
13
app/modules/payments/services/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# app/modules/payments/services/__init__.py
|
||||
"""
|
||||
Payments module services.
|
||||
|
||||
Provides:
|
||||
- PaymentService: Core payment processing
|
||||
- GatewayService: Gateway abstraction layer
|
||||
"""
|
||||
|
||||
from app.modules.payments.services.payment_service import PaymentService
|
||||
from app.modules.payments.services.gateway_service import GatewayService
|
||||
|
||||
__all__ = ["PaymentService", "GatewayService"]
|
||||
351
app/modules/payments/services/gateway_service.py
Normal file
351
app/modules/payments/services/gateway_service.py
Normal file
@@ -0,0 +1,351 @@
|
||||
# app/modules/payments/services/gateway_service.py
|
||||
"""
|
||||
Gateway service for managing payment gateway configurations.
|
||||
|
||||
This service handles:
|
||||
- Gateway configuration and credentials
|
||||
- Gateway health checks
|
||||
- Gateway-specific operations
|
||||
|
||||
Each gateway has its own implementation that conforms to the
|
||||
gateway protocol.
|
||||
|
||||
Usage:
|
||||
from app.modules.payments.services import GatewayService
|
||||
|
||||
gateway_service = GatewayService()
|
||||
|
||||
# Get available gateways
|
||||
gateways = gateway_service.get_available_gateways()
|
||||
|
||||
# Check gateway status
|
||||
status = await gateway_service.check_gateway_health("stripe")
|
||||
"""
|
||||
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, Protocol
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GatewayStatus(str, Enum):
|
||||
"""Gateway operational status."""
|
||||
|
||||
ACTIVE = "active"
|
||||
INACTIVE = "inactive"
|
||||
ERROR = "error"
|
||||
MAINTENANCE = "maintenance"
|
||||
|
||||
|
||||
@dataclass
|
||||
class GatewayInfo:
|
||||
"""Information about a payment gateway."""
|
||||
|
||||
code: str
|
||||
name: str
|
||||
status: GatewayStatus
|
||||
enabled: bool
|
||||
supports_refunds: bool = True
|
||||
supports_recurring: bool = False
|
||||
supported_currencies: list[str] | None = None
|
||||
config: dict[str, Any] | None = None
|
||||
|
||||
|
||||
class GatewayProtocol(Protocol):
|
||||
"""Protocol that all gateway implementations must follow."""
|
||||
|
||||
@property
|
||||
def code(self) -> str:
|
||||
"""Gateway code identifier."""
|
||||
...
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Gateway display name."""
|
||||
...
|
||||
|
||||
async def process_payment(
|
||||
self,
|
||||
amount: int,
|
||||
currency: str,
|
||||
payment_method: str,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Process a payment."""
|
||||
...
|
||||
|
||||
async def refund(
|
||||
self,
|
||||
transaction_id: str,
|
||||
amount: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Issue a refund."""
|
||||
...
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
"""Check if gateway is operational."""
|
||||
...
|
||||
|
||||
|
||||
class BaseGateway(ABC):
|
||||
"""Base class for gateway implementations."""
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def code(self) -> str:
|
||||
"""Gateway code identifier."""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""Gateway display name."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def process_payment(
|
||||
self,
|
||||
amount: int,
|
||||
currency: str,
|
||||
payment_method: str,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Process a payment."""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def refund(
|
||||
self,
|
||||
transaction_id: str,
|
||||
amount: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Issue a refund."""
|
||||
pass
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
"""Check if gateway is operational."""
|
||||
return True
|
||||
|
||||
|
||||
class StripeGateway(BaseGateway):
|
||||
"""Stripe payment gateway implementation."""
|
||||
|
||||
@property
|
||||
def code(self) -> str:
|
||||
return "stripe"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "Stripe"
|
||||
|
||||
async def process_payment(
|
||||
self,
|
||||
amount: int,
|
||||
currency: str,
|
||||
payment_method: str,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Process a payment through Stripe."""
|
||||
# TODO: Implement Stripe payment processing
|
||||
logger.info(f"Processing Stripe payment: {amount} {currency}")
|
||||
return {
|
||||
"success": True,
|
||||
"transaction_id": f"pi_mock_{amount}",
|
||||
"gateway": self.code,
|
||||
}
|
||||
|
||||
async def refund(
|
||||
self,
|
||||
transaction_id: str,
|
||||
amount: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Issue a refund through Stripe."""
|
||||
# TODO: Implement Stripe refund
|
||||
logger.info(f"Processing Stripe refund for {transaction_id}")
|
||||
return {
|
||||
"success": True,
|
||||
"refund_id": f"re_mock_{transaction_id}",
|
||||
}
|
||||
|
||||
|
||||
class PayPalGateway(BaseGateway):
|
||||
"""PayPal payment gateway implementation."""
|
||||
|
||||
@property
|
||||
def code(self) -> str:
|
||||
return "paypal"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "PayPal"
|
||||
|
||||
async def process_payment(
|
||||
self,
|
||||
amount: int,
|
||||
currency: str,
|
||||
payment_method: str,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Process a payment through PayPal."""
|
||||
# TODO: Implement PayPal payment processing
|
||||
logger.info(f"Processing PayPal payment: {amount} {currency}")
|
||||
return {
|
||||
"success": True,
|
||||
"transaction_id": f"paypal_mock_{amount}",
|
||||
"gateway": self.code,
|
||||
}
|
||||
|
||||
async def refund(
|
||||
self,
|
||||
transaction_id: str,
|
||||
amount: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Issue a refund through PayPal."""
|
||||
# TODO: Implement PayPal refund
|
||||
logger.info(f"Processing PayPal refund for {transaction_id}")
|
||||
return {
|
||||
"success": True,
|
||||
"refund_id": f"paypal_refund_{transaction_id}",
|
||||
}
|
||||
|
||||
|
||||
class BankTransferGateway(BaseGateway):
|
||||
"""Bank transfer gateway implementation."""
|
||||
|
||||
@property
|
||||
def code(self) -> str:
|
||||
return "bank_transfer"
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return "Bank Transfer"
|
||||
|
||||
async def process_payment(
|
||||
self,
|
||||
amount: int,
|
||||
currency: str,
|
||||
payment_method: str,
|
||||
**kwargs: Any,
|
||||
) -> dict[str, Any]:
|
||||
"""Record a bank transfer payment (manual verification)."""
|
||||
logger.info(f"Recording bank transfer: {amount} {currency}")
|
||||
return {
|
||||
"success": True,
|
||||
"transaction_id": f"bt_mock_{amount}",
|
||||
"gateway": self.code,
|
||||
"status": "pending_verification",
|
||||
}
|
||||
|
||||
async def refund(
|
||||
self,
|
||||
transaction_id: str,
|
||||
amount: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Record a bank transfer refund (manual process)."""
|
||||
logger.info(f"Recording bank transfer refund for {transaction_id}")
|
||||
return {
|
||||
"success": True,
|
||||
"refund_id": f"bt_refund_{transaction_id}",
|
||||
"status": "pending_manual",
|
||||
}
|
||||
|
||||
|
||||
class GatewayService:
|
||||
"""
|
||||
Service for managing payment gateway configurations.
|
||||
|
||||
Provides a registry of available gateways and methods for
|
||||
gateway operations.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._gateways: dict[str, BaseGateway] = {
|
||||
"stripe": StripeGateway(),
|
||||
"paypal": PayPalGateway(),
|
||||
"bank_transfer": BankTransferGateway(),
|
||||
}
|
||||
self._enabled_gateways: set[str] = {"stripe", "bank_transfer"}
|
||||
|
||||
def get_gateway(self, code: str) -> BaseGateway | None:
|
||||
"""Get a gateway by code."""
|
||||
return self._gateways.get(code)
|
||||
|
||||
def get_available_gateways(self) -> list[GatewayInfo]:
|
||||
"""Get list of all available gateways with their status."""
|
||||
result = []
|
||||
for code, gateway in self._gateways.items():
|
||||
result.append(
|
||||
GatewayInfo(
|
||||
code=code,
|
||||
name=gateway.name,
|
||||
status=GatewayStatus.ACTIVE if code in self._enabled_gateways else GatewayStatus.INACTIVE,
|
||||
enabled=code in self._enabled_gateways,
|
||||
supports_refunds=True,
|
||||
supports_recurring=code == "stripe",
|
||||
supported_currencies=["EUR", "USD", "GBP"] if code != "bank_transfer" else ["EUR"],
|
||||
)
|
||||
)
|
||||
return result
|
||||
|
||||
def enable_gateway(self, code: str) -> bool:
|
||||
"""Enable a gateway."""
|
||||
if code in self._gateways:
|
||||
self._enabled_gateways.add(code)
|
||||
logger.info(f"Enabled gateway: {code}")
|
||||
return True
|
||||
return False
|
||||
|
||||
def disable_gateway(self, code: str) -> bool:
|
||||
"""Disable a gateway."""
|
||||
if code in self._enabled_gateways:
|
||||
self._enabled_gateways.remove(code)
|
||||
logger.info(f"Disabled gateway: {code}")
|
||||
return True
|
||||
return False
|
||||
|
||||
async def check_gateway_health(self, code: str) -> dict[str, Any]:
|
||||
"""Check the health of a specific gateway."""
|
||||
gateway = self._gateways.get(code)
|
||||
if not gateway:
|
||||
return {"status": "unknown", "message": f"Gateway {code} not found"}
|
||||
|
||||
try:
|
||||
is_healthy = await gateway.health_check()
|
||||
return {
|
||||
"status": "healthy" if is_healthy else "unhealthy",
|
||||
"gateway": code,
|
||||
}
|
||||
except Exception as e:
|
||||
logger.exception(f"Gateway health check failed: {code}")
|
||||
return {
|
||||
"status": "error",
|
||||
"gateway": code,
|
||||
"message": str(e),
|
||||
}
|
||||
|
||||
async def check_all_gateways(self) -> list[dict[str, Any]]:
|
||||
"""Check health of all enabled gateways."""
|
||||
results = []
|
||||
for code in self._enabled_gateways:
|
||||
result = await self.check_gateway_health(code)
|
||||
results.append(result)
|
||||
return results
|
||||
|
||||
|
||||
# Singleton instance
|
||||
gateway_service = GatewayService()
|
||||
|
||||
__all__ = [
|
||||
"GatewayService",
|
||||
"GatewayStatus",
|
||||
"GatewayInfo",
|
||||
"GatewayProtocol",
|
||||
"BaseGateway",
|
||||
"StripeGateway",
|
||||
"PayPalGateway",
|
||||
"BankTransferGateway",
|
||||
"gateway_service",
|
||||
]
|
||||
232
app/modules/payments/services/payment_service.py
Normal file
232
app/modules/payments/services/payment_service.py
Normal file
@@ -0,0 +1,232 @@
|
||||
# app/modules/payments/services/payment_service.py
|
||||
"""
|
||||
Payment service for processing payments through configured gateways.
|
||||
|
||||
This service provides a unified interface for payment operations
|
||||
regardless of the underlying gateway (Stripe, PayPal, etc.).
|
||||
|
||||
Usage:
|
||||
from app.modules.payments.services import PaymentService
|
||||
|
||||
payment_service = PaymentService()
|
||||
|
||||
# Process a payment
|
||||
result = await payment_service.process_payment(
|
||||
amount=1000, # Amount in cents
|
||||
currency="EUR",
|
||||
payment_method_id="pm_xxx",
|
||||
description="Order #123",
|
||||
)
|
||||
|
||||
# Issue a refund
|
||||
refund = await payment_service.refund(
|
||||
transaction_id="txn_xxx",
|
||||
amount=500, # Partial refund
|
||||
)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PaymentStatus(str, Enum):
|
||||
"""Payment transaction status."""
|
||||
|
||||
PENDING = "pending"
|
||||
PROCESSING = "processing"
|
||||
SUCCEEDED = "succeeded"
|
||||
FAILED = "failed"
|
||||
CANCELLED = "cancelled"
|
||||
REFUNDED = "refunded"
|
||||
PARTIALLY_REFUNDED = "partially_refunded"
|
||||
|
||||
|
||||
class PaymentGateway(str, Enum):
|
||||
"""Supported payment gateways."""
|
||||
|
||||
STRIPE = "stripe"
|
||||
PAYPAL = "paypal"
|
||||
BANK_TRANSFER = "bank_transfer"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PaymentResult:
|
||||
"""Result of a payment operation."""
|
||||
|
||||
success: bool
|
||||
transaction_id: str | None = None
|
||||
gateway: PaymentGateway | None = None
|
||||
status: PaymentStatus = PaymentStatus.PENDING
|
||||
amount: int = 0 # Amount in cents
|
||||
currency: str = "EUR"
|
||||
error_message: str | None = None
|
||||
gateway_response: dict[str, Any] | None = None
|
||||
created_at: datetime | None = None
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return {
|
||||
"success": self.success,
|
||||
"transaction_id": self.transaction_id,
|
||||
"gateway": self.gateway.value if self.gateway else None,
|
||||
"status": self.status.value,
|
||||
"amount": self.amount,
|
||||
"currency": self.currency,
|
||||
"error_message": self.error_message,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class RefundResult:
|
||||
"""Result of a refund operation."""
|
||||
|
||||
success: bool
|
||||
refund_id: str | None = None
|
||||
transaction_id: str | None = None
|
||||
amount: int = 0
|
||||
status: PaymentStatus = PaymentStatus.PENDING
|
||||
error_message: str | None = None
|
||||
|
||||
|
||||
class PaymentService:
|
||||
"""
|
||||
Service for processing payments through configured gateways.
|
||||
|
||||
This service provides a unified interface for:
|
||||
- Processing payments
|
||||
- Issuing refunds
|
||||
- Managing payment methods
|
||||
- Retrieving transaction history
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._default_gateway = PaymentGateway.STRIPE
|
||||
|
||||
async def process_payment(
|
||||
self,
|
||||
amount: int,
|
||||
currency: str = "EUR",
|
||||
payment_method_id: str | None = None,
|
||||
gateway: PaymentGateway | None = None,
|
||||
description: str | None = None,
|
||||
metadata: dict[str, Any] | None = None,
|
||||
) -> PaymentResult:
|
||||
"""
|
||||
Process a payment through the specified gateway.
|
||||
|
||||
Args:
|
||||
amount: Amount in cents
|
||||
currency: Currency code (EUR, USD, etc.)
|
||||
payment_method_id: Stored payment method ID
|
||||
gateway: Payment gateway to use (default: stripe)
|
||||
description: Payment description
|
||||
metadata: Additional metadata for the transaction
|
||||
|
||||
Returns:
|
||||
PaymentResult with transaction details
|
||||
"""
|
||||
gateway = gateway or self._default_gateway
|
||||
|
||||
logger.info(
|
||||
f"Processing payment: {amount} {currency} via {gateway.value}",
|
||||
extra={"amount": amount, "currency": currency, "gateway": gateway.value},
|
||||
)
|
||||
|
||||
# TODO: Implement actual gateway processing
|
||||
# For now, return a mock successful result
|
||||
return PaymentResult(
|
||||
success=True,
|
||||
transaction_id=f"txn_{gateway.value}_mock",
|
||||
gateway=gateway,
|
||||
status=PaymentStatus.SUCCEEDED,
|
||||
amount=amount,
|
||||
currency=currency,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
)
|
||||
|
||||
async def refund(
|
||||
self,
|
||||
transaction_id: str,
|
||||
amount: int | None = None,
|
||||
reason: str | None = None,
|
||||
) -> RefundResult:
|
||||
"""
|
||||
Issue a refund for a transaction.
|
||||
|
||||
Args:
|
||||
transaction_id: Original transaction ID
|
||||
amount: Refund amount in cents (None for full refund)
|
||||
reason: Reason for refund
|
||||
|
||||
Returns:
|
||||
RefundResult with refund details
|
||||
"""
|
||||
logger.info(
|
||||
f"Issuing refund for transaction {transaction_id}",
|
||||
extra={"transaction_id": transaction_id, "amount": amount, "reason": reason},
|
||||
)
|
||||
|
||||
# TODO: Implement actual refund processing
|
||||
return RefundResult(
|
||||
success=True,
|
||||
refund_id=f"rf_{transaction_id}",
|
||||
transaction_id=transaction_id,
|
||||
amount=amount or 0,
|
||||
status=PaymentStatus.REFUNDED,
|
||||
)
|
||||
|
||||
async def get_transaction(self, transaction_id: str) -> dict[str, Any] | None:
|
||||
"""
|
||||
Get transaction details by ID.
|
||||
|
||||
Args:
|
||||
transaction_id: Transaction ID
|
||||
|
||||
Returns:
|
||||
Transaction details or None if not found
|
||||
"""
|
||||
# TODO: Implement transaction lookup
|
||||
return None
|
||||
|
||||
async def list_transactions(
|
||||
self,
|
||||
vendor_id: int | None = None,
|
||||
platform_id: int | None = None,
|
||||
status: PaymentStatus | None = None,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""
|
||||
List transactions with optional filters.
|
||||
|
||||
Args:
|
||||
vendor_id: Filter by vendor
|
||||
platform_id: Filter by platform
|
||||
status: Filter by status
|
||||
limit: Maximum results
|
||||
offset: Pagination offset
|
||||
|
||||
Returns:
|
||||
List of transaction records
|
||||
"""
|
||||
# TODO: Implement transaction listing
|
||||
return []
|
||||
|
||||
|
||||
# Singleton instance
|
||||
payment_service = PaymentService()
|
||||
|
||||
__all__ = [
|
||||
"PaymentService",
|
||||
"PaymentStatus",
|
||||
"PaymentGateway",
|
||||
"PaymentResult",
|
||||
"RefundResult",
|
||||
"payment_service",
|
||||
]
|
||||
Reference in New Issue
Block a user