247 lines
9.9 KiB
Python
247 lines
9.9 KiB
Python
import logging
|
|
import traceback
|
|
from authorizenet import apicontractsv1
|
|
from authorizenet.apicontrollers import (
|
|
deleteCustomerProfileController,
|
|
deleteCustomerPaymentProfileController
|
|
)
|
|
from authorizenet.constants import constants
|
|
from config import load_config
|
|
from sqlalchemy.orm import Session
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Load Authorize.net credentials
|
|
ApplicationConfig = load_config()
|
|
API_LOGIN_ID = '9U6w96gZmX'
|
|
TRANSACTION_KEY = '94s6Qy458mMNJr7G'
|
|
constants.show_url_on_request = True
|
|
constants.environment = constants.SANDBOX
|
|
|
|
|
|
def _get_authnet_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.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 delete_user_account(db: Session, customer_id: int) -> dict:
|
|
"""
|
|
Delete the complete Authorize.net account for a user.
|
|
This removes the customer profile and all associated payment profiles from Authorize.net,
|
|
then updates the database to null out the ID fields.
|
|
|
|
Args:
|
|
db: Database session
|
|
customer_id: ID of the customer to delete account for
|
|
|
|
Returns:
|
|
Dict with success status, message, and details
|
|
"""
|
|
try:
|
|
# Import crud functions here to avoid circular imports
|
|
from .. import crud
|
|
|
|
# Get customer from database
|
|
customer = crud.get_customer(db, customer_id)
|
|
if not customer:
|
|
return {
|
|
"success": False,
|
|
"message": f"Customer {customer_id} not found",
|
|
"deleted_profile_id": None
|
|
}
|
|
|
|
# Check if customer has an Authorize.net profile
|
|
if not customer.auth_net_profile_id:
|
|
return {
|
|
"success": False,
|
|
"message": "Customer does not have an Authorize.net profile",
|
|
"deleted_profile_id": None
|
|
}
|
|
|
|
profile_id_to_delete = customer.auth_net_profile_id
|
|
|
|
# Get customer's payment profiles/cards from database
|
|
cards = crud.get_customer_cards(db, customer_id)
|
|
|
|
logger.info(f"Starting deletion of Authorize.net account for customer {customer_id} (Profile ID: {profile_id_to_delete})")
|
|
|
|
# Step 1: Delete payment profiles first (must delete these before customer profile)
|
|
deleted_payment_profiles = []
|
|
if cards:
|
|
logger.info(f"Found {len(cards)} cards to delete from Authorize.net")
|
|
|
|
for card_index, card in enumerate(cards):
|
|
if card.auth_net_payment_profile_id:
|
|
try:
|
|
logger.info(f"Deleting payment profile {card.auth_net_payment_profile_id} for card {card.id}")
|
|
|
|
# Delete payment profile from Authorize.net
|
|
success = _delete_payment_profile(profile_id_to_delete, card.auth_net_payment_profile_id)
|
|
|
|
if success:
|
|
deleted_payment_profiles.append(card.auth_net_payment_profile_id)
|
|
logger.info(f"Successfully deleted payment profile {card.auth_net_payment_profile_id}")
|
|
else:
|
|
logger.warning(f"Failed to delete payment profile {card.auth_net_payment_profile_id} - it may not exist or already deleted")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error deleting payment profile {card.auth_net_payment_profile_id}: {str(e)}")
|
|
# Continue with other payment profiles - we want to delete as much as possible
|
|
|
|
# Always null out the payment profile ID in database (even if API delete failed)
|
|
card.auth_net_payment_profile_id = None
|
|
db.add(card)
|
|
|
|
# Step 2: Delete customer profile
|
|
logger.info(f"Deleting customer profile {profile_id_to_delete}")
|
|
profile_deleted_success = _delete_customer_profile(profile_id_to_delete)
|
|
|
|
# Step 3: Update database regardless of API results
|
|
customer.auth_net_profile_id = None
|
|
db.add(customer)
|
|
|
|
# Commit all database changes
|
|
db.commit()
|
|
|
|
if profile_deleted_success:
|
|
logger.info(f"Successfully deleted Authorize.net account for customer {customer_id}")
|
|
return {
|
|
"success": True,
|
|
"message": f"Successfully deleted Authorize.net account with profile ID {profile_id_to_delete}",
|
|
"deleted_profile_id": profile_id_to_delete,
|
|
"deleted_payment_profiles_count": len(deleted_payment_profiles),
|
|
"deleted_payment_profiles": deleted_payment_profiles
|
|
}
|
|
else:
|
|
logger.warning(f"Customer profile {profile_id_to_delete} may not have been completely removed from Authorize.net, but database has been updated")
|
|
return {
|
|
"success": False,
|
|
"message": f"Profile {profile_id_to_delete} may not have been completely removed from Authorize.net, but database has been cleaned up",
|
|
"deleted_profile_id": profile_id_to_delete,
|
|
"deleted_payment_profiles_count": len(deleted_payment_profiles),
|
|
"deleted_payment_profiles": deleted_payment_profiles
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"Critical exception during account deletion for customer {customer_id}: {traceback.format_exc()}")
|
|
db.rollback()
|
|
return {
|
|
"success": False,
|
|
"message": f"An unexpected error occurred: {str(e)}",
|
|
"deleted_profile_id": None
|
|
}
|
|
|
|
|
|
def _delete_customer_profile(profile_id: str) -> bool:
|
|
"""
|
|
Delete customer profile from Authorize.net.
|
|
|
|
Args:
|
|
profile_id: Authorize.net customer profile ID
|
|
|
|
Returns:
|
|
bool: True if successfully deleted or profile doesn't exist, False on error
|
|
"""
|
|
try:
|
|
merchant_auth = apicontractsv1.merchantAuthenticationType(
|
|
name=API_LOGIN_ID,
|
|
transactionKey=TRANSACTION_KEY
|
|
)
|
|
|
|
request = apicontractsv1.deleteCustomerProfileRequest(
|
|
merchantAuthentication=merchant_auth,
|
|
customerProfileId=profile_id
|
|
)
|
|
|
|
controller = deleteCustomerProfileController(request)
|
|
controller.execute()
|
|
response = controller.getresponse()
|
|
|
|
if response is None:
|
|
logger.warning(f"No response received when trying to delete profile {profile_id}")
|
|
return False
|
|
|
|
if hasattr(response, 'messages') and response.messages.resultCode == "Ok":
|
|
logger.info(f"Successfully deleted customer profile {profile_id}")
|
|
return True
|
|
else:
|
|
error_msg = _get_authnet_error_message(response)
|
|
logger.warning(f"Failed to delete customer profile {profile_id}: {error_msg}")
|
|
# Still count as success if the profile was already deleted/not found
|
|
if "not found" in error_msg.lower() or "E00040" in error_msg or "E00035" in error_msg:
|
|
logger.info(f"Profile {profile_id} was already deleted or doesn't exist")
|
|
return True
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Exception during delete customer profile {profile_id}: {str(e)}")
|
|
return False
|
|
|
|
|
|
def _delete_payment_profile(customer_profile_id: str, payment_profile_id: str) -> bool:
|
|
"""
|
|
Delete payment profile from Authorize.net.
|
|
|
|
Args:
|
|
customer_profile_id: Authorize.net customer profile ID
|
|
payment_profile_id: Authorize.net payment profile ID to delete
|
|
|
|
Returns:
|
|
bool: True if successfully deleted or profile doesn't exist, False on error
|
|
"""
|
|
try:
|
|
merchant_auth = apicontractsv1.merchantAuthenticationType(
|
|
name=API_LOGIN_ID,
|
|
transactionKey=TRANSACTION_KEY
|
|
)
|
|
|
|
request = apicontractsv1.deleteCustomerPaymentProfileRequest(
|
|
merchantAuthentication=merchant_auth,
|
|
customerProfileId=customer_profile_id,
|
|
customerPaymentProfileId=payment_profile_id
|
|
)
|
|
|
|
controller = deleteCustomerPaymentProfileController(request)
|
|
controller.execute()
|
|
response = controller.getresponse()
|
|
|
|
if response is None:
|
|
logger.warning(f"No response received when trying to delete payment profile {payment_profile_id}")
|
|
return False
|
|
|
|
if hasattr(response, 'messages') and response.messages.resultCode == "Ok":
|
|
logger.info(f"Successfully deleted payment profile {payment_profile_id}")
|
|
return True
|
|
else:
|
|
error_msg = _get_authnet_error_message(response)
|
|
logger.warning(f"Failed to delete payment profile {payment_profile_id}: {error_msg}")
|
|
# Still count as success if the payment profile was already deleted/not found
|
|
if "not found" in error_msg.lower() or "E00040" in error_msg or "E00035" in error_msg:
|
|
logger.info(f"Payment profile {payment_profile_id} was already deleted or doesn't exist")
|
|
return True
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"Exception during delete payment profile {payment_profile_id}: {str(e)}")
|
|
return False
|