- Fix critical NameError in database.py by restoring Session factory - Refactor payment_service.py and crud.py to use shared constants.py and utils.py - Deduplicate state mapping and input sanitization logic - Move transaction amount calculation logic from CRUD to Router layer - Enforce type safety in schemas using IntEnum for TransactionType/Status - Move capture endpoint from transaction.py to payment.py (now /payments/capture) - Update create_customer_profile signature for clarity
142 lines
4.9 KiB
Python
142 lines
4.9 KiB
Python
## File: app/schemas.py (or your equivalent path)
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
from typing import List, Optional
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from decimal import Decimal
|
|
import re
|
|
from .constants import TransactionType, TransactionStatus
|
|
|
|
# --- NEW SCHEMAS FOR CIM WORKFLOW (Now with correct Pydantic V2 config) ---
|
|
|
|
class CardCreate(BaseModel):
|
|
card_number: str = Field(..., min_length=13, max_length=19, description="Credit card number (13-19 digits)")
|
|
expiration_date: str = Field(..., description="Expiration date in YYYY-MM format")
|
|
cvv: str = Field(..., min_length=3, max_length=4, description="Card security code (3-4 digits)")
|
|
main_card: bool = False
|
|
|
|
@field_validator('card_number')
|
|
@classmethod
|
|
def validate_card_number(cls, v):
|
|
digits_only = re.sub(r'\D', '', v)
|
|
if len(digits_only) < 13 or len(digits_only) > 19:
|
|
raise ValueError('Card number must be 13-19 digits')
|
|
return digits_only
|
|
|
|
@field_validator('expiration_date')
|
|
@classmethod
|
|
def validate_expiration_date(cls, v):
|
|
if not re.match(r'^\d{4}-\d{2}$', v):
|
|
raise ValueError('Expiration date must be in YYYY-MM format')
|
|
return v
|
|
|
|
@field_validator('cvv')
|
|
@classmethod
|
|
def validate_cvv(cls, v):
|
|
if not re.match(r'^\d{3,4}$', v):
|
|
raise ValueError('CVV must be 3-4 digits')
|
|
return v
|
|
|
|
class Card(BaseModel):
|
|
id: int
|
|
user_id: int
|
|
last_four_digits: str
|
|
type_of_card: Optional[str] = None
|
|
expiration_month: int
|
|
expiration_year: int
|
|
|
|
# --- MODIFICATION: This is the new syntax for Pydantic V2 ---
|
|
model_config = ConfigDict(from_attributes=True)
|
|
# The line above replaces the old `class Config: orm_mode = True`
|
|
|
|
class TransactionCreateByCardID(BaseModel):
|
|
card_id: int
|
|
charge_amount: Decimal
|
|
tax_amount: Optional[Decimal] = Decimal("0.0")
|
|
service_id: Optional[int] = None
|
|
delivery_id: Optional[int] = None
|
|
|
|
class TransactionAuthorizeByCardID(BaseModel):
|
|
card_id: int
|
|
preauthorize_amount: Decimal
|
|
tax_amount: Optional[Decimal] = Decimal("0.0")
|
|
service_id: Optional[int] = None
|
|
delivery_id: Optional[int] = None
|
|
auto_id: Optional[int] = None
|
|
|
|
# --- YOUR EXISTING SCHEMAS (UPDATED for Pydantic V2) ---
|
|
|
|
class TransactionBase(BaseModel):
|
|
preauthorize_amount: Optional[Decimal] = None
|
|
charge_amount: Optional[Decimal] = None
|
|
transaction_type: TransactionType
|
|
service_id: Optional[int] = None
|
|
delivery_id: Optional[int] = None
|
|
auto_id: Optional[int] = None
|
|
card_id: Optional[int] = None
|
|
payment_gateway: int = 1
|
|
rejection_reason: Optional[str] = None
|
|
|
|
class TransactionCreate(TransactionBase):
|
|
charge_amount: Decimal
|
|
card_number: str
|
|
expiration_date: str
|
|
cvv: str
|
|
|
|
class TransactionAuthorize(TransactionBase):
|
|
preauthorize_amount: Decimal
|
|
card_number: str
|
|
expiration_date: str
|
|
cvv: str
|
|
|
|
class TransactionCapture(BaseModel):
|
|
charge_amount: Decimal
|
|
auth_net_transaction_id: str
|
|
|
|
class Transaction(TransactionBase):
|
|
id: int
|
|
transaction_type: TransactionType
|
|
status: TransactionStatus
|
|
auth_net_transaction_id: Optional[str] = None
|
|
customer_id: int
|
|
created_at: datetime
|
|
|
|
# --- MODIFICATION: This is the new syntax for Pydantic V2 ---
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
class CustomerBase(BaseModel):
|
|
account_number: Optional[str] = None
|
|
customer_last_name: Optional[str] = Field(None, max_length=50, description="Last name (max 50 chars)")
|
|
customer_first_name: Optional[str] = Field(None, max_length=50, description="First name (max 50 chars)")
|
|
customer_town: Optional[str] = Field(None, max_length=40, description="City (max 40 chars)")
|
|
customer_state: Optional[int] = None
|
|
customer_zip: Optional[str] = Field(None, max_length=20, description="ZIP code (max 20 chars)")
|
|
customer_first_call: Optional[datetime] = None
|
|
customer_email: Optional[str] = Field(None, max_length=255, description="Email (max 255 chars)")
|
|
customer_automatic: Optional[int] = None
|
|
customer_phone_number: Optional[str] = None
|
|
customer_home_type: Optional[int] = None
|
|
customer_apt: Optional[str] = None
|
|
customer_address: Optional[str] = Field(None, max_length=60, description="Address (max 60 chars)")
|
|
company_id: Optional[int] = None
|
|
customer_latitude: Optional[str] = None
|
|
customer_longitude: Optional[str] = None
|
|
correct_address: Optional[bool] = None
|
|
|
|
class Customer(CustomerBase):
|
|
id: int
|
|
auth_net_profile_id: Optional[str] = None
|
|
|
|
# --- MODIFICATION: This is the new syntax for Pydantic V2 ---
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class CustomerCardResponse(Card):
|
|
# We are inheriting all fields from `Card`
|
|
|
|
# Now, add the extra customer fields the frontend needs.
|
|
# We define it as a string because we will be returning the
|
|
# two-letter abbreviation, not the database ID.
|
|
customer_state: str
|