fix: refractor the length functions and add comments, and address the issues
This commit is contained in:
parent
fca4216043
commit
d01468f89a
@ -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}
|
||||
)
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user