cleaned up code

This commit is contained in:
2025-10-06 20:01:12 -04:00
parent d052fde8b7
commit 23a11a7009
7 changed files with 878 additions and 346 deletions

View File

@@ -4,12 +4,13 @@ from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import Tuple, Optional from typing import Tuple, Optional
import enum import enum
import logging
from .. import crud, models, schemas, database from .. import crud, models, schemas, database
from ..services import payment_service from ..services import payment_service
from config import load_config
ApplicationConfig = load_config()
logger = logging.getLogger(__name__)
@@ -193,13 +194,13 @@ def update_card_for_customer(customer_id: int, card_id: int, card_info: schemas.
try: try:
# If payment profile ID is null, refresh from Authorize.Net to sync # If payment profile ID is null, refresh from Authorize.Net to sync
if not db_card.auth_net_payment_profile_id: if not db_card.auth_net_payment_profile_id:
logger.info(f"Payment profile ID is null for card {card_id}, refreshing from Authorize.Net") print(f"Payment profile ID is null for card {card_id}, refreshing from Authorize.Net")
from ..services import user_create from ..services import user_create
user_create.refresh_customer_payment_profiles(db, customer_id, db_customer.auth_net_profile_id) user_create.refresh_customer_payment_profiles(db, customer_id, db_customer.auth_net_profile_id)
# Re-fetch the card to get the updated payment profile ID # Re-fetch the card to get the updated payment profile ID
db.refresh(db_card) db.refresh(db_card)
logger.info(f"After refresh, card {card_id} has payment_profile_id: {db_card.auth_net_payment_profile_id}") print(f"After refresh, card {card_id} has payment_profile_id: {db_card.auth_net_payment_profile_id}")
# Delete existing payment profile if it exists # Delete existing payment profile if it exists
if db_card.auth_net_payment_profile_id: if db_card.auth_net_payment_profile_id:
@@ -208,13 +209,13 @@ def update_card_for_customer(customer_id: int, card_id: int, card_info: schemas.
db_customer.auth_net_profile_id, db_card.auth_net_payment_profile_id db_customer.auth_net_profile_id, db_card.auth_net_payment_profile_id
) )
if delete_success: if delete_success:
logger.info(f"Successfully deleted old payment profile {db_card.auth_net_payment_profile_id} for card {card_id}") print(f"Successfully deleted old payment profile {db_card.auth_net_payment_profile_id} for card {card_id}")
# Clear the payment profile ID since it was deleted # Clear the payment profile ID since it was deleted
db_card.auth_net_payment_profile_id = None db_card.auth_net_payment_profile_id = None
db.add(db_card) db.add(db_card)
db.commit() db.commit()
else: else:
logger.warning(f"Failed to delete old payment profile {db_card.auth_net_payment_profile_id} for card {card_id}") print(f"Failed to delete old payment profile {db_card.auth_net_payment_profile_id} for card {card_id}")
# Create new payment profile with updated card information # Create new payment profile with updated card information
new_payment_profile_id = payment_service.add_payment_profile_to_customer( new_payment_profile_id = payment_service.add_payment_profile_to_customer(
@@ -228,7 +229,7 @@ def update_card_for_customer(customer_id: int, card_id: int, card_info: schemas.
db.add(db_card) db.add(db_card)
db.commit() db.commit()
logger.info(f"Successfully updated card {card_id} with new payment profile {new_payment_profile_id}") print(f"Successfully updated card {card_id} with new payment profile {new_payment_profile_id}")
# Return the new payment_profile_id # Return the new payment_profile_id
return {"payment_profile_id": new_payment_profile_id} return {"payment_profile_id": new_payment_profile_id}
@@ -293,9 +294,9 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
# 🚨 ENHANCED PRE-TRANSACTION VALIDATION 🚨 # 🚨 ENHANCED PRE-TRANSACTION VALIDATION 🚨
# Proactively check and fix payment profile issues before attempting transaction # Proactively check and fix payment profile issues before attempting transaction
print(f"🐛 DEBUG: Starting enhanced pre-validation for customer {customer_id}, card {db_card.id}") print(f"🐛 DEBUG: Starting enhanced pre-validation for customer {customer_id}, card {db_card.id}")
logger.info(f"🔍 PRE-TRANSACTION CHECK: Customer {customer_id}, Card {db_card.id}") print(f"🔍 PRE-TRANSACTION CHECK: Customer {customer_id}, Card {db_card.id}")
logger.info(f"🔍 Current auth_net_profile_id: '{db_customer.auth_net_profile_id}'") print(f"🔍 Current auth_net_profile_id: '{db_customer.auth_net_profile_id}'")
logger.info(f"🔍 Current payment_profile_id: '{db_card.auth_net_payment_profile_id}'") print(f"🔍 Current payment_profile_id: '{db_card.auth_net_payment_profile_id}'")
# Check for missing payment profiles OR test validity of existing ones # Check for missing payment profiles OR test validity of existing ones
needs_recovery = False needs_recovery = False
@@ -309,11 +310,11 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
# Missing/null payment profile - needs recovery # Missing/null payment profile - needs recovery
needs_recovery = True needs_recovery = True
print("🐛 DEBUG: NULL/MISSING PAYMENT PROFILE DETECTED") print("🐛 DEBUG: NULL/MISSING PAYMENT PROFILE DETECTED")
logger.warning(f"🔧 NULL/MISSING PAYMENT PROFILE DETECTED - Triggering auto-recovery") print(f"🔧 NULL/MISSING PAYMENT PROFILE DETECTED - Triggering auto-recovery")
else: else:
# Payment profile exists in DB, but let's test if it's valid in Authorize.net # Payment profile exists in DB, but let's test if it's valid in Authorize.net
print(f"🐛 DEBUG: Payment profile exists, testing validity in Authorize.net...") print(f"🐛 DEBUG: Payment profile exists, testing validity in Authorize.net...")
logger.info(f"🔍 Payment profile exists, testing validity in Authorize.net...") print(f"🔍 Payment profile exists, testing validity in Authorize.net...")
try: try:
# Quick test: try to retrieve customer payment profiles to see if our ID is valid # Quick test: try to retrieve customer payment profiles to see if our ID is valid
print(f"🐛 DEBUG: Calling get_customer_payment_profiles for profile_id: {db_customer.auth_net_profile_id}") print(f"🐛 DEBUG: Calling get_customer_payment_profiles for profile_id: {db_customer.auth_net_profile_id}")
@@ -326,9 +327,10 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
if current_id not in profile_ids_as_strings: if current_id not in profile_ids_as_strings:
needs_recovery = True needs_recovery = True
print(f"🐛 DEBUG: PAYMENT PROFILE {current_id} NOT FOUND - NEEDS RECOVERY") print(f"🐛 DEBUG: PAYMENT PROFILE {current_id} NOT FOUND - NEEDS RECOVERY")
logger.warning(f"🔧 PAYMENT PROFILE {db_card.auth_net_payment_profile_id} NOT FOUND IN AUTHORIZE.NET - Invalid ID!") print(f"🔧 PAYMENT PROFILE {db_card.auth_net_payment_profile_id} NOT FOUND IN AUTHORIZE.NET - Invalid ID!")
logger.warning(f"🔧 Available profiles in Authorize.net: {test_profiles}") print(f"🔧 Available profiles in Authorize.net: {test_profiles}")
else: else:
if ApplicationConfig.penny_test_transaction:
print(f"🐛 DEBUG: Payment profile {db_card.auth_net_payment_profile_id} exists in Authorize.net, testing usability...") print(f"🐛 DEBUG: Payment profile {db_card.auth_net_payment_profile_id} exists in Authorize.net, testing usability...")
# Profile exists in Authorize.net, but test if it's actually USABLE # Profile exists in Authorize.net, but test if it's actually USABLE
# by doing a quick test authorization with minimal amount # by doing a quick test authorization with minimal amount
@@ -356,25 +358,29 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
if "E00121" in str(test_reason) or test_status == 1: # 1 = DECLINED if "E00121" in str(test_reason) or test_status == 1: # 1 = DECLINED
print(f"🐛 DEBUG: TEST AUTH FAILED - Payment profile exists but is INVALID!") print(f"🐛 DEBUG: TEST AUTH FAILED - Payment profile exists but is INVALID!")
needs_recovery = True needs_recovery = True
logger.warning(f"🔧 PAYMENT PROFILE {db_card.auth_net_payment_profile_id} EXISTS BUT IS UNUSABLE - E00121 detected during test!") print(f"🔧 PAYMENT PROFILE {db_card.auth_net_payment_profile_id} EXISTS BUT IS UNUSABLE - E00121 detected during test!")
logger.warning(f"🔧 Test transaction failed: {test_reason}") print(f"🔧 Test transaction failed: {test_reason}")
else: else:
print(f"🐛 DEBUG: Payment profile {db_card.auth_net_payment_profile_id} is VALID and USABLE") print(f"🐛 DEBUG: Payment profile {db_card.auth_net_payment_profile_id} is VALID and USABLE")
logger.info(f"✅ Payment profile {db_card.auth_net_payment_profile_id} is valid and usable in Authorize.net") print(f"✅ Payment profile {db_card.auth_net_payment_profile_id} is valid and usable in Authorize.net")
except Exception as e: except Exception as e:
print(f"🐛 DEBUG: Exception during usability test: {str(e)} - assuming profile needs recreation") print(f"🐛 DEBUG: Exception during usability test: {str(e)} - assuming profile needs recreation")
needs_recovery = True needs_recovery = True
logger.warning(f"🔧 Could not test payment profile usability: {str(e)} - assuming it needs recreation") print(f"🔧 Could not test payment profile usability: {str(e)} - assuming it needs recreation")
else:
print(f"🔍 Skipping penny test transaction (disabled in config)")
except Exception as e: except Exception as e:
print(f"🐛 DEBUG: Exception during profile validation: {str(e)}") print(f"🐛 DEBUG: Exception during profile validation: {str(e)}")
logger.warning(f"🔧 Could not verify payment profile validity in Authorize.net: {str(e)}") print(f"🔧 Could not verify payment profile validity in Authorize.net: {str(e)}")
# If we can't verify, assume it's okay and let the transaction proceed # If we can't verify, assume it's okay and let the transaction proceed
# (better to try and fail than to block legitimate transactions) # (better to try and fail than to block legitimate transactions)
logger.info(f"⚠️ Unable to verify profile validity - proceeding with transaction anyway") print(f"⚠️ Unable to verify profile validity - proceeding with transaction anyway")
if needs_recovery: if needs_recovery:
logger.warning(f"🔧 DETECTED PAYMENT PROFILE ISSUE - Triggering auto-recovery for customer {customer_id}") print(f"🔧 DETECTED PAYMENT PROFILE ISSUE - Triggering auto-recovery for customer {customer_id}")
# Auto-recover: Refresh payment profiles before transaction # Auto-recover: Refresh payment profiles before transaction
from ..services import user_create from ..services import user_create
@@ -383,14 +389,14 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
) )
if recovery_success: if recovery_success:
logger.info("✅ Auto-recovery successful - proceeding with transaction") print("✅ Auto-recovery successful - proceeding with transaction")
# Re-fetch card data to get updated payment profile ID # Re-fetch card data to get updated payment profile ID
db.refresh(db_card) db.refresh(db_card)
db.refresh(db_customer) db.refresh(db_customer)
logger.info(f"🔍 After recovery - payment_profile_id: '{db_card.auth_net_payment_profile_id}'") print(f"🔍 After recovery - payment_profile_id: '{db_card.auth_net_payment_profile_id}'")
else: else:
logger.error("❌ Auto-recovery failed - cannot proceed with transaction") print("❌ Auto-recovery failed - cannot proceed with transaction")
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail="Payment profile setup error detected and auto-recovery failed. Please contact support." detail="Payment profile setup error detected and auto-recovery failed. Please contact support."
@@ -398,13 +404,13 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
# Final validation before proceeding # Final validation before proceeding
if not db_customer.auth_net_profile_id or not db_card.auth_net_payment_profile_id: if not db_customer.auth_net_profile_id or not db_card.auth_net_payment_profile_id:
logger.error(f"❌ CRITICAL: Payment profile validation failed after recovery attempt") print(f"❌ CRITICAL: Payment profile validation failed after recovery attempt")
raise HTTPException( raise HTTPException(
status_code=400, status_code=400,
detail="Payment profile is not set up correctly for this customer/card" detail="Payment profile is not set up correctly for this customer/card"
) )
logger.info(f"✅ Payment profile validation passed - proceeding with authorization") print(f"✅ Payment profile validation passed - proceeding with authorization")
# 🚨 ENHANCED E00121 ERROR HANDLING 🚨 # 🚨 ENHANCED E00121 ERROR HANDLING 🚨
# If transaction still fails with E00121 despite pre-validation, force-nuke the problematic ID # If transaction still fails with E00121 despite pre-validation, force-nuke the problematic ID

View File

@@ -0,0 +1,536 @@
## File: your_app/views.py
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import Tuple, Optional
import enum
import logging
from .. import crud, models, schemas, database
from ..services import payment_service
logger = logging.getLogger(__name__)
AuthNetResponse = object
router = APIRouter(
prefix="/payments",
tags=["Payments & Transactions"],
)
class TransactionStatus(enum.IntEnum):
APPROVED = 0
DECLINED = 1
class TransactionType(enum.IntEnum):
CHARGE = 0
AUTHORIZE = 1
CAPTURE = 3
# --- NEW CIM CORE FUNCTIONS ---
STATE_ID_TO_ABBREVIATION = {
0: "MA",
1: "RI",
2: "NH",
3: "ME",
4: "VT",
5: "CT",
6: "NY"
}
def _parse_authnet_response(response: Optional[AuthNetResponse]) -> Tuple[TransactionStatus, Optional[str], Optional[str]]:
"""
Parse Authorize.net response with proper attribute access for SDK objects.
Authorize.net response objects don't have .text properties, they're direct attributes.
"""
print(f"DEBUG: Parsing response, type: {type(response)}")
print(f"DEBUG: Response exists: {response is not None}")
if response is not None:
print("DEBUG: Checking for messages attribute...")
if hasattr(response, 'messages'):
print(f"DEBUG: Messages exist, resultCode: {getattr(response.messages, 'resultCode', 'NO resultCode')}")
else:
print("DEBUG: No messages attribute")
if response.messages.resultCode == "Ok":
print("DEBUG: Taking APPROVED path")
status = TransactionStatus.APPROVED
auth_net_transaction_id = None
# Extract transaction ID with proper error handling
try:
if hasattr(response, 'transactionResponse') and response.transactionResponse:
if hasattr(response.transactionResponse, 'transId') and response.transactionResponse.transId:
auth_net_transaction_id = str(response.transactionResponse.transId)
print(f"DEBUG: FOUND transaction ID: {auth_net_transaction_id}")
else:
print("DEBUG: transactionResponse exists but no transId")
else:
print("DEBUG: No transactionResponse in approved response")
except Exception as e:
print(f"DEBUG: Exception extracting transaction ID: {e}")
print(f"DEBUG: Response object inspection:")
print(type(response))
if hasattr(response, 'transactionResponse'):
print(f"TransactionResponse type: {type(response.transactionResponse)}")
print(dir(response.transactionResponse))
rejection_reason = None
print(f"DEBUG: APPROVED - ID: {auth_net_transaction_id}, rejection: {rejection_reason}")
else:
print("DEBUG: Taking DECLINED path")
status = TransactionStatus.DECLINED
auth_net_transaction_id = None
rejection_reason = "Payment declined by gateway."
if response is not None:
# Handle transaction response errors
if hasattr(response, 'transactionResponse') and response.transactionResponse:
if hasattr(response.transactionResponse, 'errors') and response.transactionResponse.errors:
print("DEBUG: Using transactionResponse.errors")
try:
error = response.transactionResponse.errors[0]
# Remove the .text access - use direct attributes
error_code = getattr(error, 'errorCode', 'Unknown')
error_text = getattr(error, 'errorText', 'Unknown error')
rejection_reason = f"{error_code}: {error_text}"
print(f"DEBUG: Transaction error: {rejection_reason}")
except Exception as e:
print(f"DEBUG: Exception parsing transaction error: {e}")
rejection_reason = "Failed to parse transaction error"
# Handle message-level errors
elif hasattr(response, 'messages') and response.messages:
if hasattr(response.messages, 'message') and response.messages.message:
print("DEBUG: Using response.messages.message")
try:
msg = response.messages.message
if isinstance(msg, list):
msg = msg[0] if msg else None
if msg:
code = getattr(msg, 'code', 'Unknown')
text = getattr(msg, 'text', 'Unknown error')
rejection_reason = f"{code}: {text}"
print(f"DEBUG: Message error: {rejection_reason}")
except Exception as e:
print(f"DEBUG: Exception parsing message error: {e}")
rejection_reason = "Failed to parse message error"
print(f"DEBUG: FINAL RESULT - Status: {status}, ID: {auth_net_transaction_id}, Reason: {rejection_reason}")
return status, auth_net_transaction_id, rejection_reason
@router.post("/customers/{customer_id}/cards", 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)):
"""
Adds a new credit card to a customer.
- If the customer doesn't have an Authorize.Net profile, it creates one.
- If they do, it adds a new payment method to their existing profile.
Returns the payment_profile_id from Authorize.Net.
"""
db_customer = crud.get_customer(db, customer_id=customer_id)
if not db_customer:
raise HTTPException(status_code=404, detail="Customer not found")
# We still need this schema for the payment service call
customer_schema = schemas.Customer.from_orm(db_customer)
payment_profile_id = None
try:
# This part now works because the service hard-codes the state to "MA"
if not db_customer.auth_net_profile_id:
profile_id, payment_id = payment_service.create_customer_profile(
customer=customer_schema, card_info=card_info
)
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
)
# Return the payment_profile_id
return {"payment_profile_id": payment_profile_id}
except ValueError as e:
# This will catch errors from the payment service
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
# This will catch any other unexpected errors, like from the database
raise HTTPException(status_code=500, detail="An internal server error occurred.")
@router.put("/customers/{customer_id}/cards/{card_id}", summary="Update an existing payment card for a customer")
def update_card_for_customer(customer_id: int, card_id: int, card_info: schemas.CardCreate, db: Session = Depends(database.get_db)):
"""
Updates an existing credit card for a customer.
- If the card has an existing Authorize.Net payment profile, deletes it first.
- Creates a new payment profile with updated card information.
- Updates the database card record with the new payment_profile_id.
Returns the new payment_profile_id from Authorize.Net.
"""
db_customer = crud.get_customer(db, customer_id=customer_id)
if not db_customer:
raise HTTPException(status_code=404, detail="Customer not found")
db_card = crud.get_card_by_id(db, card_id=card_id)
if not db_card or db_card.user_id != customer_id:
raise HTTPException(status_code=404, detail="Card not found for this customer")
if not db_customer.auth_net_profile_id:
raise HTTPException(status_code=400, detail="Customer does not have an Authorize.Net profile")
customer_schema = schemas.Customer.from_orm(db_customer)
try:
# If payment profile ID is null, refresh from Authorize.Net to sync
if not db_card.auth_net_payment_profile_id:
logger.info(f"Payment profile ID is null for card {card_id}, refreshing from Authorize.Net")
from ..services import user_create
user_create.refresh_customer_payment_profiles(db, customer_id, db_customer.auth_net_profile_id)
# Re-fetch the card to get the updated payment profile ID
db.refresh(db_card)
logger.info(f"After refresh, card {card_id} has payment_profile_id: {db_card.auth_net_payment_profile_id}")
# Delete existing payment profile if it exists
if db_card.auth_net_payment_profile_id:
from ..services import user_delete
delete_success = user_delete._delete_payment_profile(
db_customer.auth_net_profile_id, db_card.auth_net_payment_profile_id
)
if delete_success:
logger.info(f"Successfully deleted old payment profile {db_card.auth_net_payment_profile_id} for card {card_id}")
# Clear the payment profile ID since it was deleted
db_card.auth_net_payment_profile_id = None
db.add(db_card)
db.commit()
else:
logger.warning(f"Failed to delete old payment profile {db_card.auth_net_payment_profile_id} for card {card_id}")
# Create new payment profile with updated card information
new_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
)
# Update the database card with the new payment profile ID
db_card.auth_net_payment_profile_id = new_payment_profile_id
db.add(db_card)
db.commit()
logger.info(f"Successfully updated card {card_id} with new payment profile {new_payment_profile_id}")
# Return the new payment_profile_id
return {"payment_profile_id": new_payment_profile_id}
except ValueError as e:
db.rollback()
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
db.rollback()
print(f"Failed to update card {card_id}: {str(e)}")
raise HTTPException(status_code=500, detail="An internal server error occurred.")
@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.user_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(
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.
"""
print("🐛 DEBUG: ENTERING authorize_saved_card ROUTER FUNCTION")
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.user_id != customer_id:
raise HTTPException(status_code=404, detail="Customer or card not found for this account")
print("🐛 DEBUG: CUSTOMER AND CARD FOUND, STARTING PRE-VALIDATION")
# 🚨 ENHANCED PRE-TRANSACTION VALIDATION 🚨
# Proactively check and fix payment profile issues before attempting transaction
print(f"🐛 DEBUG: Starting enhanced pre-validation for customer {customer_id}, card {db_card.id}")
logger.info(f"🔍 PRE-TRANSACTION CHECK: Customer {customer_id}, Card {db_card.id}")
logger.info(f"🔍 Current auth_net_profile_id: '{db_customer.auth_net_profile_id}'")
logger.info(f"🔍 Current payment_profile_id: '{db_card.auth_net_payment_profile_id}'")
# Check for missing payment profiles OR test validity of existing ones
needs_recovery = False
print(f"🐛 DEBUG: Checking payment profiles - customer_id: {db_customer.auth_net_profile_id}, card_id: {db_card.auth_net_payment_profile_id}")
if (not db_customer.auth_net_profile_id or
not db_card.auth_net_payment_profile_id or
db_card.auth_net_payment_profile_id.strip() == "" or
str(db_card.auth_net_payment_profile_id).lower() == "none" or
db_card.auth_net_payment_profile_id is None):
# Missing/null payment profile - needs recovery
needs_recovery = True
print("🐛 DEBUG: NULL/MISSING PAYMENT PROFILE DETECTED")
logger.warning(f"🔧 NULL/MISSING PAYMENT PROFILE DETECTED - Triggering auto-recovery")
else:
# Payment profile exists in DB, but let's test if it's valid in Authorize.net
print(f"🐛 DEBUG: Payment profile exists, testing validity in Authorize.net...")
logger.info(f"🔍 Payment profile exists, testing validity in Authorize.net...")
try:
# Quick test: try to retrieve customer payment profiles to see if our ID is valid
print(f"🐛 DEBUG: Calling get_customer_payment_profiles for profile_id: {db_customer.auth_net_profile_id}")
test_profiles = payment_service.get_customer_payment_profiles(db_customer.auth_net_profile_id)
print(f"🐛 DEBUG: Got profiles from Authorize.net: {test_profiles}")
current_id = str(db_card.auth_net_payment_profile_id)
profile_ids_as_strings = [str(pid) for pid in test_profiles if pid]
print(f"🐛 DEBUG: Checking if '{current_id}' is in: {profile_ids_as_strings}")
if current_id not in profile_ids_as_strings:
needs_recovery = True
print(f"🐛 DEBUG: PAYMENT PROFILE {current_id} NOT FOUND - NEEDS RECOVERY")
logger.warning(f"🔧 PAYMENT PROFILE {db_card.auth_net_payment_profile_id} NOT FOUND IN AUTHORIZE.NET - Invalid ID!")
logger.warning(f"🔧 Available profiles in Authorize.net: {test_profiles}")
else:
print(f"🐛 DEBUG: Payment profile {db_card.auth_net_payment_profile_id} exists in Authorize.net, testing usability...")
# Profile exists in Authorize.net, but test if it's actually USABLE
# by doing a quick test authorization with minimal amount
try:
print(f"🐛 DEBUG: Testing if payment profile is actually usable...")
# Create a tiny test transaction (like $0.01) to validate the card works
test_transaction_req = schemas.TransactionAuthorizeByCardID(
card_id=db_card.id,
preauthorize_amount="0.01" # Minimal test amount
)
test_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=test_transaction_req,
db_session=None, # Don't pass DB session for test transaction
customer_id=None,
card_id=None
)
# Check if the test authorization worked
from ..services import payment_service as ps # Need access to _parse_authnet_response
test_status, _, test_reason = _parse_authnet_response(test_response)
if "E00121" in str(test_reason) or test_status == 1: # 1 = DECLINED
print(f"🐛 DEBUG: TEST AUTH FAILED - Payment profile exists but is INVALID!")
needs_recovery = True
logger.warning(f"🔧 PAYMENT PROFILE {db_card.auth_net_payment_profile_id} EXISTS BUT IS UNUSABLE - E00121 detected during test!")
logger.warning(f"🔧 Test transaction failed: {test_reason}")
else:
print(f"🐛 DEBUG: Payment profile {db_card.auth_net_payment_profile_id} is VALID and USABLE")
logger.info(f"✅ Payment profile {db_card.auth_net_payment_profile_id} is valid and usable in Authorize.net")
except Exception as e:
print(f"🐛 DEBUG: Exception during usability test: {str(e)} - assuming profile needs recreation")
needs_recovery = True
logger.warning(f"🔧 Could not test payment profile usability: {str(e)} - assuming it needs recreation")
except Exception as e:
print(f"🐛 DEBUG: Exception during profile validation: {str(e)}")
logger.warning(f"🔧 Could not verify payment profile validity in Authorize.net: {str(e)}")
# If we can't verify, assume it's okay and let the transaction proceed
# (better to try and fail than to block legitimate transactions)
logger.info(f"⚠️ Unable to verify profile validity - proceeding with transaction anyway")
if needs_recovery:
logger.warning(f"🔧 DETECTED PAYMENT PROFILE ISSUE - Triggering auto-recovery for customer {customer_id}")
# Auto-recover: Refresh payment profiles before transaction
from ..services import user_create
recovery_success = user_create.refresh_customer_payment_profiles(
db, customer_id, db_customer.auth_net_profile_id
)
if recovery_success:
logger.info("✅ Auto-recovery successful - proceeding with transaction")
# Re-fetch card data to get updated payment profile ID
db.refresh(db_card)
db.refresh(db_customer)
logger.info(f"🔍 After recovery - payment_profile_id: '{db_card.auth_net_payment_profile_id}'")
else:
logger.error("❌ Auto-recovery failed - cannot proceed with transaction")
raise HTTPException(
status_code=400,
detail="Payment profile setup error detected and auto-recovery failed. Please contact support."
)
# Final validation before proceeding
if not db_customer.auth_net_profile_id or not db_card.auth_net_payment_profile_id:
logger.error(f"❌ CRITICAL: Payment profile validation failed after recovery attempt")
raise HTTPException(
status_code=400,
detail="Payment profile is not set up correctly for this customer/card"
)
logger.info(f"✅ Payment profile validation passed - proceeding with authorization")
# 🚨 ENHANCED E00121 ERROR HANDLING 🚨
# If transaction still fails with E00121 despite pre-validation, force-nuke the problematic ID
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,
db_session=db,
customer_id=customer_id,
card_id=db_card.id
)
# Parse the transaction response (no need for E00121 nuclear cleanup since pre-validation should have caught it)
transaction_status, auth_net_transaction_id, rejection_reason = _parse_authnet_response(auth_net_response)
print(transaction_req)
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,
auto_id=transaction_req.auto_id,
card_id=transaction_req.card_id,
rejection_reason=rejection_reason
)
db_transaction = crud.create_transaction(
db=db,
transaction=transaction_data,
customer_id=customer_id,
status=transaction_status,
auth_net_transaction_id=auth_net_transaction_id
)
return db_transaction
def _nuclear_e00121_payment_profile_cleanup(db: Session, customer_id: int, card_id: int, corrupted_profile_id: str, customer_profile_id: str) -> bool:
"""
DEDICATED E00121 NUKER: Forcefully removes problematic payment profile IDs from both database and Authorize.net.
This is the nuclear option for when E00121 payment profile errors persist despite all attempts to recover.
Args:
db: Database session
customer_id: Customer ID
card_id: Card ID that has the corrupted profile
corrupted_profile_id: The problematic payment profile ID causing E00121
customer_profile_id: Customer profile ID in Authorize.net
Returns:
bool: True if cleanup successful and card can be retried
"""
print("💣 NUCLEAR E00121 CLEANUP INITIATED! 💣")
print(f"💣 Target: Card {card_id}, Profile {corrupted_profile_id}")
try:
from .. import crud
from ..services import user_delete
# Step 1: Get the card from database
card = crud.get_card_by_id(db, card_id)
if not card:
print(f"💣 ERROR: Card {card_id} not found in database")
return False
# Step 2: NUKE FROM AUTHORIZE.NET FIRST
print(f"💣 Deleting corrupted profile {corrupted_profile_id} from Authorize.net...")
try:
delete_success = user_delete._delete_payment_profile(customer_profile_id, corrupted_profile_id)
if delete_success:
print("✅ Successfully deleted corrupted profile from Authorize.net")
else:
print("⚠️ Profile may not have existed in Authorize.net or delete failed")
except Exception as e:
print(f"⚠️ Exception deleting from Authorize.net: {str(e)} - continuing anyway")
# Step 3: NUKE FROM DATABASE REGARDLESS
print(f"💣 Clearing corrupted profile ID {corrupted_profile_id} from database card {card_id}")
card.auth_net_payment_profile_id = None
db.add(card)
db.commit()
print("✅ Successfully cleared corrupted profile ID from database")
# Step 4: Attempt immediate recreation of payment profile
print("💣 Attempting immediate recreation of payment profile...")
try:
customer = crud.get_customer(db, customer_id)
# Format card data for recreation
# Convert to string first to handle cases where database returns int instead of string
exp_year_str = str(card.expiration_year)
exp_month_str = str(card.expiration_month)
exp_year = exp_year_str.zfill(4) if len(exp_year_str) < 4 else exp_year_str
exp_month = exp_month_str.zfill(2) if len(exp_month_str) < 2 else exp_month_str
exp_date = f"{exp_year}-{exp_month}"
card_create_data = schemas.CardCreate(
card_number=card.card_number,
expiration_date=exp_date,
cvv=card.security_number
)
from ..services import payment_service
new_profile_id = payment_service.add_payment_profile_to_customer(
customer_profile_id, customer, card_create_data, is_default=(card.main_card == True)
)
if new_profile_id:
print(f"✅ IMMEDIATE RECREATION SUCCESSFUL: New profile {new_profile_id}")
card.auth_net_payment_profile_id = str(new_profile_id)
db.add(card)
db.commit()
print(f"💣 Nuclear cleanup COMPLETE - Card {card_id} has new profile {new_profile_id}")
return True
else:
print("❌ Immediate recreation failed - no new profile ID")
return False
except Exception as recreate_e:
print(f"❌ Immediate recreation failed: {str(recreate_e)}")
print("💣 Database cleared but recreation failed - needs manual recreation later")
return False
except Exception as nuke_e:
print(f"💣 CRITICAL ERROR in nuclear cleanup: {str(nuke_e)}")
db.rollback()
return False
# --- CAPTURE ENDPOINT MOVED TO TRANSACTION ROUTER ---

View File

@@ -4,21 +4,32 @@ from .. import crud, database, schemas
from config import load_config from config import load_config
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
# Load Authorize.net credentials
ApplicationConfig = load_config()
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
# Set Authorize.net environment based on configuration # Set Authorize.net environment based on configuration
from authorizenet.constants import constants from authorizenet.constants import constants
from config import load_config # Assuming you have this
# Load Authorize.net credentials
ApplicationConfig = load_config()
# Set Authorize.net environment based on configuration
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION': if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
constants.environment = constants.PRODUCTION constants.environment = constants.PRODUCTION
VALIDATION_MODE = "liveMode"
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
else: else:
constants.environment = constants.SANDBOX constants.environment = constants.SANDBOX
constants.show_url_on_request = True constants.show_url_on_request = True
VALIDATION_MODE = "testMode"
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
def verify_customer_authorize_account(db: Session, customer_id: int) -> dict: def verify_customer_authorize_account(db: Session, customer_id: int) -> dict:
""" """
@@ -173,8 +184,13 @@ def _get_customer_profile(profile_id: str):
) )
controller = controllers.getCustomerProfileController(request) controller = controllers.getCustomerProfileController(request)
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
controller.setenvironment(constants.PRODUCTION) controller.setenvironment(constants.PRODUCTION)
controller.execute() controller.execute()
else:
controller.execute()
response = controller.getresponse() response = controller.getresponse()
return response return response

View File

@@ -20,20 +20,48 @@ ApplicationConfig = load_config()
# Set Authorize.net environment based on configuration # Set Authorize.net environment based on configuration
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION': if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
constants.environment = constants.PRODUCTION constants.environment = constants.PRODUCTION
print("Payment Service Production")
VALIDATION_MODE = "liveMode" VALIDATION_MODE = "liveMode"
API_LOGIN_ID = '4d2Mn6H23R' API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
TRANSACTION_KEY = '7B94d8xfTQXv37WS' TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
else: else:
constants.environment = constants.SANDBOX constants.environment = constants.SANDBOX
VALIDATION_MODE = "testMode" VALIDATION_MODE = "testMode"
print("Payment Service Sandbox")
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
constants.show_url_on_request = True # Very useful for debugging constants.show_url_on_request = True # Very useful for debugging
def _is_e00121_response(response):
"""
Check if the Authorize.Net response contains E00121 error (invalid payment profile ID).
"""
if response is None:
return False
try:
if hasattr(response, 'messages') and response.messages is not None:
# Check for E00121 in different response message structures
if hasattr(response.messages, 'message'):
message = response.messages.message
# Handle list of messages
if isinstance(message, list):
for msg in message:
if getattr(msg, 'code', '') == 'E00121':
print("E00121 detected in message list")
return True
# Handle single message
elif hasattr(message, 'code'):
if message.code == 'E00121':
print(f"E00121 detected: '{getattr(message, 'text', 'No details')}'")
return True
else:
print(f"Message code: '{message.code}' (not E00121)")
return False
except Exception as e:
print(f"Error checking for E00121: {str(e)}")
return False
def _get_authnet_error_message(response): def _get_authnet_error_message(response):
""" """
@@ -67,24 +95,11 @@ def create_customer_profile(customer: schemas.Customer, card_info: schemas.CardC
This version sanitizes and trims customer data before sending. This version sanitizes and trims customer data before sending.
""" """
print(f"Attempting to create Auth.Net profile for customer ID: {customer.id}") print(f"Attempting to create Auth.Net profile for customer ID: {customer.id}")
print("dogpoop")
print(ApplicationConfig.API_LOGIN_ID)
print(ApplicationConfig.TRANSACTION_KEY)
try: try:
merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY) merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY)
except Exception as e: except Exception as e:
print(e) print(e)
print("here1234")
# --- DATA SANITIZATION LOGIC ---
def sanitize(text, max_len, allow_spaces=False, is_zip=False):
if not text:
return ""
if is_zip:
pattern = r'[^a-zA-Z0-9-]'
else:
pattern = r'[^a-zA-Z0-9]' if not allow_spaces else r'[^a-zA-Z0-9\s]'
sanitized = re.sub(pattern, '', str(text))
return sanitized.strip()[:max_len]
# API max lengths: email=255 # API max lengths: email=255
email = (customer.customer_email or f"no-email-{customer.id}@example.com")[:255] email = (customer.customer_email or f"no-email-{customer.id}@example.com")[:255]
@@ -102,30 +117,21 @@ def create_customer_profile(customer: schemas.Customer, card_info: schemas.CardC
controller = createCustomerProfileController(request) controller = createCustomerProfileController(request)
try:
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
controller.setenvironment(constants.PRODUCTION) controller.setenvironment(constants.PRODUCTION)
controller.execute() controller.execute()
else:
controller.execute()
response = controller.getresponse() response = controller.getresponse()
print("************")
print(response)
print(response.messages)
print(response.messages.resultCode)
print("************")
except Exception as e:
print("^^^")
print(response)
print(response.messages)
print(response.messages.resultCode)
print("^^^")
print(f"API execution failed: {traceback.format_exc()}")
raise ValueError("API call execution error")
try: try:
if response.messages.resultCode == "Ok": if response.messages.resultCode == "Ok":
profile_id = response.customerProfileId profile_id = response.customerProfileId
print(profile_id) print(profile_id)
# Payment profile ID is not available since profiles are added separately # # Payment profile ID is not available since profiles are added separately
payment_id = "" # payment_id = ""
print(f"SUCCESS: Created customer profile: {profile_id} (payment profiles added separately)") print(f"SUCCESS: Created customer profile: {profile_id} (payment profiles added separately)")
# Add detailed logging # Add detailed logging
@@ -149,102 +155,6 @@ def create_customer_profile(customer: schemas.Customer, card_info: schemas.CardC
def add_payment_profile_to_customer(customer_profile_id: str, customer: schemas.Customer, card_info: schemas.CardCreate, is_default: bool = False):
print(f"Adding {'default ' if is_default else ''}payment profile to Auth.Net customer profile ID: {customer_profile_id}")
merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY)
def sanitize(text, max_len, allow_spaces=False, is_zip=False):
if not text:
return ""
if is_zip:
pattern = r'[^a-zA-Z0-9-]'
else:
pattern = r'[^a-zA-Z0-9]' if not allow_spaces else r'[^a-zA-Z0-9\s]'
sanitized = re.sub(pattern, '', str(text))
return sanitized.strip()[:max_len]
first_name = sanitize(customer.customer_first_name, 50) or "N/A"
last_name = sanitize(customer.customer_last_name, 50) or "N/A"
address = sanitize(customer.customer_address, 60, allow_spaces=True) or "N/A"
city = sanitize(customer.customer_town, 40) or "N/A"
# ========= CHANGE 1.B: ADD STATE HERE =========
state = sanitize(customer.customer_state, 40) or "MA" # Defaulting to MA for safety
zip_code = sanitize(customer.customer_zip, 20, is_zip=True)
# Fix expiration date format for cards
try:
expiration_year = int(card_info.expiration_date.split('-')[0])
expiration_month = int(card_info.expiration_date.split('-')[1])
expiration_date = f"{expiration_month:02d}{expiration_year % 100:02d}"
except (ValueError, IndexError):
sanitized_exp = card_info.expiration_date.replace('/', '').replace('-', '')
if len(sanitized_exp) == 4:
expiration_date = sanitized_exp
else:
expiration_date = "0325"
print(f"Parsed expiration date for card: {card_info.expiration_date} -> {expiration_date}")
creditCard = apicontractsv1.creditCardType(
cardNumber=card_info.card_number,
expirationDate=expiration_date,
cardCode=card_info.cvv
)
billTo = apicontractsv1.customerAddressType(
firstName=first_name,
lastName=last_name,
address=address,
city=city,
state=state,
zip=zip_code,
country="USA"
)
paymentProfile = apicontractsv1.customerPaymentProfileType(
billTo=billTo,
payment=apicontractsv1.paymentType(creditCard=creditCard),
defaultPaymentProfile=is_default
)
request = apicontractsv1.createCustomerPaymentProfileRequest(
merchantAuthentication=merchantAuth,
customerProfileId=customer_profile_id,
paymentProfile=paymentProfile,
# ========= CHANGE 2.B: USE liveMode =========
validationMode=VALIDATION_MODE
)
controller = createCustomerPaymentProfileController(request)
try:
controller.setenvironment(constants.PRODUCTION)
controller.execute()
response = controller.getresponse()
if response.messages.resultCode == "Ok":
# Fix: Proper payment profile ID extraction (same bug fix as above)
if hasattr(response, 'customerPaymentProfileId') and response.customerPaymentProfileId:
return str(response.customerPaymentProfileId)
else:
print("WARNING: Added payment profile but no ID returned")
raise ValueError("Payment profile created but ID not found in response")
else:
error_msg = _get_authnet_error_message(response)
print(f"Failed to add payment profile: {error_msg}")
print(f"SANITIZED DATA SENT FOR ADD PROFILE: FirstName='{first_name}', LastName='{last_name}', Address='{address}', City='{city}', State='{state}', Zip='{zip_code}'")
print(f"Card info: number='{card_info.card_number}', exp='{card_info.expiration_date}', cvv='{card_info.cvv}'") # Mask if sensitive
print(pprint.pformat(vars(billTo)))
print(pprint.pformat(vars(request)))
raise ValueError(error_msg)
except Exception as e:
print(f"A critical exception occurred during the API call: {traceback.format_exc()}")
raise ValueError("Could not connect to the payment gateway.")
def authorize_customer_profile(customer_profile_id: str, payment_profile_id: str, transaction_req: schemas.TransactionAuthorizeByCardID, db_session=None, customer_id=None, card_id=None): def authorize_customer_profile(customer_profile_id: str, payment_profile_id: str, transaction_req: schemas.TransactionAuthorizeByCardID, db_session=None, customer_id=None, card_id=None):
""" """
Creates an AUTH_ONLY transaction against a customer profile with automatic E00121 recovery. Creates an AUTH_ONLY transaction against a customer profile with automatic E00121 recovery.
@@ -331,8 +241,13 @@ def _perform_authorization(customer_profile_id: str, payment_profile_id: str, tr
) )
controller = createTransactionController(createtransactionrequest) controller = createTransactionController(createtransactionrequest)
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
controller.setenvironment(constants.PRODUCTION) controller.setenvironment(constants.PRODUCTION)
controller.execute() controller.execute()
else:
controller.execute()
response = controller.getresponse() response = controller.getresponse()
# Log response details # Log response details
@@ -345,37 +260,7 @@ def _perform_authorization(customer_profile_id: str, payment_profile_id: str, tr
return response return response
def _is_e00121_response(response):
"""
Check if the Authorize.Net response contains E00121 error (invalid payment profile ID).
"""
if response is None:
return False
try:
if hasattr(response, 'messages') and response.messages is not None:
# Check for E00121 in different response message structures
if hasattr(response.messages, 'message'):
message = response.messages.message
# Handle list of messages
if isinstance(message, list):
for msg in message:
if getattr(msg, 'code', '') == 'E00121':
print("E00121 detected in message list")
return True
# Handle single message
elif hasattr(message, 'code'):
if message.code == 'E00121':
print(f"E00121 detected: '{getattr(message, 'text', 'No details')}'")
return True
else:
print(f"Message code: '{message.code}' (not E00121)")
return False
except Exception as e:
print(f"Error checking for E00121: {str(e)}")
return False
def capture_authorized_transaction(transaction_req: schemas.TransactionCapture): def capture_authorized_transaction(transaction_req: schemas.TransactionCapture):
@@ -396,11 +281,115 @@ def capture_authorized_transaction(transaction_req: schemas.TransactionCapture):
) )
controller = createTransactionController(createtransactionrequest) controller = createTransactionController(createtransactionrequest)
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
controller.setenvironment(constants.PRODUCTION) controller.setenvironment(constants.PRODUCTION)
controller.execute() controller.execute()
else:
controller.execute()
return controller.getresponse() return controller.getresponse()
def add_payment_profile_to_customer(customer_profile_id: str, customer: schemas.Customer, card_info: schemas.CardCreate, is_default: bool = False):
print(f"Adding {'default ' if is_default else ''}payment profile to Auth.Net customer profile ID: {customer_profile_id}")
merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY)
def sanitize(text, max_len, allow_spaces=False, is_zip=False):
if not text:
return ""
if is_zip:
pattern = r'[^a-zA-Z0-9-]'
else:
pattern = r'[^a-zA-Z0-9]' if not allow_spaces else r'[^a-zA-Z0-9\s]'
sanitized = re.sub(pattern, '', str(text))
return sanitized.strip()[:max_len]
first_name = sanitize(customer.customer_first_name, 50) or "N/A"
last_name = sanitize(customer.customer_last_name, 50) or "N/A"
address = sanitize(customer.customer_address, 60, allow_spaces=True) or "N/A"
city = sanitize(customer.customer_town, 40) or "N/A"
# ========= CHANGE 1.B: ADD STATE HERE =========
state = sanitize(customer.customer_state, 40) or "MA" # Defaulting to MA for safety
zip_code = sanitize(customer.customer_zip, 20, is_zip=True)
# Fix expiration date format for cards
try:
expiration_year = int(card_info.expiration_date.split('-')[0])
expiration_month = int(card_info.expiration_date.split('-')[1])
expiration_date = f"{expiration_month:02d}{expiration_year % 100:02d}"
except (ValueError, IndexError):
sanitized_exp = card_info.expiration_date.replace('/', '').replace('-', '')
if len(sanitized_exp) == 4:
expiration_date = sanitized_exp
else:
expiration_date = "0325"
print(f"Parsed expiration date for card: {card_info.expiration_date} -> {expiration_date}")
creditCard = apicontractsv1.creditCardType(
cardNumber=card_info.card_number,
expirationDate=expiration_date,
cardCode=card_info.cvv
)
billTo = apicontractsv1.customerAddressType(
firstName=first_name,
lastName=last_name,
address=address,
city=city,
state=state,
zip=zip_code,
country="USA"
)
paymentProfile = apicontractsv1.customerPaymentProfileType(
billTo=billTo,
payment=apicontractsv1.paymentType(creditCard=creditCard),
defaultPaymentProfile=is_default
)
request = apicontractsv1.createCustomerPaymentProfileRequest(
merchantAuthentication=merchantAuth,
customerProfileId=customer_profile_id,
paymentProfile=paymentProfile,
# ========= CHANGE 2.B: USE liveMode =========
validationMode=VALIDATION_MODE
)
controller = createCustomerPaymentProfileController(request)
try:
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
controller.setenvironment(constants.PRODUCTION)
controller.execute()
else:
controller.execute()
response = controller.getresponse()
if response.messages.resultCode == "Ok":
# Fix: Proper payment profile ID extraction (same bug fix as above)
if hasattr(response, 'customerPaymentProfileId') and response.customerPaymentProfileId:
return str(response.customerPaymentProfileId)
else:
print("WARNING: Added payment profile but no ID returned")
raise ValueError("Payment profile created but ID not found in response")
else:
error_msg = _get_authnet_error_message(response)
print(f"Failed to add payment profile: {error_msg}")
print(f"SANITIZED DATA SENT FOR ADD PROFILE: FirstName='{first_name}', LastName='{last_name}', Address='{address}', City='{city}', State='{state}', Zip='{zip_code}'")
print(f"Card info: number='{card_info.card_number}', exp='{card_info.expiration_date}', cvv='{card_info.cvv}'") # Mask if sensitive
print(pprint.pformat(vars(billTo)))
print(pprint.pformat(vars(request)))
raise ValueError(error_msg)
except Exception as e:
print(f"A critical exception occurred during the API call: {traceback.format_exc()}")
raise ValueError("Could not connect to the payment gateway.")
def get_customer_payment_profiles(customer_profile_id: str): def get_customer_payment_profiles(customer_profile_id: str):
""" """
@@ -420,8 +409,12 @@ def get_customer_payment_profiles(customer_profile_id: str):
controller = getCustomerProfileController(request) controller = getCustomerProfileController(request)
try: try:
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
controller.setenvironment(constants.PRODUCTION) controller.setenvironment(constants.PRODUCTION)
controller.execute() controller.execute()
else:
controller.execute()
response = controller.getresponse() response = controller.getresponse()
if response.messages.resultCode == "Ok": if response.messages.resultCode == "Ok":
@@ -467,7 +460,11 @@ def charge_customer_profile(customer_profile_id: str, payment_profile_id: str, t
) )
controller = createTransactionController(createtransactionrequest) controller = createTransactionController(createtransactionrequest)
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
controller.setenvironment(constants.PRODUCTION) controller.setenvironment(constants.PRODUCTION)
controller.execute() controller.execute()
else:
controller.execute()
# The response is returned directly to the router to be parsed there # The response is returned directly to the router to be parsed there
return controller.getresponse() return controller.getresponse()

View File

@@ -22,17 +22,11 @@ ApplicationConfig = load_config()
# Set Authorize.net environment based on configuration # Set Authorize.net environment based on configuration
if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION': if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION':
#constants.environment = constants.PRODUCTION #constants.environment = constants.PRODUCTION
print("Constants:", ApplicationConfig.CURRENT_SETTINGS) API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
print("Authorize.Net URL:", constants.environment) TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
API_LOGIN_ID = '4d2Mn6H23R'
TRANSACTION_KEY = '7B94d8xfTQXv37WS'
# Override to standard production endpoint # Override to standard production endpoint
constants.environment = "https://api.authorize.net/xml/v1/request.api"
print("Overridden Authorize.Net URL:", constants.environment)
else: else:
print("Constants:", ApplicationConfig.CURRENT_SETTINGS)
constants.environment = constants.SANDBOX constants.environment = constants.SANDBOX
print("Authorize.Net URL:", constants.environment)
API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID
TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
@@ -40,33 +34,6 @@ constants.show_url_on_request = True
def _get_error_message(response):
"""
Robust error parsing function that correctly handles the API's response format.
"""
if response is None:
return "No response from payment gateway."
try:
if hasattr(response, 'messages') and response.messages is not None:
if hasattr(response, 'transactionResponse') and response.transactionResponse and hasattr(response.transactionResponse, 'errors') and response.transactionResponse.errors:
error = response.transactionResponse.errors[0]
return f"Error {error.errorCode}: {error.errorText}"
if hasattr(response.messages, 'message'):
message_list = response.messages.message
if not isinstance(message_list, list):
message_list = [message_list]
if message_list:
msg = message_list[0]
code = msg.code if hasattr(msg, 'code') else 'Unknown'
text = msg.text if hasattr(msg, 'text') else 'No details provided.'
return f"Error {code}: {text}"
except Exception as e:
logger.error(f"Error while parsing Auth.Net error message: {e}")
return "An unparsable error occurred with the payment gateway."
return "An unknown error occurred with the payment gateway."
def create_user_account(db: Session, customer_id: int) -> dict: def create_user_account(db: Session, customer_id: int) -> dict:
""" """
@@ -92,7 +59,7 @@ def create_user_account(db: Session, customer_id: int) -> dict:
"message": f"Customer {customer_id} not found", "message": f"Customer {customer_id} not found",
"profile_id": None "profile_id": None
} }
print(customer) print(f"Found customer id={customer}")
# Get customer's cards from database # Get customer's cards from database
cards = crud.get_customer_cards(db, customer_id) cards = crud.get_customer_cards(db, customer_id)
if not cards: if not cards:
@@ -107,9 +74,9 @@ def create_user_account(db: Session, customer_id: int) -> dict:
# Get the first card to use for initial profile creation # Get the first card to use for initial profile creation
first_card = cards[0] first_card = cards[0]
logger.info(f"Using first card ID={first_card.id} for profile creation") print(f"Using first card ID={first_card.id} for profile creation")
logger.info(f"Card Number: {first_card.card_number[:4]}**** ****{first_card.card_number[-4:]}") print(f"Card Number: {first_card.card_number[:4]}**** ****{first_card.card_number[-4:]}")
logger.info(f"Expiration: {first_card.expiration_month}/{first_card.expiration_year}") print(f"Expiration: {first_card.expiration_month}/{first_card.expiration_year}")
# Create CardCreate object for the first card # Create CardCreate object for the first card
# Format expiration date for string values - pad year to 4 digits and month to 2 digits # Format expiration date for string values - pad year to 4 digits and month to 2 digits
@@ -125,8 +92,8 @@ def create_user_account(db: Session, customer_id: int) -> dict:
cvv=first_card.security_number cvv=first_card.security_number
) )
logger.info(f"Card info expiration_date: {card_info.expiration_date}") print(f"Card info expiration_date: {card_info.expiration_date}")
logger.info(f"Processing Authorize.net profile for customer {customer_id} with {len(cards)} cards") print(f"Processing Authorize.net profile for customer {customer_id} with {len(cards)} cards")
# Create customer profile and payment profiles if not exists # Create customer profile and payment profiles if not exists
if not customer.auth_net_profile_id: if not customer.auth_net_profile_id:
@@ -135,7 +102,7 @@ def create_user_account(db: Session, customer_id: int) -> dict:
auth_profile_id, _ = payment_service.create_customer_profile(customer, card_info) auth_profile_id, _ = payment_service.create_customer_profile(customer, card_info)
except ValueError as e: except ValueError as e:
error_str = str(e) error_str = str(e)
logger.error(f"API call failed: {error_str}") print(f"API call failed: {error_str}")
return { return {
"success": False, "success": False,
@@ -146,7 +113,7 @@ def create_user_account(db: Session, customer_id: int) -> dict:
} }
if not auth_profile_id: if not auth_profile_id:
logger.error("No auth_profile_id returned from API") print("No auth_profile_id returned from API")
return { return {
"success": False, "success": False,
"message": "Failed to create customer profile - no profile ID returned", "message": "Failed to create customer profile - no profile ID returned",
@@ -158,12 +125,12 @@ def create_user_account(db: Session, customer_id: int) -> dict:
first_payment_profile_id = payment_service.add_payment_profile_to_customer( first_payment_profile_id = payment_service.add_payment_profile_to_customer(
auth_profile_id, customer, card_info, is_default=True auth_profile_id, customer, card_info, is_default=True
) )
logger.info(f"Successfully added first payment profile: {first_payment_profile_id} (default)") print(f"Successfully added first payment profile: {first_payment_profile_id} (default)")
# Assign to first_card # Assign to first_card
first_card.auth_net_payment_profile_id = first_payment_profile_id first_card.auth_net_payment_profile_id = first_payment_profile_id
db.add(first_card) db.add(first_card)
except ValueError as e: except ValueError as e:
logger.error(f"Failed to add payment profile for first card: {str(e)}") print(f"Failed to add payment profile for first card: {str(e)}")
return { return {
"success": False, "success": False,
"message": f"Failed to add first payment profile: {str(e)}", "message": f"Failed to add first payment profile: {str(e)}",
@@ -190,19 +157,19 @@ def create_user_account(db: Session, customer_id: int) -> dict:
payment_profile_id = payment_service.add_payment_profile_to_customer( payment_profile_id = payment_service.add_payment_profile_to_customer(
auth_profile_id, customer, card_info_additional, is_default=False auth_profile_id, customer, card_info_additional, is_default=False
) )
logger.info(f"Successfully added additional payment profile ID '{payment_profile_id}' for card {card.id}") print(f"Successfully added additional payment profile ID '{payment_profile_id}' for card {card.id}")
except ValueError as e: except ValueError as e:
logger.error(f"Failed to add payment profile for additional card {card.id}: {str(e)}") print(f"Failed to add payment profile for additional card {card.id}: {str(e)}")
else: else:
auth_profile_id = customer.auth_net_profile_id auth_profile_id = customer.auth_net_profile_id
logger.info(f"Using existing Authorize.net profile {auth_profile_id}") print(f"Using existing Authorize.net profile {auth_profile_id}")
# RETRIEVE ALL PAYMENT PROFILE IDs - This is the key step # RETRIEVE ALL PAYMENT PROFILE IDs - This is the key step
try: try:
payment_profile_ids = payment_service.get_customer_payment_profiles(auth_profile_id) payment_profile_ids = payment_service.get_customer_payment_profiles(auth_profile_id)
logger.info(f"DEBUG: Retrieved {len(payment_profile_ids)} payment profile IDs: {payment_profile_ids}") print(f"DEBUG: Retrieved {len(payment_profile_ids)} payment profile IDs: {payment_profile_ids}")
except ValueError as e: except ValueError as e:
logger.error(f"Failed to retrieve payment profiles: {str(e)}") print(f"Failed to retrieve payment profiles: {str(e)}")
return { return {
"success": False, "success": False,
"message": f"Failed to retrieve payment profiles: {str(e)}", "message": f"Failed to retrieve payment profiles: {str(e)}",
@@ -211,7 +178,7 @@ def create_user_account(db: Session, customer_id: int) -> dict:
# Assign payment profile IDs to cards # Assign payment profile IDs to cards
num_to_update = min(len(cards), len(payment_profile_ids)) num_to_update = min(len(cards), len(payment_profile_ids))
logger.info(f"Assigning {num_to_update} payment profile IDs to cards") print(f"Assigning {num_to_update} payment profile IDs to cards")
if len(payment_profile_ids) != len(cards): if len(payment_profile_ids) != len(cards):
logger.warning(f"Mismatch between payment profile count ({len(payment_profile_ids)}) and card count ({len(cards)})") logger.warning(f"Mismatch between payment profile count ({len(payment_profile_ids)}) and card count ({len(cards)})")
@@ -222,10 +189,10 @@ def create_user_account(db: Session, customer_id: int) -> dict:
if payment_profile_ids[i] and str(payment_profile_ids[i]).strip(): # Validate the ID exists and isn't empty if payment_profile_ids[i] and str(payment_profile_ids[i]).strip(): # Validate the ID exists and isn't empty
cards[i].auth_net_payment_profile_id = str(payment_profile_ids[i]) # Ensure string cards[i].auth_net_payment_profile_id = str(payment_profile_ids[i]) # Ensure string
db.add(cards[i]) db.add(cards[i])
logger.info(f"Successfully assigned payment profile ID '{payment_profile_ids[i]}' to card {cards[i].id}") print(f"Successfully assigned payment profile ID '{payment_profile_ids[i]}' to card {cards[i].id}")
cards_updated += 1 cards_updated += 1
else: else:
logger.error(f"Missing or invalid payment profile ID '{payment_profile_ids[i]}' for card {cards[i].id}") print(f"Missing or invalid payment profile ID '{payment_profile_ids[i]}' for card {cards[i].id}")
# Save customer profile ID if not set (handle both new and existing case) # Save customer profile ID if not set (handle both new and existing case)
if not customer.auth_net_profile_id: if not customer.auth_net_profile_id:
@@ -234,35 +201,35 @@ def create_user_account(db: Session, customer_id: int) -> dict:
# Commit all changes # Commit all changes
db.commit() db.commit()
logger.info(f"Successfully committed payment profile IDs to database ({cards_updated} cards updated)") print(f"Successfully committed payment profile IDs to database ({cards_updated} cards updated)")
# Enhanced verification - check what was actually saved # Enhanced verification - check what was actually saved
logger.info("Verifying payment profile IDs were saved correctly:") print("Verifying payment profile IDs were saved correctly:")
all_saved_correctly = True all_saved_correctly = True
for i, card in enumerate(cards[:num_to_update]): for i, card in enumerate(cards[:num_to_update]):
committed_card = crud.get_card_by_id(db, card.id) committed_card = crud.get_card_by_id(db, card.id)
if committed_card and committed_card.auth_net_payment_profile_id: if committed_card and committed_card.auth_net_payment_profile_id:
logger.info(f"SUCCESS: Card {card.id} has payment profile ID '{committed_card.auth_net_payment_profile_id}'") print(f"SUCCESS: Card {card.id} has payment profile ID '{committed_card.auth_net_payment_profile_id}'")
else: else:
logger.error(f"ERROR: Card {card.id} is missing payment profile ID") print(f"ERROR: Card {card.id} is missing payment profile ID")
all_saved_correctly = False all_saved_correctly = False
if not all_saved_correctly: if not all_saved_correctly:
logger.error("PAYMENT PROFILE ASSIGNMENT ERRORS DETECTED - This may cause transaction failures!") print("PAYMENT PROFILE ASSIGNMENT ERRORS DETECTED - This may cause transaction failures!")
operation_type = "created" if not original_profile_id else "updated" operation_type = "created" if not original_profile_id else "updated"
logger.info(f"Successfully {operation_type} Authorize.net profile {auth_profile_id} for customer {customer_id} with {len(cards)} cards") print(f"Successfully {operation_type} Authorize.net profile {auth_profile_id} for customer {customer_id} with {len(cards)} cards")
# 🔄 PROACTIVELY REFRESH PAYMENT PROFILES TO ENSURE VALIDITY # 🔄 PROACTIVELY REFRESH PAYMENT PROFILES TO ENSURE VALIDITY
logger.info(f"🔄 Auto-refresh START: customer_id={customer_id}, auth_profile_id={auth_profile_id}") print(f"🔄 Auto-refresh START: customer_id={customer_id}, auth_profile_id={auth_profile_id}")
logger.info(f"🔄 Auto-refresh BEFORE: Cards have these payment profile IDs: {[f'card_{c.id}={c.auth_net_payment_profile_id}' for c in cards]}") print(f"🔄 Auto-refresh BEFORE: Cards have these payment profile IDs: {[f'card_{c.id}={c.auth_net_payment_profile_id}' for c in cards]}")
# Check what changed after refresh # Check what changed after refresh
cards_after = crud.get_customer_cards(db, customer_id) cards_after = crud.get_customer_cards(db, customer_id)
logger.info(f"🔄 Auto-refresh AFTER: Cards now have these payment profile IDs: {[f'card_{c.id}={c.auth_net_payment_profile_id}' for c in cards_after]}") print(f"🔄 Auto-refresh AFTER: Cards now have these payment profile IDs: {[f'card_{c.id}={c.auth_net_payment_profile_id}' for c in cards_after]}")
logger.info(f"🔄 Auto-refresh COMPLETE - IDs changed: {len([c for c in cards if c.auth_net_payment_profile_id != cards_after[cards.index(c)].auth_net_payment_profile_id])} cards updated") print(f"🔄 Auto-refresh COMPLETE - IDs changed: {len([c for c in cards if c.auth_net_payment_profile_id != cards_after[cards.index(c)].auth_net_payment_profile_id])} cards updated")
return { return {
"success": True, "success": True,
@@ -272,7 +239,7 @@ def create_user_account(db: Session, customer_id: int) -> dict:
} }
except Exception as e: except Exception as e:
logger.error(f"Critical exception during user account creation for customer {customer_id}: {traceback.format_exc()}") print(f"Critical exception during user account creation for customer {customer_id}: {traceback.format_exc()}")
db.rollback() db.rollback()
return { return {
"success": False, "success": False,
@@ -303,30 +270,31 @@ def refresh_customer_payment_profiles(db: Session, customer_id: int, auth_profil
# Get customer's current cards from database # Get customer's current cards from database
cards_before = crud.get_customer_cards(db, customer_id) cards_before = crud.get_customer_cards(db, customer_id)
customer = crud.get_customer(db, customer_id) customer = crud.get_customer(db, customer_id)
logger.info(f"🔄 Refresh START: customer_id={customer_id}, profile_id={auth_profile_id}, current cards={len(cards_before)}") print(f"🔄 Refresh START: customer_id={customer_id}, profile_id={auth_profile_id}, current cards={len(cards_before)}")
# STEP 1: Try to get actual payment profiles from Authorize.net # STEP 1: Try to get actual payment profiles from Authorize.net
payment_profile_ids = [] payment_profile_ids = []
try: try:
payment_profile_ids = payment_service.get_customer_payment_profiles(auth_profile_id) payment_profile_ids = payment_service.get_customer_payment_profiles(auth_profile_id)
logger.info(f"🔄 Retrieved {len(payment_profile_ids)} payment profiles from Authorize.net: {payment_profile_ids}") print(f"🔄 Retrieved {len(payment_profile_ids)} payment profiles from Authorize.net: {payment_profile_ids}")
except Exception as e: except Exception as e:
logger.warning(f"🔄 Could not retrieve payment profiles from Authorize.net: {str(e)}") logger.warning(f"🔄 Could not retrieve payment profiles from Authorize.net: {str(e)}")
logger.info("🔄 Will attempt to recreate missing payment profiles") print("🔄 Will attempt to recreate missing payment profiles")
# STEP 2: Check if we have enough payment profiles for our cards # STEP 2: Check if we have enough payment profiles for our cards
cards_need_update = [] cards_need_update = []
for card in cards_before: for card in cards_before:
if not card.auth_net_payment_profile_id or card.auth_net_payment_profile_id.strip() == "": if not card.auth_net_payment_profile_id or card.auth_net_payment_profile_id.strip() == "":
cards_need_update.append(card) cards_need_update.append(card)
logger.info(f"🔄 Card {card.id} needs payment profile ID assignment") print(f"🔄 Card {card.id} needs payment profile ID assignment")
elif str(card.auth_net_payment_profile_id) not in [str(pid) for pid in payment_profile_ids if pid]: elif str(card.auth_net_payment_profile_id) not in [str(pid) for pid in payment_profile_ids if pid]:
# Payment profile ID exists in DB but not found in Authorize.net - likely invalid # Payment profile ID exists in DB but not found in Authorize.net - likely invalid
cards_need_update.append(card) cards_need_update.append(card)
logger.warning(f"🔄 Card {card.id} has payment profile ID {card.auth_net_payment_profile_id} but it's not found in Authorize.net - NEEDS RECREATION") logger.warning(f"🔄 Card {card.id} has payment profile ID {card.auth_net_payment_profile_id} but it's not found in Authorize.net - NEEDS RECREATION")
else: else:
if ApplicationConfig.penny_test_transaction:
# Profile exists in Authorize.net, but let's double-check it's usable by doing a quick test # Profile exists in Authorize.net, but let's double-check it's usable by doing a quick test
logger.info(f"🔄 Card {card.id} has payment profile ID {card.auth_net_payment_profile_id} in Authorize.net - testing usability...") print(f"🔄 Card {card.id} has payment profile ID {card.auth_net_payment_profile_id} in Authorize.net - testing usability...")
try: try:
test_req = schemas.TransactionAuthorizeByCardID( test_req = schemas.TransactionAuthorizeByCardID(
card_id=card.id, card_id=card.id,
@@ -349,7 +317,7 @@ def refresh_customer_payment_profiles(db: Session, customer_id: int, auth_profil
from . import user_delete from . import user_delete
delete_success = user_delete._delete_payment_profile(customer.auth_net_profile_id, card.auth_net_payment_profile_id) delete_success = user_delete._delete_payment_profile(customer.auth_net_profile_id, card.auth_net_payment_profile_id)
if delete_success: if delete_success:
logger.info(f"🔄 Successfully deleted corrupted payment profile {card.auth_net_payment_profile_id}") print(f"🔄 Successfully deleted corrupted payment profile {card.auth_net_payment_profile_id}")
card.auth_net_payment_profile_id = None card.auth_net_payment_profile_id = None
db.add(card) db.add(card)
except Exception as del_e: except Exception as del_e:
@@ -358,15 +326,18 @@ def refresh_customer_payment_profiles(db: Session, customer_id: int, auth_profil
logger.debug(f"🔄 Card {card.id} has valid and usable payment profile ID {card.auth_net_payment_profile_id}") logger.debug(f"🔄 Card {card.id} has valid and usable payment profile ID {card.auth_net_payment_profile_id}")
except Exception as test_e: except Exception as test_e:
logger.warning(f"🔄 Could not test usability of profile {card.auth_net_payment_profile_id} for card {card.id}: {str(test_e)} - assuming it's okay") logger.warning(f"🔄 Could not test usability of profile {card.auth_net_payment_profile_id} for card {card.id}: {str(test_e)} - assuming it's okay")
else:
print(f"🔄 Skipping penny test transaction for card {card.id} (disabled in config)")
logger.debug(f"🔄 Card {card.id} has payment profile ID {card.auth_net_payment_profile_id} in Authorize.net - skipping usability test (config disabled)")
# STEP 3: If we don't have enough valid payment profiles, recreate missing ones # STEP 3: If we don't have enough valid payment profiles, recreate missing ones
if len(cards_need_update) > 0: if len(cards_need_update) > 0:
logger.info(f"🔄 Need to recreate {len(cards_need_update)} payment profiles") print(f"🔄 Need to recreate {len(cards_need_update)} payment profiles")
# Clear payment profile IDs for cards that need recreation (they're invalid anyway) # Clear payment profile IDs for cards that need recreation (they're invalid anyway)
for card in cards_need_update: for card in cards_need_update:
if card.auth_net_payment_profile_id: if card.auth_net_payment_profile_id:
logger.info(f"🔄 Clearing invalid payment profile ID {card.auth_net_payment_profile_id} for card {card.id}") print(f"🔄 Clearing invalid payment profile ID {card.auth_net_payment_profile_id} for card {card.id}")
card.auth_net_payment_profile_id = None card.auth_net_payment_profile_id = None
db.add(card) db.add(card)
@@ -388,7 +359,7 @@ def refresh_customer_payment_profiles(db: Session, customer_id: int, auth_profil
cvv=card.security_number cvv=card.security_number
) )
logger.info(f"🔄 Recreating payment profile for card {card.id} (**** **** **** {card.last_four_digits})") print(f"🔄 Recreating payment profile for card {card.id} (**** **** **** {card.last_four_digits})")
new_payment_profile_id = payment_service.add_payment_profile_to_customer( new_payment_profile_id = payment_service.add_payment_profile_to_customer(
auth_profile_id, customer, card_create_data, is_default=(card.main_card == True) auth_profile_id, customer, card_create_data, is_default=(card.main_card == True)
) )
@@ -397,43 +368,43 @@ def refresh_customer_payment_profiles(db: Session, customer_id: int, auth_profil
card.auth_net_payment_profile_id = str(new_payment_profile_id) card.auth_net_payment_profile_id = str(new_payment_profile_id)
db.add(card) db.add(card)
recreated_cards.append(card) recreated_cards.append(card)
logger.info(f"✅ Successfully recreated payment profile {new_payment_profile_id} for card {card.id}") print(f"✅ Successfully recreated payment profile {new_payment_profile_id} for card {card.id}")
else: else:
logger.error(f"❌ Failed to recreate payment profile for card {card.id} - no ID returned") print(f"❌ Failed to recreate payment profile for card {card.id} - no ID returned")
except Exception as e: except Exception as e:
logger.error(f"❌ Failed to recreate payment profile for card {card.id}: {str(e)}") print(f"❌ Failed to recreate payment profile for card {card.id}: {str(e)}")
continue continue
if recreated_cards: if recreated_cards:
db.commit() db.commit()
logger.info(f"✅ Successfully recreated and saved {len(recreated_cards)} payment profiles") print(f"✅ Successfully recreated and saved {len(recreated_cards)} payment profiles")
else: else:
logger.error("❌ No payment profiles could be recreated - this is a critical failure") print("❌ No payment profiles could be recreated - this is a critical failure")
return False return False
else: else:
logger.info(f"🔄 All {len(cards_before)} cards have valid payment profile IDs") print(f"🔄 All {len(cards_before)} cards have valid payment profile IDs")
# STEP 4: Final verification that everything looks good # STEP 4: Final verification that everything looks good
cards_final = crud.get_customer_cards(db, customer_id) cards_final = crud.get_customer_cards(db, customer_id)
logger.info("🔄 FINAL VERIFICATION:") print("🔄 FINAL VERIFICATION:")
all_valid = True all_valid = True
for card in cards_final: for card in cards_final:
status = "✅ VALID" if card.auth_net_payment_profile_id and card.auth_net_payment_profile_id.strip() else "❌ INVALID" status = "✅ VALID" if card.auth_net_payment_profile_id and card.auth_net_payment_profile_id.strip() else "❌ INVALID"
logger.info(f"🔄 {status} Card {card.id}: auth_net_payment_profile_id='{card.auth_net_payment_profile_id}'") print(f"🔄 {status} Card {card.id}: auth_net_payment_profile_id='{card.auth_net_payment_profile_id}'")
if not card.auth_net_payment_profile_id or card.auth_net_payment_profile_id.strip() == "": if not card.auth_net_payment_profile_id or card.auth_net_payment_profile_id.strip() == "":
all_valid = False all_valid = False
if all_valid: if all_valid:
logger.info(f"🔄 Refresh COMPLETE: All {len(cards_final)} cards have valid payment profile IDs") print(f"🔄 Refresh COMPLETE: All {len(cards_final)} cards have valid payment profile IDs")
return True return True
else: else:
logger.warning("🔄 Refresh PARTIAL: Some cards may still have invalid payment profile IDs") logger.warning("🔄 Refresh PARTIAL: Some cards may still have invalid payment profile IDs")
return False return False
except Exception as e: except Exception as e:
logger.error(f"🔄 Refresh FAILED for customer {customer_id}: {str(e)}") print(f"🔄 Refresh FAILED for customer {customer_id}: {str(e)}")
logger.error(f"🔄 Refresh traceback: {traceback.format_exc()}") print(f"🔄 Refresh traceback: {traceback.format_exc()}")
db.rollback() db.rollback()
return False return False

View File

@@ -31,3 +31,6 @@ class ApplicationConfig:
# Authorize.net credentials # Authorize.net credentials
API_LOGIN_ID = 'bizdev05' API_LOGIN_ID = 'bizdev05'
TRANSACTION_KEY = '4kJd237rZu59qAZd' TRANSACTION_KEY = '4kJd237rZu59qAZd'
# Payment processing flags
penny_test_transaction = False # Run penny test in development

View File

@@ -25,3 +25,6 @@ class ApplicationConfig:
# Authorize.net credentials # Authorize.net credentials
API_LOGIN_ID = '4d2Mn6H23R' API_LOGIN_ID = '4d2Mn6H23R'
TRANSACTION_KEY = '7B94d8xfTQXv37WS' TRANSACTION_KEY = '7B94d8xfTQXv37WS'
# Payment processing flags
penny_test_transaction = False # Skip penny test in production