Updated auth api for customer profile

This commit is contained in:
2025-09-14 11:59:31 -04:00
parent 8d134f691b
commit 5a6bcc0700
5 changed files with 431 additions and 245 deletions

View File

@@ -1,22 +1,18 @@
#
# your_app/views.py (or wherever this file is located)
#
import datetime
## File: your_app/views.py
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
prefix="/payments",
tags=["Payments & Transactions"],
)
class TransactionStatus(enum.IntEnum):
@@ -28,59 +24,121 @@ class TransactionType(enum.IntEnum):
AUTHORIZE = 1
CAPTURE = 3
# --- Helper function to avoid repeating response parsing logic ---
# This helper function is perfect, keep it.
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)
"""
# ... (Your existing _parse_authnet_response function code) ...
if response is not None 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
auth_net_transaction_id = str(response.transactionResponse.transId) if hasattr(response, 'transactionResponse') and response.transactionResponse.transId else None
rejection_reason = None
else:
status = TransactionStatus.DECLINED
auth_net_transaction_id = None
# Improved rejection reason extraction
rejection_reason = "Payment declined by gateway."
if hasattr(response, '_rejection_reason') and response._rejection_reason:
rejection_reason = str(response._rejection_reason)
elif response is not None:
# Check transaction response for errors
if hasattr(response, 'transactionResponse') and response.transactionResponse:
tr = response.transactionResponse
if hasattr(tr, 'errors') and tr.errors:
for error in tr.errors:
if hasattr(error, 'errorCode') and hasattr(error, 'errorText'):
error_code = error.errorCode.text if error.errorCode else "Unknown"
error_text = error.errorText.text if error.errorText else "No error details"
rejection_reason = f"{error_code}: {error_text}"
break
elif hasattr(tr, 'messages') and tr.messages:
if len(tr.messages) > 0:
msg = tr.messages[0]
if hasattr(msg, 'code') and hasattr(msg, 'description'):
code = msg.code.text if msg.code else "Unknown"
desc = msg.description.text if msg.description else "No description"
rejection_reason = f"{code}: {desc}"
elif response is None:
rejection_reason = "No response received from payment gateway."
if response is not None:
if hasattr(response, 'transactionResponse') and response.transactionResponse and hasattr(response.transactionResponse, 'errors') and response.transactionResponse.errors:
error = response.transactionResponse.errors[0]
rejection_reason = f"{error.errorCode.text}: {error.errorText.text}"
elif hasattr(response, 'messages') and response.messages and hasattr(response.messages, 'message') and response.messages.message:
msg = response.messages.message[0]
rejection_reason = f"{msg.code.text}: {msg.text.text}"
return status, auth_net_transaction_id, rejection_reason
# --- NEW ENDPOINT TO ADD A CARD ---
@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)
@router.post("/customers/{customer_id}/cards", response_model=schemas.Card, summary="Add a new payment card for a customer")
def add_card_to_customer(customer_id: int, card_info: schemas.CardCreate, db: Session = Depends(database.get_db)):
db_customer = crud.get_customer(db, customer_id=customer_id)
if not db_customer:
raise HTTPException(status_code=404, detail="Customer not found")
customer_schema = schemas.Customer.from_orm(db_customer)
payment_profile_id = None
if not db_customer.auth_net_profile_id:
profile_id, payment_id = payment_service.create_customer_profile(customer=customer_schema, card_info=card_info)
if not profile_id or not payment_id:
raise HTTPException(status_code=400, detail="Failed to create payment profile with Authorize.Net")
crud.update_customer_auth_net_profile_id(db, customer_id=customer_id, profile_id=profile_id)
payment_profile_id = payment_id
else:
payment_profile_id = payment_service.add_payment_profile_to_customer(
customer_profile_id=db_customer.auth_net_profile_id,
customer=customer_schema,
card_info=card_info
)
if not payment_profile_id:
raise HTTPException(status_code=400, detail="Failed to add new card to Authorize.Net profile")
new_card = crud.create_customer_card(db=db, customer_id=customer_id, card_info=card_info, payment_profile_id=payment_profile_id)
return new_card
# --- REFACTORED CHARGE ENDPOINT ---
@router.post("/charge/saved-card/{customer_id}", response_model=schemas.Transaction, summary="Charge a customer using a saved card")
def charge_saved_card(customer_id: int, transaction_req: schemas.TransactionCreateByCardID, db: Session = Depends(database.get_db)):
db_customer = crud.get_customer(db, customer_id=customer_id)
db_card = crud.get_card_by_id(db, card_id=transaction_req.card_id)
if not db_customer or not db_card or db_card.customer_id != customer_id:
raise HTTPException(status_code=404, detail="Customer or card not found for this account")
if not db_customer.auth_net_profile_id or not db_card.auth_net_payment_profile_id:
raise HTTPException(status_code=400, detail="Payment profile is not set up correctly for this customer/card")
auth_net_response = payment_service.charge_customer_profile(
customer_profile_id=db_customer.auth_net_profile_id,
payment_profile_id=db_card.auth_net_payment_profile_id,
transaction_req=transaction_req
)
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,
charge_amount=transaction_req.charge_amount,
transaction_type=TransactionType.CHARGE,
service_id=transaction_req.service_id,
delivery_id=transaction_req.delivery_id,
card_id=transaction_req.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("/authorize/saved-card/{customer_id}", response_model=schemas.Transaction, summary="Authorize a payment on a saved card")
def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionAuthorizeByCardID, db: Session = Depends(database.get_db)):
"""
Creates a pre-authorization on a customer's saved card.
This validates the card and reserves the funds.
"""
db_customer = crud.get_customer(db, customer_id=customer_id)
db_card = crud.get_card_by_id(db, card_id=transaction_req.card_id)
if not db_customer or not db_card or db_card.customer_id != customer_id:
raise HTTPException(status_code=404, detail="Customer or card not found for this account")
if not db_customer.auth_net_profile_id or not db_card.auth_net_payment_profile_id:
raise HTTPException(status_code=400, detail="Payment profile is not set up correctly for this customer/card")
# Call the NEW service function for authorization
auth_net_response = payment_service.authorize_customer_profile(
customer_profile_id=db_customer.auth_net_profile_id,
payment_profile_id=db_card.auth_net_payment_profile_id,
transaction_req=transaction_req
)
status, auth_net_transaction_id, rejection_reason = _parse_authnet_response(auth_net_response)
# Create the transaction record in your database with the correct type
transaction_data = schemas.TransactionBase(
preauthorize_amount=transaction_req.preauthorize_amount,
transaction_type=TransactionType.AUTHORIZE, # This is key
service_id=transaction_req.service_id,
delivery_id=transaction_req.delivery_id,
card_id=transaction_req.card_id,
rejection_reason=rejection_reason
)
@@ -92,57 +150,25 @@ def authorize_card(customer_id: int, transaction: schemas.TransactionAuthorize,
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)):
# --- YOUR EXISTING CAPTURE ENDPOINT NEEDS NO CHANGES ---
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
)
result = crud.create_transaction(
db=db,
transaction=transaction_data,
customer_id=customer_id,
status=status,
auth_net_transaction_id=auth_net_transaction_id
)
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)
@router.post("/capture/", response_model=schemas.Transaction, summary="Capture a previously authorized amount")
def capture_authorized_amount(transaction: schemas.TransactionCapture, db: Session = Depends(database.get_db)):
# This endpoint continues to work perfectly.
# It finds the original transaction by its ID and captures the funds.
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")
# ... The rest of your existing capture logic remains the same ...
auth_net_response = payment_service.capture_authorized_transaction(transaction)
status, _, rejection_reason = _parse_authnet_response(auth_net_response)
status, _, rejection_reason = _parse_authnet_response(auth_net_response) # The capture response doesn't return a new ID
# Use your existing CRUD function to update the transaction
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
)