Add all project files
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
FROM python:3.9
|
||||
ENV PYTHONFAULTHANDLER=1
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
ENV MODE="DEVELOPMENT"
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,14 @@
|
||||
FROM python:3.9
|
||||
ENV PYTHONFAULTHANDLER=1
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
ENV MODE="LOCAL"
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,16 @@
|
||||
FROM python:3.9
|
||||
|
||||
ENV PYTHONFAULTHANDLER=1
|
||||
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
|
||||
ENV MODE="PRODUCTION"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt requirements.txt
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+61
@@ -0,0 +1,61 @@
|
||||
from sqlalchemy.orm import Session
|
||||
from . import models, schemas
|
||||
|
||||
def get_customer(db: Session, customer_id: int):
|
||||
return db.query(models.Customer).filter(models.Customer.id == customer_id).first()
|
||||
|
||||
def get_customer_by_email(db: Session, email: str):
|
||||
return db.query(models.Customer).filter(models.Customer.customer_email == email).first()
|
||||
|
||||
def get_customers(db: Session, skip: int = 0, limit: int = 100):
|
||||
return db.query(models.Customer).offset(skip).limit(limit).all()
|
||||
|
||||
def create_transaction(db: Session, transaction: schemas.TransactionBase, customer_id: int, status: int, auth_net_transaction_id: str = None):
|
||||
db_transaction = models.Transaction(
|
||||
preauthorize_amount=transaction.preauthorize_amount,
|
||||
charge_amount=transaction.charge_amount,
|
||||
transaction_type=transaction.transaction_type,
|
||||
customer_id=customer_id,
|
||||
status=status,
|
||||
auth_net_transaction_id=auth_net_transaction_id,
|
||||
service_id=transaction.service_id,
|
||||
delivery_id=transaction.delivery_id,
|
||||
card_id=transaction.card_id,
|
||||
payment_gateway=transaction.payment_gateway,
|
||||
rejection_reason=transaction.rejection_reason
|
||||
)
|
||||
db.add(db_transaction)
|
||||
db.commit()
|
||||
db.refresh(db_transaction)
|
||||
return db_transaction
|
||||
|
||||
def get_transaction_by_delivery_id(db: Session, delivery_id: int):
|
||||
return db.query(models.Transaction).filter(
|
||||
models.Transaction.delivery_id == delivery_id,
|
||||
models.Transaction.transaction_type == 1, # auth transactions
|
||||
models.Transaction.status == 0 # approved
|
||||
).first()
|
||||
|
||||
# --- THIS IS THE FIX ---
|
||||
# This function was missing, causing the AttributeError.
|
||||
# It finds a transaction using the unique Authorize.Net transaction ID.
|
||||
def get_transaction_by_auth_id(db: Session, auth_net_transaction_id: str):
|
||||
return db.query(models.Transaction).filter(
|
||||
models.Transaction.auth_net_transaction_id == auth_net_transaction_id
|
||||
).first()
|
||||
# --- END OF FIX ---
|
||||
|
||||
def update_transaction_for_capture(db: Session, auth_net_transaction_id: str, charge_amount: float, status: int, rejection_reason: str = None):
|
||||
transaction = db.query(models.Transaction).filter(models.Transaction.auth_net_transaction_id == auth_net_transaction_id).first()
|
||||
if not transaction:
|
||||
return None
|
||||
|
||||
transaction.charge_amount = charge_amount
|
||||
transaction.transaction_type = 3 # capture
|
||||
transaction.status = status
|
||||
if rejection_reason:
|
||||
transaction.rejection_reason = rejection_reason
|
||||
|
||||
db.commit()
|
||||
db.refresh(transaction)
|
||||
return transaction
|
||||
@@ -0,0 +1,34 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.engine import URL
|
||||
from config import load_config
|
||||
|
||||
|
||||
ApplicationConfig = load_config()
|
||||
|
||||
|
||||
url = URL.create(
|
||||
drivername="postgresql",
|
||||
username=ApplicationConfig.POSTGRES_USERNAME,
|
||||
password=ApplicationConfig.POSTGRES_PW,
|
||||
host=ApplicationConfig.POSTGRES_SERVER,
|
||||
database=ApplicationConfig.POSTGRES_DBNAME00,
|
||||
port=ApplicationConfig.POSTGRES_PORT
|
||||
)
|
||||
|
||||
engine = create_engine(url)
|
||||
|
||||
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
session = Session()
|
||||
|
||||
Base = declarative_base()
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
|
||||
def get_db():
|
||||
db = Session()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
from fastapi import FastAPI
|
||||
from .database import engine
|
||||
from . import models
|
||||
from .routers import payment
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from config import load_config
|
||||
|
||||
|
||||
ApplicationConfig = load_config()
|
||||
|
||||
|
||||
models.Base.metadata.create_all(bind=engine)
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
# print(ApplicationConfig.origins)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=ApplicationConfig.origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
|
||||
|
||||
app.include_router(payment.router, prefix="/api", tags=["payment"])
|
||||
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def read_root():
|
||||
return {"message": "Welcome to the HVAC Payment API"}
|
||||
@@ -0,0 +1,45 @@
|
||||
from sqlalchemy import Column, Integer, String, Float, DateTime, Boolean
|
||||
from .database import Base
|
||||
import datetime
|
||||
|
||||
|
||||
|
||||
|
||||
class Customer(Base):
|
||||
__tablename__ = "customers"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
account_number = Column(String(25))
|
||||
customer_last_name = Column(String(250))
|
||||
customer_first_name = Column(String(250))
|
||||
customer_town = Column(String(140))
|
||||
customer_state = Column(Integer)
|
||||
customer_zip = Column(String(25))
|
||||
customer_first_call = Column(DateTime)
|
||||
customer_email = Column(String(500))
|
||||
customer_automatic = Column(Integer)
|
||||
customer_phone_number = Column(String(25))
|
||||
customer_home_type = Column(Integer)
|
||||
customer_apt = Column(String(140))
|
||||
customer_address = Column(String(1000))
|
||||
company_id = Column(Integer)
|
||||
customer_latitude = Column(String(250))
|
||||
customer_longitude = Column(String(250))
|
||||
correct_address = Column(Boolean)
|
||||
|
||||
class Transaction(Base):
|
||||
__tablename__ = "transactions"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
preauthorize_amount = Column(Float, nullable=True) # Amount preauthorized (for auth transactions)
|
||||
charge_amount = Column(Float, nullable=True) # Final charge amount (for charge/capture transactions)
|
||||
transaction_type = Column(Integer) # 0 = charge, 1 = auth, 3 = capture
|
||||
status = Column(Integer) # 0 = approved, 1 = declined
|
||||
auth_net_transaction_id = Column(String, unique=True, index=True, nullable=True)
|
||||
customer_id = Column(Integer)
|
||||
service_id = Column(Integer, nullable=True) # Reference to Service_Service.id
|
||||
delivery_id = Column(Integer, nullable=True) # Reference to Delivery_Delivery.id
|
||||
card_id = Column(Integer, nullable=True) # Reference to credit card used for payment
|
||||
payment_gateway = Column(Integer, default=1) # 1 = Authorize.Net, 0 = Other
|
||||
rejection_reason = Column(String, nullable=True) # Detailed error message when payment is declined
|
||||
created_at = Column(DateTime, default=datetime.datetime.utcnow)
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,133 @@
|
||||
#
|
||||
# your_app/views.py (or wherever this file is located)
|
||||
#
|
||||
import datetime
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Tuple, Optional
|
||||
import enum
|
||||
|
||||
# Assuming these are your project's modules
|
||||
from .. import crud, models, schemas, database
|
||||
from ..services import payment_service
|
||||
|
||||
# Placeholder for the Authorize.Net response object type
|
||||
AuthNetResponse = object
|
||||
|
||||
|
||||
router = APIRouter(
|
||||
tags=["Transactions"], # Tags are for documentation only, they don't affect URLs
|
||||
)
|
||||
|
||||
class TransactionStatus(enum.IntEnum):
|
||||
APPROVED = 0
|
||||
DECLINED = 1
|
||||
|
||||
class TransactionType(enum.IntEnum):
|
||||
CHARGE = 0
|
||||
AUTHORIZE = 1
|
||||
CAPTURE = 3
|
||||
|
||||
# --- Helper function to avoid repeating response parsing logic ---
|
||||
def _parse_authnet_response(response: Optional[AuthNetResponse]) -> Tuple[TransactionStatus, Optional[str], Optional[str]]:
|
||||
"""
|
||||
Parses the response from the Authorize.Net service.
|
||||
(This is the same helper from before, it's a good change to keep)
|
||||
"""
|
||||
if response and hasattr(response, 'messages') and response.messages.resultCode == "Ok":
|
||||
status = TransactionStatus.APPROVED
|
||||
auth_net_transaction_id = str(response.transactionResponse.transId) if hasattr(response, 'transactionResponse') else None
|
||||
rejection_reason = None
|
||||
else:
|
||||
status = TransactionStatus.DECLINED
|
||||
auth_net_transaction_id = None
|
||||
if hasattr(response, '_rejection_reason'):
|
||||
rejection_reason = str(response._rejection_reason)
|
||||
elif response is None:
|
||||
rejection_reason = "No response received from payment gateway."
|
||||
else:
|
||||
rejection_reason = "Payment declined by gateway."
|
||||
return status, auth_net_transaction_id, rejection_reason
|
||||
|
||||
|
||||
@router.post("/authorize/", response_model=schemas.Transaction)
|
||||
def authorize_card(customer_id: int, transaction: schemas.TransactionAuthorize, db: Session = Depends(database.get_db)):
|
||||
auth_net_response = payment_service.authorize_credit_card(transaction)
|
||||
status, auth_net_transaction_id, rejection_reason = _parse_authnet_response(auth_net_response)
|
||||
|
||||
transaction_data = schemas.TransactionBase(
|
||||
preauthorize_amount=transaction.preauthorize_amount,
|
||||
transaction_type=TransactionType.AUTHORIZE,
|
||||
service_id=transaction.service_id,
|
||||
delivery_id=transaction.delivery_id,
|
||||
card_id=transaction.card_id,
|
||||
rejection_reason=rejection_reason
|
||||
)
|
||||
|
||||
return crud.create_transaction(
|
||||
db=db,
|
||||
transaction=transaction_data,
|
||||
customer_id=customer_id,
|
||||
status=status,
|
||||
auth_net_transaction_id=auth_net_transaction_id
|
||||
)
|
||||
|
||||
@router.post("/charge/{customer_id}", response_model=schemas.Transaction)
|
||||
def charge_card(customer_id: int, transaction: schemas.TransactionCreate, db: Session = Depends(database.get_db)):
|
||||
# Add debug logging
|
||||
print(f"DEBUG: Received charge request for customer_id: {customer_id}")
|
||||
print(f"DEBUG: Transaction data: {transaction.dict() if hasattr(transaction, 'dict') else transaction}")
|
||||
|
||||
try:
|
||||
auth_net_response = payment_service.charge_credit_card(transaction)
|
||||
status, auth_net_transaction_id, rejection_reason = _parse_authnet_response(auth_net_response)
|
||||
|
||||
transaction_data = schemas.TransactionBase(
|
||||
charge_amount=transaction.charge_amount,
|
||||
transaction_type=TransactionType.CHARGE,
|
||||
service_id=transaction.service_id,
|
||||
delivery_id=transaction.delivery_id,
|
||||
card_id=transaction.card_id,
|
||||
rejection_reason=rejection_reason
|
||||
)
|
||||
print(f"DEBUG: Transaction data to create: {transaction_data.dict()}")
|
||||
|
||||
result = crud.create_transaction(
|
||||
db=db,
|
||||
transaction=transaction_data,
|
||||
customer_id=customer_id,
|
||||
status=status,
|
||||
auth_net_transaction_id=auth_net_transaction_id
|
||||
)
|
||||
print(f"DEBUG: Created transaction: {result.dict()}")
|
||||
return result
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Exception in charge_card: {str(e)}")
|
||||
import traceback
|
||||
print(f"DEBUG: Traceback: {traceback.format_exc()}")
|
||||
raise
|
||||
|
||||
|
||||
@router.post("/capture/", response_model=schemas.Transaction)
|
||||
def capture_authorized_amount(transaction: schemas.TransactionCapture, db: Session = Depends(database.get_db)):
|
||||
auth_transaction = crud.get_transaction_by_auth_id(db, auth_net_transaction_id=transaction.auth_net_transaction_id)
|
||||
if not auth_transaction:
|
||||
raise HTTPException(status_code=404, detail="Authorization transaction not found")
|
||||
|
||||
auth_net_response = payment_service.capture_authorized_transaction(transaction)
|
||||
status, _, rejection_reason = _parse_authnet_response(auth_net_response)
|
||||
|
||||
return crud.update_transaction_for_capture(
|
||||
db=db,
|
||||
auth_net_transaction_id=transaction.auth_net_transaction_id,
|
||||
charge_amount=transaction.charge_amount,
|
||||
status=status,
|
||||
rejection_reason=rejection_reason
|
||||
)
|
||||
|
||||
@router.get("/transaction/delivery/{delivery_id}", response_model=schemas.Transaction)
|
||||
def get_transaction_by_delivery(delivery_id: int, db: Session = Depends(database.get_db)):
|
||||
transaction = crud.get_transaction_by_delivery_id(db, delivery_id=delivery_id)
|
||||
if not transaction:
|
||||
raise HTTPException(status_code=404, detail="No pre-authorized transaction found for this delivery")
|
||||
return transaction
|
||||
@@ -0,0 +1,64 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
class TransactionBase(BaseModel):
|
||||
preauthorize_amount: Optional[float] = None # Amount preauthorized (for auth transactions)
|
||||
charge_amount: Optional[float] = None # Final charge amount (for charge/capture transactions)
|
||||
transaction_type: int # 0 = charge, 1 = auth, 3 = capture - Required for database
|
||||
service_id: Optional[int] = None # Reference to Service_Service.id
|
||||
delivery_id: Optional[int] = None # Reference to Delivery_Delivery.id
|
||||
card_id: Optional[int] = None # Reference to credit card used for payment
|
||||
payment_gateway: int = 1 # 1 = Authorize.Net, 0 = Other
|
||||
rejection_reason: Optional[str] = None # Detailed error message when payment is declined
|
||||
|
||||
class TransactionCreate(TransactionBase):
|
||||
charge_amount: float # Final charge amount
|
||||
card_number: str
|
||||
expiration_date: str # MM/YY
|
||||
cvv: str
|
||||
|
||||
class TransactionAuthorize(TransactionBase):
|
||||
preauthorize_amount: float # Amount to preauthorize
|
||||
card_number: str
|
||||
expiration_date: str
|
||||
cvv: str
|
||||
|
||||
class TransactionCapture(BaseModel):
|
||||
charge_amount: float # Amount to capture
|
||||
auth_net_transaction_id: str
|
||||
|
||||
class Transaction(TransactionBase):
|
||||
id: int
|
||||
transaction_type: int # 0 = charge, 1 = auth, 3 = capture
|
||||
status: int # 0 = approved, 1 = declined
|
||||
auth_net_transaction_id: Optional[str] = None
|
||||
customer_id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
class CustomerBase(BaseModel):
|
||||
account_number: Optional[str] = None
|
||||
customer_last_name: Optional[str] = None
|
||||
customer_first_name: Optional[str] = None
|
||||
customer_town: Optional[str] = None
|
||||
customer_state: Optional[int] = None
|
||||
customer_zip: Optional[str] = None
|
||||
customer_first_call: Optional[datetime] = None
|
||||
customer_email: Optional[str] = None
|
||||
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] = None
|
||||
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
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
Binary file not shown.
@@ -0,0 +1,235 @@
|
||||
import logging
|
||||
from authorizenet import apicontractsv1
|
||||
from authorizenet.apicontrollers import createTransactionController
|
||||
from .. import schemas
|
||||
from config import load_config
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Load Authorize.net credentials from config
|
||||
ApplicationConfig = load_config()
|
||||
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
|
||||
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
|
||||
# For sandbox, endpoint is https://apitest.authorize.net/xml/v1/request.api
|
||||
|
||||
def safe_string_convert(value):
|
||||
"""
|
||||
Safely convert any value to string, handling lxml objects properly.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
# Check if it's an lxml object with text attribute
|
||||
if hasattr(value, 'text'):
|
||||
return value.text
|
||||
# Otherwise use standard string conversion
|
||||
return str(value)
|
||||
|
||||
def charge_credit_card(transaction: schemas.TransactionCreate):
|
||||
logger.info(f"Processing charge for amount: {transaction.charge_amount}")
|
||||
|
||||
merchantAuth = apicontractsv1.merchantAuthenticationType()
|
||||
merchantAuth.name = API_LOGIN_ID
|
||||
merchantAuth.transactionKey = TRANSACTION_KEY
|
||||
|
||||
creditCard = apicontractsv1.creditCardType()
|
||||
creditCard.cardNumber = transaction.card_number
|
||||
creditCard.expirationDate = transaction.expiration_date
|
||||
creditCard.cardCode = transaction.cvv
|
||||
|
||||
payment = apicontractsv1.paymentType()
|
||||
payment.creditCard = creditCard
|
||||
|
||||
transactionRequest = apicontractsv1.transactionRequestType()
|
||||
transactionRequest.transactionType = "authCaptureTransaction"
|
||||
transactionRequest.amount = transaction.charge_amount # ✅ Fixed: Use charge_amount
|
||||
transactionRequest.payment = payment
|
||||
|
||||
createtransactionrequest = apicontractsv1.createTransactionRequest()
|
||||
createtransactionrequest.merchantAuthentication = merchantAuth
|
||||
createtransactionrequest.refId = "ref_id" # Optional reference ID
|
||||
createtransactionrequest.transactionRequest = transactionRequest
|
||||
|
||||
controller = createTransactionController(createtransactionrequest)
|
||||
controller.execute()
|
||||
|
||||
response = controller.getresponse()
|
||||
|
||||
# Extract rejection reason if payment failed
|
||||
rejection_reason = None
|
||||
if response is not None and response.messages is not None:
|
||||
logger.info(f"Charge response: {response.messages.resultCode}")
|
||||
|
||||
# If payment was declined (resultCode is "Error"), extract error details
|
||||
if response.messages.resultCode == "Error":
|
||||
rejection_reason = "Authorize.Net Charge Error"
|
||||
if hasattr(response.messages, 'message'):
|
||||
try:
|
||||
if len(response.messages.message) > 0:
|
||||
for msg in response.messages.message:
|
||||
if hasattr(msg, 'code') and hasattr(msg, 'text'):
|
||||
# Convert lxml StringElement objects to Python strings
|
||||
code_str = msg.code.text if msg.code else "Unknown"
|
||||
text_str = msg.text.text if msg.text else "No details available"
|
||||
rejection_reason = f"{code_str}: {text_str}"
|
||||
break # Use the first error message
|
||||
elif hasattr(msg, 'text'):
|
||||
# Convert lxml StringElement to Python string
|
||||
text_str = msg.text.text if msg.text else "No details available"
|
||||
rejection_reason = f"Error: {text_str}"
|
||||
break
|
||||
else:
|
||||
rejection_reason = "Charge declined - no specific error details available"
|
||||
except Exception as e:
|
||||
rejection_reason = f"Charge declined - error details could not be parsed: {str(e)}"
|
||||
else:
|
||||
rejection_reason = "Charge declined - no error message available"
|
||||
|
||||
if hasattr(response.messages, 'message') and len(response.messages.message) > 0:
|
||||
for msg in response.messages.message:
|
||||
logger.info(f"Message: {msg.text.text if msg.text else 'No message text'}")
|
||||
else:
|
||||
logger.error("No response from Authorize.net")
|
||||
rejection_reason = "No response received from Authorize.Net"
|
||||
|
||||
# Attach rejection reason to response for the router to use
|
||||
if response is not None:
|
||||
response._rejection_reason = rejection_reason
|
||||
|
||||
return response
|
||||
|
||||
def authorize_credit_card(transaction: schemas.TransactionAuthorize):
|
||||
logger.info(f"Processing preauthorization for amount: {transaction.preauthorize_amount}")
|
||||
|
||||
merchantAuth = apicontractsv1.merchantAuthenticationType()
|
||||
merchantAuth.name = API_LOGIN_ID
|
||||
merchantAuth.transactionKey = TRANSACTION_KEY
|
||||
|
||||
creditCard = apicontractsv1.creditCardType()
|
||||
creditCard.cardNumber = transaction.card_number
|
||||
creditCard.expirationDate = transaction.expiration_date
|
||||
creditCard.cardCode = transaction.cvv
|
||||
|
||||
payment = apicontractsv1.paymentType()
|
||||
payment.creditCard = creditCard
|
||||
|
||||
transactionRequest = apicontractsv1.transactionRequestType()
|
||||
transactionRequest.transactionType = "authOnlyTransaction"
|
||||
transactionRequest.amount = transaction.preauthorize_amount # ✅ Fixed: Use preauthorize_amount
|
||||
transactionRequest.payment = payment
|
||||
|
||||
createtransactionrequest = apicontractsv1.createTransactionRequest()
|
||||
createtransactionrequest.merchantAuthentication = merchantAuth
|
||||
createtransactionrequest.refId = "ref_id"
|
||||
createtransactionrequest.transactionRequest = transactionRequest
|
||||
|
||||
controller = createTransactionController(createtransactionrequest)
|
||||
controller.execute()
|
||||
|
||||
response = controller.getresponse()
|
||||
|
||||
# Extract rejection reason if payment failed
|
||||
rejection_reason = None
|
||||
if response is not None and response.messages is not None:
|
||||
logger.info(f"Preauthorization response: {response.messages.resultCode}")
|
||||
|
||||
# If payment was declined (resultCode is "Error"), extract error details
|
||||
if response.messages.resultCode == "Error":
|
||||
rejection_reason = "Authorize.Net Error"
|
||||
if hasattr(response.messages, 'message'):
|
||||
try:
|
||||
if len(response.messages.message) > 0:
|
||||
for msg in response.messages.message:
|
||||
if hasattr(msg, 'code') and hasattr(msg, 'text'):
|
||||
# Convert lxml StringElement objects to Python strings
|
||||
code_str = msg.code.text if msg.code else "Unknown"
|
||||
text_str = msg.text.text if msg.text else "No details available"
|
||||
rejection_reason = f"{code_str}: {text_str}"
|
||||
break # Use the first error message
|
||||
elif hasattr(msg, 'text'):
|
||||
# Convert lxml StringElement to Python string
|
||||
text_str = msg.text.text if msg.text else "No details available"
|
||||
rejection_reason = f"Error: {text_str}"
|
||||
break
|
||||
else:
|
||||
rejection_reason = "Payment declined - no specific error details available"
|
||||
except Exception as e:
|
||||
rejection_reason = f"Payment declined - error details could not be parsed: {str(e)}"
|
||||
else:
|
||||
rejection_reason = "Payment declined - no error message available"
|
||||
|
||||
if hasattr(response.messages, 'message') and len(response.messages.message) > 0:
|
||||
for msg in response.messages.message:
|
||||
logger.info(f"Message: {msg.text.text if msg.text else 'No message text'}")
|
||||
else:
|
||||
logger.error("No response from Authorize.net for preauthorization")
|
||||
rejection_reason = "No response received from Authorize.Net"
|
||||
|
||||
# Attach rejection reason to response for the router to use
|
||||
if response is not None:
|
||||
response._rejection_reason = rejection_reason
|
||||
|
||||
return response
|
||||
|
||||
def capture_authorized_transaction(transaction: schemas.TransactionCapture):
|
||||
merchantAuth = apicontractsv1.merchantAuthenticationType()
|
||||
merchantAuth.name = API_LOGIN_ID
|
||||
merchantAuth.transactionKey = TRANSACTION_KEY
|
||||
|
||||
transactionRequest = apicontractsv1.transactionRequestType()
|
||||
transactionRequest.transactionType = "priorAuthCaptureTransaction"
|
||||
transactionRequest.amount = transaction.charge_amount # ✅ Fixed: Use charge_amount
|
||||
transactionRequest.refTransId = transaction.auth_net_transaction_id
|
||||
|
||||
createtransactionrequest = apicontractsv1.createTransactionRequest()
|
||||
createtransactionrequest.merchantAuthentication = merchantAuth
|
||||
createtransactionrequest.refId = "ref_id"
|
||||
createtransactionrequest.transactionRequest = transactionRequest
|
||||
|
||||
controller = createTransactionController(createtransactionrequest)
|
||||
controller.execute()
|
||||
|
||||
response = controller.getresponse()
|
||||
|
||||
# Extract rejection reason if capture failed
|
||||
rejection_reason = None
|
||||
if response is not None and response.messages is not None:
|
||||
logger.info(f"Capture response: {response.messages.resultCode}")
|
||||
|
||||
# If capture was declined (resultCode is "Error"), extract error details
|
||||
if response.messages.resultCode == "Error":
|
||||
rejection_reason = "Authorize.Net Capture Error"
|
||||
if hasattr(response.messages, 'message'):
|
||||
try:
|
||||
if len(response.messages.message) > 0:
|
||||
for msg in response.messages.message:
|
||||
if hasattr(msg, 'code') and hasattr(msg, 'text'):
|
||||
# Convert lxml StringElement objects to Python strings
|
||||
code_str = msg.code.text if msg.code else "Unknown"
|
||||
text_str = msg.text.text if msg.text else "No details available"
|
||||
rejection_reason = f"{code_str}: {text_str}"
|
||||
break # Use the first error message
|
||||
elif hasattr(msg, 'text'):
|
||||
# Convert lxml StringElement to Python string
|
||||
text_str = msg.text.text if msg.text else "No details available"
|
||||
rejection_reason = f"Error: {text_str}"
|
||||
break
|
||||
else:
|
||||
rejection_reason = "Capture declined - no specific error details available"
|
||||
except Exception as e:
|
||||
rejection_reason = f"Capture declined - error details could not be parsed: {str(e)}"
|
||||
else:
|
||||
rejection_reason = "Capture declined - no error message available"
|
||||
|
||||
if hasattr(response.messages, 'message') and len(response.messages.message) > 0:
|
||||
for msg in response.messages.message:
|
||||
logger.info(f"Message: {msg.text.text if msg.text else 'No message text'}")
|
||||
else:
|
||||
logger.error("No response from Authorize.net for capture")
|
||||
rejection_reason = "No response received from Authorize.Net for capture"
|
||||
|
||||
# Attach rejection reason to response for the router to use
|
||||
if response is not None:
|
||||
response._rejection_reason = rejection_reason
|
||||
|
||||
return response
|
||||
@@ -0,0 +1,23 @@
|
||||
import os
|
||||
|
||||
def load_config(mode=os.environ.get('MODE')):
|
||||
|
||||
try:
|
||||
print(f"mode is {mode}")
|
||||
if mode == 'PRODUCTION':
|
||||
from settings_prod import ApplicationConfig
|
||||
return ApplicationConfig
|
||||
|
||||
elif mode == 'LOCAL':
|
||||
from settings_local import ApplicationConfig
|
||||
return ApplicationConfig
|
||||
|
||||
elif mode == 'DEVELOPMENT':
|
||||
from settings_dev import ApplicationConfig
|
||||
return ApplicationConfig
|
||||
else:
|
||||
pass
|
||||
|
||||
except ImportError:
|
||||
from settings_local import ApplicationConfig
|
||||
return ApplicationConfig
|
||||
@@ -0,0 +1,7 @@
|
||||
fastapi
|
||||
uvicorn
|
||||
sqlalchemy
|
||||
psycopg2-binary
|
||||
pydantic
|
||||
python-dotenv
|
||||
authorizenet
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
|
||||
class ApplicationConfig:
|
||||
"""
|
||||
Basic Configuration for a generic User
|
||||
"""
|
||||
CURRENT_SETTINGS = 'DEVELOPMENT'
|
||||
# databases info
|
||||
POSTGRES_USERNAME = 'postgres'
|
||||
POSTGRES_PW = 'password'
|
||||
POSTGRES_SERVER = '192.168.1.204'
|
||||
POSTGRES_PORT = '5432'
|
||||
POSTGRES_DBNAME00 = 'eamco'
|
||||
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
|
||||
POSTGRES_PW,
|
||||
POSTGRES_SERVER,
|
||||
POSTGRES_DBNAME00
|
||||
)
|
||||
SQLALCHEMY_BINDS = {'eamco': SQLALCHEMY_DATABASE_URI}
|
||||
|
||||
origins = [
|
||||
"http://localhost:9000",
|
||||
"https://localhost:9513",
|
||||
"http://localhost:9514",
|
||||
"http://localhost:9512",
|
||||
"http://localhost:9511",
|
||||
"http://localhost:5173", # Frontend port
|
||||
"http://localhost:9516", # Authorize service port
|
||||
|
||||
]
|
||||
|
||||
# Authorize.net credentials (Sandbox Test Credentials)
|
||||
API_LOGIN_ID = '5KP3u95bQpv'
|
||||
TRANSACTION_KEY = '346HZ32z3fP4hTG2'
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
|
||||
class ApplicationConfig:
|
||||
"""
|
||||
Basic Configuration for a generic User
|
||||
"""
|
||||
CURRENT_SETTINGS = 'LOCAL'
|
||||
# databases info
|
||||
POSTGRES_USERNAME = 'postgres'
|
||||
POSTGRES_PW = 'password'
|
||||
POSTGRES_SERVER = '192.168.1.204'
|
||||
POSTGRES_PORT = '5432'
|
||||
POSTGRES_DBNAME00 = 'auburnoil'
|
||||
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
|
||||
POSTGRES_PW,
|
||||
POSTGRES_SERVER,
|
||||
POSTGRES_DBNAME00
|
||||
)
|
||||
|
||||
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
|
||||
|
||||
|
||||
origins = [
|
||||
"http://192.168.1.204:9000",
|
||||
"http://192.168.1.204:9613",
|
||||
"http://192.168.1.204:9614",
|
||||
"http://192.168.1.204:9612",
|
||||
"http://192.168.1.204:9611",
|
||||
]
|
||||
|
||||
# Authorize.net credentials
|
||||
API_LOGIN_ID = 'bizdev05'
|
||||
TRANSACTION_KEY = '4kJd237rZu59qAZd'
|
||||
@@ -0,0 +1,26 @@
|
||||
class ApplicationConfig:
|
||||
"""
|
||||
Basic Configuration for a generic User
|
||||
"""
|
||||
CURRENT_SETTINGS = 'PRODUCTION'
|
||||
# databases info
|
||||
POSTGRES_USERNAME = 'postgres'
|
||||
POSTGRES_PW = 'password'
|
||||
POSTGRES_SERVER = '192.168.1.204'
|
||||
POSTGRES_PORT = '5432'
|
||||
POSTGRES_DBNAME00 = 'auburnoil'
|
||||
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
|
||||
POSTGRES_PW,
|
||||
POSTGRES_SERVER,
|
||||
POSTGRES_DBNAME00
|
||||
)
|
||||
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
|
||||
|
||||
origins = [
|
||||
"https://oil.edwineames.com",
|
||||
"https://apiauto.edwineames.com",
|
||||
]
|
||||
|
||||
# Authorize.net credentials
|
||||
API_LOGIN_ID = 'bizdev05'
|
||||
TRANSACTION_KEY = '4kJd237rZu59qAZd'
|
||||
Reference in New Issue
Block a user