first commit
This commit is contained in:
305
app/routers/main.py
Normal file
305
app/routers/main.py
Normal file
@@ -0,0 +1,305 @@
|
||||
## File: app/routers/main.py (or your equivalent path)
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
|
||||
|
||||
# Your existing imports
|
||||
from database import session
|
||||
from pyowm import OWM
|
||||
|
||||
from app.models.customer import Customer_Customer
|
||||
from app.models.cards import Card_Card
|
||||
# Imports needed for the migration task.
|
||||
# YOU MUST ADJUST THESE IMPORTS TO MATCH YOUR PROJECT'S ACTUAL STRUCTURE.
|
||||
from database import session # Assuming your SessionLocal is here for a clean session
|
||||
from authorizenet import apicontractsv1
|
||||
from authorizenet.constants import constants
|
||||
from authorizenet.apicontrollers import (
|
||||
createCustomerProfileController,
|
||||
createCustomerPaymentProfileController,
|
||||
getCustomerProfileIdsController,
|
||||
getCustomerProfileController
|
||||
)
|
||||
# --- ROUTER DEFINITION ---
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/main",
|
||||
tags=["main"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
API_LOGIN_ID = '9U6w96gZmX'
|
||||
TRANSACTION_KEY = '94s6Qy458mMNJr7G'
|
||||
|
||||
constants.show_url_on_request = True
|
||||
constants.environment = constants.SANDBOX
|
||||
|
||||
|
||||
@router.post("/maintenance/migrate-cards-now")
|
||||
def run_card_migration_synchronously():
|
||||
"""
|
||||
Triggers a one-time, SYNCHRONOUS task to migrate all existing, insecurely
|
||||
stored credit cards from the `card_card` table to secure Authorize.Net CIM profiles.
|
||||
This function is self-contained and does not use `crud` or `schemas`.
|
||||
|
||||
WARNING: This is a long-running process and the request may time out.
|
||||
Monitor server logs for completion. This endpoint should be removed after use.
|
||||
"""
|
||||
|
||||
def _create_authnet_profile(customer_obj: Customer_Customer, card_obj: Card_Card):
|
||||
"""Helper to create a new Authorize.Net Customer Profile."""
|
||||
merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY)
|
||||
expiration_date_str = f"{card_obj.expiration_year}-{str(card_obj.expiration_month).zfill(2)}"
|
||||
|
||||
# Validate expiration
|
||||
month = None
|
||||
year = None
|
||||
if card_obj.expiration_month:
|
||||
try:
|
||||
month = int(card_obj.expiration_month)
|
||||
except:
|
||||
pass
|
||||
if card_obj.expiration_year:
|
||||
try:
|
||||
year = int(card_obj.expiration_year)
|
||||
except:
|
||||
pass
|
||||
if not year or not month or month < 1 or month > 12 or year < 2000 or year > 2050:
|
||||
raise ValueError(f"Invalid expiration year={card_obj.expiration_year}, month={card_obj.expiration_month} for card ID {card_obj.id}")
|
||||
expiration_date_str = f"{year:04d}-{month:02d}"
|
||||
# // FIX 1: Sanitize card number by removing all non-digit characters
|
||||
sanitized_card_number = ''.join(filter(str.isdigit, str(card_obj.card_number)))
|
||||
if len(sanitized_card_number) < 13 or len(sanitized_card_number) > 19 or not sanitized_card_number.isdigit():
|
||||
raise ValueError(f"Invalid card number length {len(sanitized_card_number)} or non-numeric for card ID {card_obj.id}")
|
||||
# Sanitize CVV
|
||||
cardCode = str(card_obj.security_number).strip()
|
||||
if len(cardCode) < 3 or len(cardCode) > 4 or not cardCode.isdigit():
|
||||
raise ValueError(f"Invalid CVV length {len(cardCode)} or non-numeric for card ID {card_obj.id}")
|
||||
# Debug print
|
||||
print(f"DEBUG SEND CREATE: cardNumber='{sanitized_card_number}', expirationDate='{expiration_date_str}', cardCode='{cardCode}', customerID={customer_obj.id}, name='{customer_obj.customer_first_name} {customer_obj.customer_last_name}', address='{customer_obj.customer_address}', city='{customer_obj.customer_town}', zip='{customer_obj.customer_zip}', email='{customer_obj.customer_email}'")
|
||||
creditCard = apicontractsv1.creditCardType(
|
||||
cardNumber=sanitized_card_number,
|
||||
expirationDate=expiration_date_str,
|
||||
cardCode=cardCode
|
||||
)
|
||||
billTo = apicontractsv1.customerAddressType(
|
||||
firstName=customer_obj.customer_first_name,
|
||||
lastName=customer_obj.customer_last_name,
|
||||
address=customer_obj.customer_address,
|
||||
city=customer_obj.customer_town,
|
||||
zip=customer_obj.customer_zip
|
||||
)
|
||||
paymentProfile = apicontractsv1.customerPaymentProfileType(
|
||||
billTo=billTo,
|
||||
payment=apicontractsv1.paymentType(creditCard=creditCard)
|
||||
)
|
||||
customerProfile = apicontractsv1.customerProfileType(
|
||||
merchantCustomerId=str(customer_obj.id),
|
||||
email=customer_obj.customer_email,
|
||||
paymentProfiles=[paymentProfile]
|
||||
)
|
||||
request = apicontractsv1.createCustomerProfileRequest(
|
||||
merchantAuthentication=merchantAuth,
|
||||
profile=customerProfile,
|
||||
validationMode="testMode"
|
||||
)
|
||||
controller = createCustomerProfileController(request)
|
||||
controller.execute()
|
||||
response = controller.getresponse()
|
||||
|
||||
if response is not None and hasattr(response, 'messages') and response.messages.resultCode == "Ok":
|
||||
profile_id = response.customerProfileId if hasattr(response, 'customerProfileId') else None
|
||||
payment_ids = response.customerPaymentProfileIdList.numericString if hasattr(response, 'customerPaymentProfileIdList') and hasattr(response.customerPaymentProfileIdList, 'numericString') else []
|
||||
payment_id = payment_ids[0] if payment_ids else None
|
||||
return str(profile_id), str(payment_id) if payment_id else None
|
||||
else:
|
||||
# Handle errors
|
||||
error_msg = "Unknown Authorize.Net Error"
|
||||
if response is not None and hasattr(response, 'messages') and response.messages is not None and hasattr(response.messages, 'message') and response.messages.message:
|
||||
msg = response.messages.message[0] if isinstance(response.messages.message, list) else response.messages.message
|
||||
if msg and hasattr(msg, 'text') and msg.text is not None:
|
||||
if isinstance(msg.text, str):
|
||||
error_msg = msg.text
|
||||
else:
|
||||
# Handle case where text might be an object with 'text' attribute
|
||||
error_msg = msg.text.text if hasattr(msg.text, 'text') else str(msg.text)
|
||||
print(f" AUTH.NET ERROR: {error_msg}")
|
||||
return None, None
|
||||
|
||||
def _add_authnet_payment_profile(profile_id: str, customer_obj: Customer_Customer, card_obj: Card_Card):
|
||||
"""Helper to add a new Payment Profile to an existing Customer Profile."""
|
||||
merchantAuth = apicontractsv1.merchantAuthenticationType(name=API_LOGIN_ID, transactionKey=TRANSACTION_KEY)
|
||||
expiration_date_str = f"{card_obj.expiration_year}-{str(card_obj.expiration_month).zfill(2)}"
|
||||
|
||||
# // FIX 1 (Applied here as well): Sanitize card number
|
||||
sanitized_card_number = ''.join(filter(str.isdigit, str(card_obj.card_number)))
|
||||
|
||||
creditCard = apicontractsv1.creditCardType(
|
||||
cardNumber=sanitized_card_number,
|
||||
expirationDate=expiration_date_str,
|
||||
cardCode=str(card_obj.security_number).strip()
|
||||
)
|
||||
paymentProfile = apicontractsv1.customerPaymentProfileType(
|
||||
billTo=apicontractsv1.customerAddressType(firstName=customer_obj.customer_first_name, lastName=customer_obj.customer_last_name),
|
||||
payment=apicontractsv1.paymentType(creditCard=creditCard)
|
||||
)
|
||||
request = apicontractsv1.createCustomerPaymentProfileRequest(
|
||||
merchantAuthentication=merchantAuth,
|
||||
customerProfileId=profile_id,
|
||||
paymentProfile=paymentProfile,
|
||||
validationMode="testMode"
|
||||
)
|
||||
controller = createCustomerPaymentProfileController(request)
|
||||
controller.execute()
|
||||
response = controller.getresponse()
|
||||
if response is not None and response.messages.resultCode == "Ok":
|
||||
return str(response.customerPaymentProfileId)
|
||||
else:
|
||||
# // FIX 2 (Applied here as well): Robust error message parsing
|
||||
error_msg = "Unknown Authorize.Net Error"
|
||||
if response is not None and hasattr(response, 'messages') and response.messages is not None and hasattr(response.messages, 'message') and response.messages.message:
|
||||
msg = response.messages.message[0]
|
||||
if hasattr(msg, 'text') and msg.text is not None:
|
||||
error_msg = msg.text.text
|
||||
print(f" AUTH.NET ERROR: {error_msg}")
|
||||
return None
|
||||
|
||||
# --- MIGRATION SCRIPT LOGIC ---
|
||||
print("="*60)
|
||||
print("MIGRATION STARTED: Migrating all customer cards to Authorize.Net CIM.")
|
||||
print(f"Start Time: {datetime.now()}")
|
||||
print("="*60)
|
||||
|
||||
customers_processed = 0
|
||||
cards_migrated = 0
|
||||
error_count = 0
|
||||
|
||||
try:
|
||||
customers_to_migrate = (session
|
||||
.query(Customer_Customer)
|
||||
.filter(Customer_Customer.auth_net_profile_id == None)
|
||||
.order_by(func.random())
|
||||
.limit(1)
|
||||
.all())
|
||||
|
||||
total_customers = len(customers_to_migrate)
|
||||
print(f"Found {total_customers} customers to migrate.")
|
||||
|
||||
for index, customer in enumerate(customers_to_migrate):
|
||||
customers_processed += 1
|
||||
print(f"\n--- Processing Customer {index + 1}/{total_customers} (ID: {customer.id}, Name: {customer.customer_first_name}) ---")
|
||||
|
||||
insecure_cards = (session
|
||||
.query(Card_Card)
|
||||
.filter(Card_Card.user_id == customer.id)
|
||||
.all())
|
||||
|
||||
print(f"DEBUG: Customer {customer.id} has {len(insecure_cards)} cards")
|
||||
customer_profile_id_for_session = None
|
||||
|
||||
# # Check for existing profile
|
||||
# existing_profile, existing_payment_ids = _get_existing_profile(str(customer.id))
|
||||
# if existing_profile and existing_payment_ids:
|
||||
# print(f" -> Found existing Authorize.Net profile ID {existing_profile}")
|
||||
# customer.auth_net_profile_id = existing_profile
|
||||
# session.add(customer)
|
||||
# session.commit()
|
||||
# for i, card_data in enumerate(insecure_cards):
|
||||
# if i < len(existing_payment_ids):
|
||||
# display_card_number = ''.join(filter(str.isdigit, str(card_data.card_number)))
|
||||
# if not display_card_number:
|
||||
# print(f" SKIPPING CARD: Card number is empty after sanitization for card ID {card_data.id}")
|
||||
# error_count += 1
|
||||
# continue
|
||||
# card_data.auth_net_payment_profile_id = existing_payment_ids[i]
|
||||
# session.add(card_data)
|
||||
# session.commit()
|
||||
# print(f" SUCCESS: Mapped existing Payment ID {existing_payment_ids[i]} to card ending ...{display_card_number[-4:]}")
|
||||
# cards_migrated += 1
|
||||
# else:
|
||||
# print(f" WARNING: No existing payment profile Kawasaki for card {card_data.id}")
|
||||
# error_count += 1
|
||||
# continue # skip creation loop
|
||||
|
||||
for i, card_data in enumerate(insecure_cards):
|
||||
try:
|
||||
# Basic data validation before sending
|
||||
if not card_data.card_number or not card_data.expiration_year or not card_data.expiration_month:
|
||||
print(f" SKIPPING CARD: Incomplete card data for card ID {card_data.id}.")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
print(f"DEBUG RAW: Card ID {card_data.id}: number='{card_data.card_number}', year='{card_data.expiration_year}', month='{card_data.expiration_month}', cvv='{card_data.security_number}', name='{customer.customer_first_name} {customer.customer_last_name}'")
|
||||
|
||||
# Sanitize card number for display
|
||||
display_card_number = ''.join(filter(str.isdigit, str(card_data.card_number)))
|
||||
|
||||
if not display_card_number:
|
||||
print(f" SKIPPING CARD: Card number is empty after sanitization for card ID {card_data.id}.")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
if i == 0:
|
||||
print(f" -> Creating new CIM profile with card ending in ...{display_card_number[-4:]}")
|
||||
profile_id, payment_id = _create_authnet_profile(customer, card_data)
|
||||
|
||||
if profile_id and payment_id:
|
||||
customer.auth_net_profile_id = profile_id
|
||||
session.add(customer)
|
||||
|
||||
customer_profile_id_for_session = profile_id
|
||||
|
||||
card_data.auth_net_payment_profile_id = payment_id
|
||||
session.add(card_data)
|
||||
session.commit()
|
||||
|
||||
print(f" SUCCESS: Created Profile ID {profile_id} and Payment ID {payment_id}")
|
||||
cards_migrated += 1
|
||||
else:
|
||||
print(f" ERROR: Failed to create profile for customer {customer.id}.")
|
||||
error_count += 1
|
||||
break
|
||||
else:
|
||||
if not display_card_number:
|
||||
print(f" SKIPPING CARD: Card number is empty after sanitization for card ID {card_data.id}.")
|
||||
error_count += 1
|
||||
continue
|
||||
print(f" -> Adding additional card ending in ...{display_card_number[-4:]}")
|
||||
payment_id = _add_authnet_payment_profile(customer_profile_id_for_session, customer, card_data)
|
||||
if payment_id:
|
||||
card_data.auth_net_payment_profile_id = payment_id
|
||||
session.add(card_data)
|
||||
session.commit()
|
||||
print(f" SUCCESS: Added Payment ID {payment_id}")
|
||||
cards_migrated += 1
|
||||
else:
|
||||
print(f" ERROR: Failed to add additional card for customer {customer.id}.")
|
||||
error_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" CRITICAL ERROR processing a card for customer {customer.id}: {str(e)}")
|
||||
error_count += 1
|
||||
session.rollback()
|
||||
|
||||
except Exception as e:
|
||||
print(f"A critical error occurred during the migration process: {e}")
|
||||
session.rollback()
|
||||
return {"ok": False, "error": f"Migration failed with a critical error: {e}"}, 500
|
||||
|
||||
finally:
|
||||
print("="*60)
|
||||
print("MIGRATION FINISHED!")
|
||||
print(f"End Time: {datetime.now()}")
|
||||
print(f"Summary: Processed {customers_processed} customers, migrated {cards_migrated} cards, encountered {error_count} errors.")
|
||||
print("="*60)
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"message": "Migration process completed successfully.",
|
||||
"customers_processed": customers_processed,
|
||||
"cards_migrated": cards_migrated,
|
||||
"errors": error_count,
|
||||
}, 200
|
||||
Reference in New Issue
Block a user