import logging from authorizenet import apicontractsv1 from authorizenet.apicontrollers import createTransactionController from .. import schemas from config import load_config # Set up logging logger = logging.getLogger(__name__) # Load Authorize.net credentials from config ApplicationConfig = load_config() API_LOGIN_ID = ApplicationConfig.API_LOGIN_ID TRANSACTION_KEY = ApplicationConfig.TRANSACTION_KEY # For sandbox, endpoint is https://apitest.authorize.net/xml/v1/request.api def safe_string_convert(value): """ Safely convert any value to string, handling lxml objects properly. """ if value is None: return None # Check if it's an lxml object with text attribute if hasattr(value, 'text'): return value.text # Otherwise use standard string conversion return str(value) def charge_credit_card(transaction: schemas.TransactionCreate): logger.info(f"Processing charge for amount: {transaction.charge_amount}") merchantAuth = apicontractsv1.merchantAuthenticationType() merchantAuth.name = API_LOGIN_ID merchantAuth.transactionKey = TRANSACTION_KEY creditCard = apicontractsv1.creditCardType() creditCard.cardNumber = transaction.card_number creditCard.expirationDate = transaction.expiration_date creditCard.cardCode = transaction.cvv payment = apicontractsv1.paymentType() payment.creditCard = creditCard transactionRequest = apicontractsv1.transactionRequestType() transactionRequest.transactionType = "authCaptureTransaction" transactionRequest.amount = transaction.charge_amount # ✅ Fixed: Use charge_amount transactionRequest.payment = payment createtransactionrequest = apicontractsv1.createTransactionRequest() createtransactionrequest.merchantAuthentication = merchantAuth createtransactionrequest.refId = "ref_id" # Optional reference ID createtransactionrequest.transactionRequest = transactionRequest controller = createTransactionController(createtransactionrequest) controller.execute() response = controller.getresponse() # Extract rejection reason if payment failed rejection_reason = None if response is not None and response.messages is not None: logger.info(f"Charge response: {response.messages.resultCode}") # If payment was declined (resultCode is "Error"), extract error details if response.messages.resultCode == "Error": rejection_reason = "Authorize.Net Charge Error" if hasattr(response.messages, 'message'): try: if len(response.messages.message) > 0: for msg in response.messages.message: if hasattr(msg, 'code') and hasattr(msg, 'text'): # Convert lxml StringElement objects to Python strings code_str = msg.code.text if msg.code else "Unknown" text_str = msg.text.text if msg.text else "No details available" rejection_reason = f"{code_str}: {text_str}" break # Use the first error message elif hasattr(msg, 'text'): # Convert lxml StringElement to Python string text_str = msg.text.text if msg.text else "No details available" rejection_reason = f"Error: {text_str}" break else: rejection_reason = "Charge declined - no specific error details available" except Exception as e: rejection_reason = f"Charge declined - error details could not be parsed: {str(e)}" else: rejection_reason = "Charge declined - no error message available" if hasattr(response.messages, 'message') and len(response.messages.message) > 0: for msg in response.messages.message: logger.info(f"Message: {msg.text.text if msg.text else 'No message text'}") else: logger.error("No response from Authorize.net") rejection_reason = "No response received from Authorize.Net" # Attach rejection reason to response for the router to use if response is not None: response._rejection_reason = rejection_reason return response def authorize_credit_card(transaction: schemas.TransactionAuthorize): logger.info(f"Processing preauthorization for amount: {transaction.preauthorize_amount}") merchantAuth = apicontractsv1.merchantAuthenticationType() merchantAuth.name = API_LOGIN_ID merchantAuth.transactionKey = TRANSACTION_KEY creditCard = apicontractsv1.creditCardType() creditCard.cardNumber = transaction.card_number creditCard.expirationDate = transaction.expiration_date creditCard.cardCode = transaction.cvv payment = apicontractsv1.paymentType() payment.creditCard = creditCard transactionRequest = apicontractsv1.transactionRequestType() transactionRequest.transactionType = "authOnlyTransaction" transactionRequest.amount = transaction.preauthorize_amount # ✅ Fixed: Use preauthorize_amount transactionRequest.payment = payment createtransactionrequest = apicontractsv1.createTransactionRequest() createtransactionrequest.merchantAuthentication = merchantAuth createtransactionrequest.refId = "ref_id" createtransactionrequest.transactionRequest = transactionRequest controller = createTransactionController(createtransactionrequest) controller.execute() response = controller.getresponse() # Extract rejection reason if payment failed rejection_reason = None if response is not None and response.messages is not None: logger.info(f"Preauthorization response: {response.messages.resultCode}") # If payment was declined (resultCode is "Error"), extract error details if response.messages.resultCode == "Error": rejection_reason = "Authorize.Net Error" if hasattr(response.messages, 'message'): try: if len(response.messages.message) > 0: for msg in response.messages.message: if hasattr(msg, 'code') and hasattr(msg, 'text'): # Convert lxml StringElement objects to Python strings code_str = msg.code.text if msg.code else "Unknown" text_str = msg.text.text if msg.text else "No details available" rejection_reason = f"{code_str}: {text_str}" break # Use the first error message elif hasattr(msg, 'text'): # Convert lxml StringElement to Python string text_str = msg.text.text if msg.text else "No details available" rejection_reason = f"Error: {text_str}" break else: rejection_reason = "Payment declined - no specific error details available" except Exception as e: rejection_reason = f"Payment declined - error details could not be parsed: {str(e)}" else: rejection_reason = "Payment declined - no error message available" if hasattr(response.messages, 'message') and len(response.messages.message) > 0: for msg in response.messages.message: logger.info(f"Message: {msg.text.text if msg.text else 'No message text'}") else: logger.error("No response from Authorize.net for preauthorization") rejection_reason = "No response received from Authorize.Net" # Attach rejection reason to response for the router to use if response is not None: response._rejection_reason = rejection_reason return response def capture_authorized_transaction(transaction: schemas.TransactionCapture): merchantAuth = apicontractsv1.merchantAuthenticationType() merchantAuth.name = API_LOGIN_ID merchantAuth.transactionKey = TRANSACTION_KEY transactionRequest = apicontractsv1.transactionRequestType() transactionRequest.transactionType = "priorAuthCaptureTransaction" transactionRequest.amount = transaction.charge_amount # ✅ Fixed: Use charge_amount transactionRequest.refTransId = transaction.auth_net_transaction_id createtransactionrequest = apicontractsv1.createTransactionRequest() createtransactionrequest.merchantAuthentication = merchantAuth createtransactionrequest.refId = "ref_id" createtransactionrequest.transactionRequest = transactionRequest controller = createTransactionController(createtransactionrequest) controller.execute() response = controller.getresponse() # Extract rejection reason if capture failed rejection_reason = None if response is not None and response.messages is not None: logger.info(f"Capture response: {response.messages.resultCode}") # If capture was declined (resultCode is "Error"), extract error details if response.messages.resultCode == "Error": rejection_reason = "Authorize.Net Capture Error" if hasattr(response.messages, 'message'): try: if len(response.messages.message) > 0: for msg in response.messages.message: if hasattr(msg, 'code') and hasattr(msg, 'text'): # Convert lxml StringElement objects to Python strings code_str = msg.code.text if msg.code else "Unknown" text_str = msg.text.text if msg.text else "No details available" rejection_reason = f"{code_str}: {text_str}" break # Use the first error message elif hasattr(msg, 'text'): # Convert lxml StringElement to Python string text_str = msg.text.text if msg.text else "No details available" rejection_reason = f"Error: {text_str}" break else: rejection_reason = "Capture declined - no specific error details available" except Exception as e: rejection_reason = f"Capture declined - error details could not be parsed: {str(e)}" else: rejection_reason = "Capture declined - no error message available" if hasattr(response.messages, 'message') and len(response.messages.message) > 0: for msg in response.messages.message: logger.info(f"Message: {msg.text.text if msg.text else 'No message text'}") else: logger.error("No response from Authorize.net for capture") rejection_reason = "No response received from Authorize.Net for capture" # Attach rejection reason to response for the router to use if response is not None: response._rejection_reason = rejection_reason return response