From 5b4a05566ae681e0edd7da2010d1f69dd429a065 Mon Sep 17 00:00:00 2001 From: Edwin Eames Date: Sat, 27 Sep 2025 14:25:28 -0400 Subject: [PATCH] config issues prod --- app/routers/payment.py | 317 +++++++++++++++++++++++++++++--- app/services/payment_service.py | 5 +- app/services/user_create.py | 155 ++++++++++++++++ 3 files changed, 451 insertions(+), 26 deletions(-) diff --git a/app/routers/payment.py b/app/routers/payment.py index 57c1899..dfece44 100644 --- a/app/routers/payment.py +++ b/app/routers/payment.py @@ -4,10 +4,13 @@ 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 -import logging + +logger = logging.getLogger(__name__) + 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 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) @@ -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. 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") - # Add CRITICAL DEBUGGING to see exactly what's in the database - 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)})") + print("🐛 DEBUG: CUSTOMER AND CARD FOUND, STARTING PRE-VALIDATION") - # Check for specific problem values - if db_card.auth_net_payment_profile_id is None: - print("ROUTER DEBUG: CRITICAL - payment_profile_id is None - this will cause E00121!") - elif db_card.auth_net_payment_profile_id == "": - print("ROUTER DEBUG: CRITICAL - payment_profile_id is empty string - this will cause E00121!") - elif str(db_card.auth_net_payment_profile_id).lower() == "none": - print("ROUTER DEBUG: CRITICAL - payment_profile_id is string 'None' - this will cause E00121!") + # 🚨 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: - 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: - 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}'") - raise HTTPException(status_code=400, detail="Payment profile is not set up correctly for this customer/card") + 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" + ) - # 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( 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, # For auto-recovery - customer_id=customer_id, # For auto-recovery - card_id=db_card.id # For auto-recovery + db_session=db, + customer_id=customer_id, + 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) - # Create the transaction record in your database with the correct type transaction_data = schemas.TransactionBase( preauthorize_amount=transaction_req.preauthorize_amount, transaction_type=TransactionType.AUTHORIZE, # This is key @@ -254,10 +432,99 @@ def authorize_saved_card(customer_id: int, transaction_req: schemas.TransactionA db=db, transaction=transaction_data, customer_id=customer_id, - status=status, + 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 + 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 --- diff --git a/app/services/payment_service.py b/app/services/payment_service.py index 6ad3ce3..387a7e3 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -25,8 +25,10 @@ TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY # Set Authorize.net environment based on configuration if ApplicationConfig.CURRENT_SETTINGS == 'PRODUCTION': constants.environment = constants.PRODUCTION + VALIDATION_MODE = "liveMode" else: constants.environment = constants.SANDBOX + VALIDATION_MODE = "testMode" 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, paymentProfile=paymentProfile, # ========= CHANGE 2.B: USE liveMode ========= - validationMode="testMode" + validationMode=VALIDATION_MODE ) 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" 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("POOOP") logger.info(f"🔄 AUTO-RECOVERING: Starting payment profile refresh for customer {customer_id}") try: diff --git a/app/services/user_create.py b/app/services/user_create.py index 5c15614..408f5ad 100644 --- a/app/services/user_create.py +++ b/app/services/user_create.py @@ -263,3 +263,158 @@ def create_user_account(db: Session, customer_id: int) -> dict: "message": f"An unexpected error occurred: {str(e)}", "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