config issues prod

This commit is contained in:
2025-09-27 14:25:28 -04:00
parent a629168299
commit 5b4a05566a
3 changed files with 451 additions and 26 deletions

View File

@@ -4,10 +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
import logging
logger = logging.getLogger(__name__)
AuthNetResponse = object AuthNetResponse = object
@@ -161,6 +164,80 @@ def add_card_to_customer(customer_id: int, card_info: schemas.CardCreate, db: Se
# This will catch any other unexpected errors, like from the database # This will catch any other unexpected errors, like from the database
raise HTTPException(status_code=500, detail="An internal server error occurred.") 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") @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)): 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_customer = crud.get_customer(db, customer_id=customer_id)
@@ -200,46 +277,147 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
Creates a pre-authorization on a customer's saved card. Creates a pre-authorization on a customer's saved card.
This validates the card and reserves the funds. 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_customer = crud.get_customer(db, customer_id=customer_id)
db_card = crud.get_card_by_id(db, card_id=transaction_req.card_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: 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") raise HTTPException(status_code=404, detail="Customer or card not found for this account")
# Add CRITICAL DEBUGGING to see exactly what's in the database print("🐛 DEBUG: CUSTOMER AND CARD FOUND, STARTING PRE-VALIDATION")
print(f"ROUTER DEBUG: Customer ID: {customer_id}")
print(f"ROUTER DEBUG: db_customer: {db_customer}")
print(f"ROUTER DEBUG: db_customer.auth_net_profile_id: '{db_customer.auth_net_profile_id}' (type: {type(db_customer.auth_net_profile_id)})")
print(f"ROUTER DEBUG: db_card: {db_card}")
print(f"ROUTER DEBUG: db_card.auth_net_payment_profile_id: '{db_card.auth_net_payment_profile_id}' (type: {type(db_card.auth_net_payment_profile_id)})")
# Check for specific problem values # 🚨 ENHANCED PRE-TRANSACTION VALIDATION 🚨
if db_card.auth_net_payment_profile_id is None: # Proactively check and fix payment profile issues before attempting transaction
print("ROUTER DEBUG: CRITICAL - payment_profile_id is None - this will cause E00121!") print(f"🐛 DEBUG: Starting enhanced pre-validation for customer {customer_id}, card {db_card.id}")
elif db_card.auth_net_payment_profile_id == "": logger.info(f"🔍 PRE-TRANSACTION CHECK: Customer {customer_id}, Card {db_card.id}")
print("ROUTER DEBUG: CRITICAL - payment_profile_id is empty string - this will cause E00121!") logger.info(f"🔍 Current auth_net_profile_id: '{db_customer.auth_net_profile_id}'")
elif str(db_card.auth_net_payment_profile_id).lower() == "none": logger.info(f"🔍 Current payment_profile_id: '{db_card.auth_net_payment_profile_id}'")
print("ROUTER DEBUG: CRITICAL - payment_profile_id is string 'None' - this will cause E00121!")
# 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: else:
print(f"ROUTER DEBUG: payment_profile_id appears valid: '{db_card.auth_net_payment_profile_id}'") # 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: if not db_customer.auth_net_profile_id or not db_card.auth_net_payment_profile_id:
print(f"ROUTER DEBUG: WILL THROW HTTP 400 ERROR: auth_net_profile_id='{db_customer.auth_net_profile_id}', payment_profile_id='{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") raise HTTPException(
status_code=400,
detail="Payment profile is not set up correctly for this customer/card"
)
# Call the service function for authorization with auto-recovery enabled 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( auth_net_response = payment_service.authorize_customer_profile(
customer_profile_id=db_customer.auth_net_profile_id, customer_profile_id=db_customer.auth_net_profile_id,
payment_profile_id=db_card.auth_net_payment_profile_id, payment_profile_id=db_card.auth_net_payment_profile_id,
transaction_req=transaction_req, transaction_req=transaction_req,
db_session=db, # For auto-recovery db_session=db,
customer_id=customer_id, # For auto-recovery customer_id=customer_id,
card_id=db_card.id # For auto-recovery card_id=db_card.id
) )
status, auth_net_transaction_id, rejection_reason = _parse_authnet_response(auth_net_response) # 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) print(transaction_req)
# Create the transaction record in your database with the correct type
transaction_data = schemas.TransactionBase( transaction_data = schemas.TransactionBase(
preauthorize_amount=transaction_req.preauthorize_amount, preauthorize_amount=transaction_req.preauthorize_amount,
transaction_type=TransactionType.AUTHORIZE, # This is key transaction_type=TransactionType.AUTHORIZE, # This is key
@@ -254,10 +432,99 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA
db=db, db=db,
transaction=transaction_data, transaction=transaction_data,
customer_id=customer_id, customer_id=customer_id,
status=status, status=transaction_status,
auth_net_transaction_id=auth_net_transaction_id auth_net_transaction_id=auth_net_transaction_id
) )
return db_transaction 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
exp_year = card.expiration_year.zfill(4) if len(card.expiration_year) < 4 else card.expiration_year
exp_month = card.expiration_month.zfill(2) if len(card.expiration_month) < 2 else card.expiration_month
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 --- # --- CAPTURE ENDPOINT MOVED TO TRANSACTION ROUTER ---

