diff --git a/app/crud.py b/app/crud.py index 17fb441..26877ec 100644 --- a/app/crud.py +++ b/app/crud.py @@ -90,7 +90,7 @@ def update_transaction_for_capture(db: Session, auth_net_transaction_id: str, ch return None transaction.charge_amount = charge_amount if status == 0 else Decimal("0.0") - transaction.transaction_type = 3 + transaction.transaction_type = 2 transaction.status = status if rejection_reason: transaction.rejection_reason = rejection_reason diff --git a/app/models.py b/app/models.py index 6d1a301..7221982 100644 --- a/app/models.py +++ b/app/models.py @@ -56,7 +56,7 @@ class Transaction(Base): preauthorize_amount = Column(Numeric(10, 2), nullable=True) charge_amount = Column(Numeric(10, 2), nullable=True) customer_id = Column(Integer) - transaction_type = Column(Integer) + transaction_type = Column(Integer)# 0 = charge, 1 = auth, 2 = capture status = Column(Integer) auth_net_transaction_id = Column(String, unique=True, index=True, nullable=True) service_id = Column(Integer, nullable=True) diff --git a/app/routers/payment.py b/app/routers/payment.py index ea16c99..eece78d 100644 --- a/app/routers/payment.py +++ b/app/routers/payment.py @@ -36,55 +36,87 @@ STATE_ID_TO_ABBREVIATION = { 6: "NY" } -# This helper function is perfect, keep it. def _parse_authnet_response(response: Optional[AuthNetResponse]) -> Tuple[TransactionStatus, Optional[str], Optional[str]]: - # ... (Your existing _parse_authnet_response function code) ... + """ + 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 is not None and hasattr(response, 'messages') and response.messages.resultCode == "Ok": + print("DEBUG: Taking APPROVED path") status = TransactionStatus.APPROVED - auth_net_transaction_id = str(response.transactionResponse.transId) if hasattr(response, 'transactionResponse') and response.transactionResponse.transId else None + 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." - print("DEBUG: Full response object") - print(response) - print("DEBUG: response.messages") - print(response.messages) - print("DEBUG: response.messages.resultCode") - print(response.messages.resultCode) + if response is not None: - if hasattr(response, 'transactionResponse') and response.transactionResponse and hasattr(response.transactionResponse, 'errors') and response.transactionResponse.errors: - print("DEBUG: Using transactionResponse.errors") - error = response.transactionResponse.errors[0] - rejection_reason = f"{error.errorCode.text}: {error.errorText.text}" - elif hasattr(response, 'messages') and response.messages and hasattr(response.messages, 'message') and response.messages.message: - print("DEBUG: Using response.messages.message") - msg = response.messages.message[0] - print("DEBUG: msg object") - print(msg) - print("DEBUG: msg attributes") - print(dir(msg)) - print("DEBUG: msg.code") - print(getattr(msg, 'code', 'NO code ATTR')) - print("DEBUG: msg.text") - print(getattr(msg, 'text', 'NO text ATTR')) + # 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" - code_val = None - text_val = None - if hasattr(msg, 'code') and msg.code is not None and hasattr(msg.code, 'text'): - code_val = msg.code.text - elif hasattr(msg, 'code'): - code_val = str(msg.code) + # 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" - if hasattr(msg, 'text') and msg.text is not None and hasattr(msg.text, 'text'): - text_val = msg.text.text - elif hasattr(msg, 'text'): - text_val = str(msg.text) - - rejection_reason = f"{code_val}: {text_val}" if code_val and text_val and text_val != "None" else f"{code_val}" if code_val else f"Error: {getattr(msg, 'text', 'Unknown error')}" - print("DEBUG: Constructed rejection_reason") - print(rejection_reason) + 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") diff --git a/app/services/payment_service.py b/app/services/payment_service.py index d2a41d9..24cf9ee 100644 --- a/app/services/payment_service.py +++ b/app/services/payment_service.py @@ -276,3 +276,33 @@ def capture_authorized_transaction(transaction_req: schemas.TransactionCapture): controller = createTransactionController(createtransactionrequest) controller.execute() return controller.getresponse() + + +def charge_customer_profile(customer_profile_id: str, payment_profile_id: str, transaction_req: schemas.TransactionCreateByCardID): + """ + Creates an AUTH_CAPTURE transaction (charge now) against a customer profile. + This charges the customer immediately for the full amount. + """ + logger.info(f"Charging profile {customer_profile_id} / payment {payment_profile_id} for ${transaction_req.charge_amount}") + + merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY) + + profile_to_charge = apicontractsv1.customerProfilePaymentType() + profile_to_charge.customerProfileId = customer_profile_id + profile_to_charge.customerPaymentProfileId = payment_profile_id + + transactionRequest = apicontractsv1.transactionRequestType( + transactionType="authCaptureTransaction", + amount=f"{transaction_req.charge_amount:.2f}", + profile=profile_to_charge + ) + + createtransactionrequest = apicontractsv1.createTransactionRequest( + merchantAuthentication=merchantAuth, + transactionRequest=transactionRequest + ) + + controller = createTransactionController(createtransactionrequest) + controller.execute() + # The response is returned directly to the router to be parsed there + return controller.getresponse()