fix: refractor the length functions and add comments, and address the issues

This commit is contained in:
sunhaolou 2025-07-22 13:16:14 +08:00
parent fca4216043
commit d01468f89a
2 changed files with 202 additions and 149 deletions

View File

@ -26,6 +26,14 @@ class StripeManager:
async def create_account_link(self, account_id: str, link_type: str = "account_onboarding") -> Optional[str]:
account = stripe.Account.retrieve(account_id)
# For account_update, try to show dashboard if TOS is accepted
self.module_logger.log_info("create_account_link urls",
{
"redirect_url": "{}/work".format(self.site_url_root),
"refresh_url": "{}/front-door".format(self.site_url_root),
"return_url": "{}/work".format(self.site_url_root)
}
)
if link_type == "account_update" and account.tos_acceptance.date:
login_link = stripe.Account.create_login_link(
account_id,
@ -345,10 +353,14 @@ class StripeManager:
async def invoke_checkout_session_webhook(
self, event: dict
) -> Tuple[bool, Optional[str], Optional[str]]:
# Handle the checkout.session.completed event
"""
Handle checkout.session.completed webhook events from Stripe.
Updates transaction status and saves payment method information for future use.
"""
if event["type"] == "checkout.session.completed":
session = event["data"]["object"]
# Find and validate the transaction
transaction = await self.__fetch_transaction_by_session_id(session["id"])
if not transaction:
await self.module_logger.log_error(
@ -357,145 +369,195 @@ class StripeManager:
)
return False, None, None
# Update transaction status
transaction.status = TransactionStatus.COMPLETED
transaction.updated_time = datetime.now(timezone.utc)
await transaction.save()
# Update transaction status to completed
await self.__update_transaction_status(transaction)
# Save payment method information
payment_method_saved = False
try:
# Get the Stripe session to extract payment method details
stripe_session = stripe.checkout.Session.retrieve(session["id"])
payment_intent_id = stripe_session.get('payment_intent')
# Process and save payment method information
await self.__process_payment_method(session, transaction)
if payment_intent_id:
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
payment_method_id = payment_intent.get('payment_method')
if payment_method_id:
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
card_details = payment_method.get('card', {})
# Get user email (use a fallback since we don't have access to user profile)
user_email = f"user_{transaction.from_user}@freeleaps.com"
# Get or create customer for the user
customer_id = None
try:
# Search for existing customers by email
customers = stripe.Customer.list(email=user_email, limit=1)
if customers.data:
customer_id = customers.data[0].id
else:
# Create new customer
customer = stripe.Customer.create(
email=user_email,
metadata={"user_id": transaction.from_user}
)
customer_id = customer.id
except Exception as customer_error:
# Use a fallback customer ID or skip payment method saving
customer_id = None
if customer_id:
try:
# Check if payment method is already attached to a customer
payment_method_obj = stripe.PaymentMethod.retrieve(payment_method_id)
if payment_method_obj.customer:
# Use the existing customer ID
customer_id = payment_method_obj.customer
else:
# Try to attach payment method to customer in Stripe
stripe.PaymentMethod.attach(
payment_method_id,
customer=customer_id
)
# Check if payment method already exists in our database
from backend.infra.payment.models import StripePaymentMethodDoc
existing_payment_method = await StripePaymentMethodDoc.find_one(
StripePaymentMethodDoc.stripe_payment_method_id == payment_method_id
)
if existing_payment_method:
payment_method_saved = True
else:
# Save to our database only if it doesn't exist
payment_method_doc = StripePaymentMethodDoc(
user_id=transaction.from_user,
stripe_customer_id=customer_id,
stripe_payment_method_id=payment_method_id,
card_last4=card_details.get('last4'),
card_brand=card_details.get('brand'),
card_exp_month=card_details.get('exp_month'),
card_exp_year=card_details.get('exp_year'),
created_time=datetime.now(timezone.utc),
updated_time=datetime.now(timezone.utc),
)
await payment_method_doc.save()
payment_method_saved = True
except stripe.error.InvalidRequestError as attach_error:
if "already attached" in str(attach_error).lower():
# Check if payment method already exists in our database
from backend.infra.payment.models import StripePaymentMethodDoc
existing_payment_method = await StripePaymentMethodDoc.find_one(
StripePaymentMethodDoc.stripe_payment_method_id == payment_method_id
)
if existing_payment_method:
payment_method_saved = True
else:
# Still save to our database since it's already attached
payment_method_doc = StripePaymentMethodDoc(
user_id=transaction.from_user,
stripe_customer_id=customer_id,
stripe_payment_method_id=payment_method_id,
card_last4=card_details.get('last4'),
card_brand=card_details.get('brand'),
card_exp_month=card_details.get('exp_month'),
card_exp_year=card_details.get('exp_year'),
created_time=datetime.now(timezone.utc),
updated_time=datetime.now(timezone.utc),
)
await payment_method_doc.save()
payment_method_saved = True
elif "may not be used again" in str(attach_error).lower():
# Check if payment method already exists in our database
from backend.infra.payment.models import StripePaymentMethodDoc
existing_payment_method = await StripePaymentMethodDoc.find_one(
StripePaymentMethodDoc.stripe_payment_method_id == payment_method_id
)
if existing_payment_method:
payment_method_saved = True
else:
# Save to our database even though it can't be attached
payment_method_doc = StripePaymentMethodDoc(
user_id=transaction.from_user,
stripe_customer_id=customer_id,
stripe_payment_method_id=payment_method_id,
card_last4=card_details.get('last4'),
card_brand=card_details.get('brand'),
card_exp_month=card_details.get('exp_month'),
card_exp_year=card_details.get('exp_year'),
created_time=datetime.now(timezone.utc),
updated_time=datetime.now(timezone.utc),
)
await payment_method_doc.save()
payment_method_saved = True
except Exception as save_error:
await self.module_logger.log_error(
error=f"Error saving payment method to database: {save_error}",
properties={"payment_method_id": payment_method_id, "user_id": transaction.from_user}
)
except Exception as payment_method_error:
await self.module_logger.log_error(
error=f"Error processing payment method: {payment_method_error}",
properties={"session_id": session["id"], "user_id": transaction.from_user}
)
# Don't fail the webhook if payment method saving fails, but log it
return True, transaction.project_id, transaction.milestone_index
return False, None, None
async def __update_transaction_status(self, transaction: StripeTransactionDoc) -> None:
"""
Update transaction status to completed and save to database.
"""
transaction.status = TransactionStatus.COMPLETED
transaction.updated_time = datetime.now(timezone.utc)
await transaction.save()
async def __process_payment_method(self, session: dict, transaction: StripeTransactionDoc) -> None:
"""
Extract payment method details from Stripe session and save to database.
Creates or finds customer and attaches payment method for future use.
"""
try:
# Get payment method details from Stripe
payment_method_info = await self.__extract_payment_method_info(session)
if not payment_method_info:
return
payment_method_id, card_details = payment_method_info
# Get or create Stripe customer for the user
customer_id = await self.__get_or_create_customer(transaction.from_user)
if not customer_id:
return
# Attach payment method to customer and save to database
await self.__attach_and_save_payment_method(
payment_method_id, card_details, customer_id, transaction.from_user
)
except Exception as payment_method_error:
await self.module_logger.log_error(
error=f"Error processing payment method: {payment_method_error}",
properties={"session_id": session["id"], "user_id": transaction.from_user}
)
async def __extract_payment_method_info(self, session: dict) -> Optional[Tuple[str, dict]]:
"""
Extract payment method ID and card details from Stripe session.
Returns tuple of (payment_method_id, card_details) or None if not found.
"""
try:
# Get the Stripe session to extract payment method details
stripe_session = stripe.checkout.Session.retrieve(session["id"])
payment_intent_id = stripe_session.get('payment_intent')
if not payment_intent_id:
return None
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
payment_method_id = payment_intent.get('payment_method')
if not payment_method_id:
return None
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
card_details = payment_method.get('card', {})
return payment_method_id, card_details
except Exception as e:
await self.module_logger.log_error(
error=f"Error extracting payment method info: {e}",
properties={"session_id": session["id"]}
)
return None
async def __get_or_create_customer(self, user_id: str) -> Optional[str]:
"""
Find existing Stripe customer by email or create new one.
Returns customer ID or None if creation fails.
"""
try:
# Generate email for user (fallback since we don't have access to user profile)
user_email = f"user_{user_id}@freeleaps.com"
# Search for existing customers by email
customers = stripe.Customer.list(email=user_email, limit=1)
if customers.data:
return customers.data[0].id
# Create new customer if not found
customer = stripe.Customer.create(
email=user_email,
metadata={"user_id": user_id}
)
return customer.id
except Exception as customer_error:
await self.module_logger.log_error(
error=f"Error getting/creating customer: {customer_error}",
properties={"user_id": user_id}
)
return None
async def __attach_and_save_payment_method(
self, payment_method_id: str, card_details: dict, customer_id: str, user_id: str
) -> None:
"""
Attach payment method to Stripe customer and save details to database.
Handles various error scenarios gracefully.
"""
try:
# Check if payment method is already attached to a customer
payment_method_obj = stripe.PaymentMethod.retrieve(payment_method_id)
if payment_method_obj.customer:
# Use the existing customer ID
customer_id = payment_method_obj.customer
else:
# Try to attach payment method to customer in Stripe
stripe.PaymentMethod.attach(
payment_method_id,
customer=customer_id
)
# Save to database
await self.__save_payment_method_to_db(
payment_method_id, card_details, customer_id, user_id
)
except stripe.error.InvalidRequestError as attach_error:
# Handle specific Stripe attachment errors
await self.__handle_attachment_error(
attach_error, payment_method_id, card_details, customer_id, user_id
)
except Exception as save_error:
await self.module_logger.log_error(
error=f"Error attaching payment method: {save_error}",
properties={"payment_method_id": payment_method_id, "user_id": user_id}
)
async def __save_payment_method_to_db(
self, payment_method_id: str, card_details: dict, customer_id: str, user_id: str
) -> None:
"""
Save payment method details to database if it doesn't already exist.
"""
from backend.infra.payment.models import StripePaymentMethodDoc
# Check if payment method already exists in our database
existing_payment_method = await StripePaymentMethodDoc.find_one(
StripePaymentMethodDoc.stripe_payment_method_id == payment_method_id
)
if existing_payment_method:
return # Already saved
# Save to our database
payment_method_doc = StripePaymentMethodDoc(
user_id=user_id,
stripe_customer_id=customer_id,
stripe_payment_method_id=payment_method_id,
card_last4=card_details.get('last4'),
card_brand=card_details.get('brand'),
card_exp_month=card_details.get('exp_month'),
card_exp_year=card_details.get('exp_year'),
created_time=datetime.now(timezone.utc),
updated_time=datetime.now(timezone.utc),
)
await payment_method_doc.save()
async def __handle_attachment_error(
self, attach_error: stripe.error.InvalidRequestError,
payment_method_id: str, card_details: dict, customer_id: str, user_id: str
) -> None:
"""
Handle specific Stripe attachment errors and still save to database when possible.
"""
error_message = str(attach_error).lower()
if "already attached" in error_message or "may not be used again" in error_message:
# Payment method can't be attached but we can still save to database
await self.__save_payment_method_to_db(
payment_method_id, card_details, customer_id, user_id
)
else:
# Log other attachment errors
await self.module_logger.log_error(
error=f"Error attaching payment method: {attach_error}",
properties={"payment_method_id": payment_method_id, "user_id": user_id}
)

View File

@ -47,17 +47,8 @@ class LoggerBase:
filter=lambda record: record["extra"].get("topic") == self.__logger_name,
)
try:
host_name = socket.gethostname()
host_ip = socket.gethostbyname(host_name)
except socket.gaierror:
# Fallback if hostname resolution fails
host_name = "localhost"
host_ip = "127.0.0.1"
except Exception:
# Generic fallback
host_name = "localhost"
host_ip = "127.0.0.1"
host_name = socket.gethostname()
host_ip = socket.gethostbyname(host_name)
self.logger = guru_logger.bind(
topic=self.__logger_name,
host_ip=host_ip,