View File

@@ -25,8 +25,10 @@ TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY
# 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
VALIDATION_MODE = "liveMode"
else: else:
constants.environment = constants.SANDBOX constants.environment = constants.SANDBOX
VALIDATION_MODE = "testMode"
constants.show_url_on_request = True # Very useful for debugging constants.show_url_on_request = True # Very useful for debugging
@@ -191,7 +193,7 @@ def add_payment_profile_to_customer(customer_profile_id: str, customer: schemas.
customerProfileId=customer_profile_id, customerProfileId=customer_profile_id,
paymentProfile=paymentProfile, paymentProfile=paymentProfile,
# ========= CHANGE 2.B: USE liveMode ========= # ========= CHANGE 2.B: USE liveMode =========
validationMode="testMode" validationMode=VALIDATION_MODE
) )
controller = createCustomerPaymentProfileController(request) controller = createCustomerPaymentProfileController(request)
@@ -243,6 +245,7 @@ def authorize_customer_profile(customer_profile_id: str, payment_profile_id: str
# CHECK FOR E00121 ERROR - "invalid payment profile ID" # CHECK FOR E00121 ERROR - "invalid payment profile ID"
if db_session and customer_id and card_id and _is_e00121_response(response): if db_session and customer_id and card_id and _is_e00121_response(response):
logger.warning(f"🚨 E00121 DETECTED! Invalid payment profile {payment_profile_id}") logger.warning(f"🚨 E00121 DETECTED! Invalid payment profile {payment_profile_id}")
logger.warning("POOOP")
logger.info(f"🔄 AUTO-RECOVERING: Starting payment profile refresh for customer {customer_id}") logger.info(f"🔄 AUTO-RECOVERING: Starting payment profile refresh for customer {customer_id}")
try: try:

View File

@@ -263,3 +263,158 @@ def create_user_account(db: Session, customer_id: int) -> dict:
"message": f"An unexpected error occurred: {str(e)}", "message": f"An unexpected error occurred: {str(e)}",
"profile_id": None "profile_id": None
} }
def refresh_customer_payment_profiles(db: Session, customer_id: int, auth_profile_id: str) -> bool:
"""
Refresh payment profiles for a customer by syncing from Authorize.net to database.
This ensures database payment_profile_ids stay in sync with Authorize.net.
NOW WITH RECREATION FALLBACK: If no payment profiles exist in Authorize.net,
recreates them from stored card data instead of failing.
Args:
db: Database session
customer_id: ID of the customer
auth_profile_id: Authorize.net customer profile ID
Returns:
bool: True if refresh successful, False otherwise
"""
try:
# Import here to avoid circular imports
from .. import crud
# Get customer's current cards from database
cards_before = crud.get_customer_cards(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)}")
# STEP 1: Try to get actual payment profiles from Authorize.net
payment_profile_ids = []
try:
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}")
except Exception as e:
logger.warning(f"🔄 Could not retrieve payment profiles from Authorize.net: {str(e)}")
logger.info("🔄 Will attempt to recreate missing payment profiles")
# STEP 2: Check if we have enough payment profiles for our cards
cards_need_update = []
for card in cards_before:
if not card.auth_net_payment_profile_id or card.auth_net_payment_profile_id.strip() == "":
cards_need_update.append(card)
logger.info(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]:
# Payment profile ID exists in DB but not found in Authorize.net - likely invalid
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")
else:
# 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...")
try:
test_req = schemas.TransactionAuthorizeByCardID(
card_id=card.id,
preauthorize_amount="0.01"
)
test_response = payment_service.authorize_customer_profile(
customer_profile_id=customer.auth_net_profile_id,
payment_profile_id=card.auth_net_payment_profile_id,
transaction_req=test_req,
db_session=None, customer_id=None, card_id=None
)
# Check test result
_, _, test_reason = payment_service._parse_authnet_response(test_response)
if "E00121" in str(test_reason):
cards_need_update.append(card)
logger.warning(f"🔄 Card {card.id} has profile {card.auth_net_payment_profile_id} that EXISTS but is CORRUPTED - NEEDS RECREATION")
# Explicitly delete the corrupted profile first
try:
from . import user_delete
delete_success = user_delete._delete_payment_profile(customer.auth_net_profile_id, card.auth_net_payment_profile_id)
if delete_success:
logger.info(f"🔄 Successfully deleted corrupted payment profile {card.auth_net_payment_profile_id}")
card.auth_net_payment_profile_id = None
db.add(card)
except Exception as del_e:
logger.warning(f"🔄 Failed to delete corrupted profile {card.auth_net_payment_profile_id}: {str(del_e)}")
else:
logger.debug(f"🔄 Card {card.id} has valid and usable payment profile ID {card.auth_net_payment_profile_id}")
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")
# STEP 3: If we don't have enough valid payment profiles, recreate missing ones
if len(cards_need_update) > 0:
logger.info(f"🔄 Need to recreate {len(cards_need_update)} payment profiles")
# Clear payment profile IDs for cards that need recreation (they're invalid anyway)
for card in cards_need_update:
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}")
card.auth_net_payment_profile_id = None
db.add(card)
# Recreate payment profiles for cards that need them
recreated_cards = []
for card in cards_need_update:
try:
# Format expiration date for recreation
exp_year = card.expiration_year.zfill(4) if len(card.expiration_year) < 4 else card.expiration_year
exp_month = card.expiration_month.zfill(2) if len(card.expiration_month) < 2 else card.expiration_month
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
)
logger.info(f"🔄 Recreating payment profile for card {card.id} (**** **** **** {card.last_four_digits})")
new_payment_profile_id = payment_service.add_payment_profile_to_customer(
auth_profile_id, customer, card_create_data, is_default=(card.main_card == True)
)
if new_payment_profile_id:
card.auth_net_payment_profile_id = str(new_payment_profile_id)
db.add(card)
recreated_cards.append(card)
logger.info(f"✅ Successfully recreated payment profile {new_payment_profile_id} for card {card.id}")
else:
logger.error(f"❌ Failed to recreate payment profile for card {card.id} - no ID returned")
except Exception as e:
logger.error(f"❌ Failed to recreate payment profile for card {card.id}: {str(e)}")
continue
if recreated_cards:
db.commit()
logger.info(f"✅ Successfully recreated and saved {len(recreated_cards)} payment profiles")
else:
logger.error("❌ No payment profiles could be recreated - this is a critical failure")
return False
else:
logger.info(f"🔄 All {len(cards_before)} cards have valid payment profile IDs")
# STEP 4: Final verification that everything looks good
cards_final = crud.get_customer_cards(db, customer_id)
logger.info("🔄 FINAL VERIFICATION:")
all_valid = True
for card in cards_final:
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}'")
if not card.auth_net_payment_profile_id or card.auth_net_payment_profile_id.strip() == "":
all_valid = False
if all_valid:
logger.info(f"🔄 Refresh COMPLETE: All {len(cards_final)} cards have valid payment profile IDs")
return True
else:
logger.warning("🔄 Refresh PARTIAL: Some cards may still have invalid payment profile IDs")
return False
except Exception as e:
logger.error(f"🔄 Refresh FAILED for customer {customer_id}: {str(e)}")
logger.error(f"🔄 Refresh traceback: {traceback.format_exc()}")
db.rollback()
return False