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]:
|
async def create_account_link(self, account_id: str, link_type: str = "account_onboarding") -> Optional[str]:
|
||||||
account = stripe.Account.retrieve(account_id)
|
account = stripe.Account.retrieve(account_id)
|
||||||
# For account_update, try to show dashboard if TOS is accepted
|
# 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:
|
if link_type == "account_update" and account.tos_acceptance.date:
|
||||||
login_link = stripe.Account.create_login_link(
|
login_link = stripe.Account.create_login_link(
|
||||||
account_id,
|
account_id,
|
||||||
@ -345,10 +353,14 @@ class StripeManager:
|
|||||||
async def invoke_checkout_session_webhook(
|
async def invoke_checkout_session_webhook(
|
||||||
self, event: dict
|
self, event: dict
|
||||||
) -> Tuple[bool, Optional[str], Optional[str]]:
|
) -> 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":
|
if event["type"] == "checkout.session.completed":
|
||||||
session = event["data"]["object"]
|
session = event["data"]["object"]
|
||||||
|
|
||||||
|
# Find and validate the transaction
|
||||||
transaction = await self.__fetch_transaction_by_session_id(session["id"])
|
transaction = await self.__fetch_transaction_by_session_id(session["id"])
|
||||||
if not transaction:
|
if not transaction:
|
||||||
await self.module_logger.log_error(
|
await self.module_logger.log_error(
|
||||||
@ -357,48 +369,119 @@ class StripeManager:
|
|||||||
)
|
)
|
||||||
return False, None, None
|
return False, None, None
|
||||||
|
|
||||||
# Update transaction status
|
# Update transaction status to completed
|
||||||
|
await self.__update_transaction_status(transaction)
|
||||||
|
|
||||||
|
# Process and save payment method information
|
||||||
|
await self.__process_payment_method(session, transaction)
|
||||||
|
|
||||||
|
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.status = TransactionStatus.COMPLETED
|
||||||
transaction.updated_time = datetime.now(timezone.utc)
|
transaction.updated_time = datetime.now(timezone.utc)
|
||||||
await transaction.save()
|
await transaction.save()
|
||||||
|
|
||||||
# Save payment method information
|
async def __process_payment_method(self, session: dict, transaction: StripeTransactionDoc) -> None:
|
||||||
payment_method_saved = False
|
"""
|
||||||
|
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:
|
try:
|
||||||
# Get the Stripe session to extract payment method details
|
# Get the Stripe session to extract payment method details
|
||||||
stripe_session = stripe.checkout.Session.retrieve(session["id"])
|
stripe_session = stripe.checkout.Session.retrieve(session["id"])
|
||||||
payment_intent_id = stripe_session.get('payment_intent')
|
payment_intent_id = stripe_session.get('payment_intent')
|
||||||
|
|
||||||
if payment_intent_id:
|
if not payment_intent_id:
|
||||||
|
return None
|
||||||
|
|
||||||
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
|
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
|
||||||
payment_method_id = payment_intent.get('payment_method')
|
payment_method_id = payment_intent.get('payment_method')
|
||||||
|
|
||||||
if payment_method_id:
|
if not payment_method_id:
|
||||||
|
return None
|
||||||
|
|
||||||
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
|
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
|
||||||
card_details = payment_method.get('card', {})
|
card_details = payment_method.get('card', {})
|
||||||
|
|
||||||
# Get user email (use a fallback since we don't have access to user profile)
|
return payment_method_id, card_details
|
||||||
user_email = f"user_{transaction.from_user}@freeleaps.com"
|
|
||||||
|
|
||||||
# Get or create customer for the user
|
except Exception as e:
|
||||||
customer_id = None
|
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:
|
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
|
# Search for existing customers by email
|
||||||
customers = stripe.Customer.list(email=user_email, limit=1)
|
customers = stripe.Customer.list(email=user_email, limit=1)
|
||||||
if customers.data:
|
if customers.data:
|
||||||
customer_id = customers.data[0].id
|
return customers.data[0].id
|
||||||
else:
|
|
||||||
# Create new customer
|
# Create new customer if not found
|
||||||
customer = stripe.Customer.create(
|
customer = stripe.Customer.create(
|
||||||
email=user_email,
|
email=user_email,
|
||||||
metadata={"user_id": transaction.from_user}
|
metadata={"user_id": user_id}
|
||||||
)
|
)
|
||||||
customer_id = customer.id
|
return customer.id
|
||||||
except Exception as customer_error:
|
|
||||||
# Use a fallback customer ID or skip payment method saving
|
|
||||||
customer_id = None
|
|
||||||
|
|
||||||
if 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:
|
try:
|
||||||
# Check if payment method is already attached to a customer
|
# Check if payment method is already attached to a customer
|
||||||
payment_method_obj = stripe.PaymentMethod.retrieve(payment_method_id)
|
payment_method_obj = stripe.PaymentMethod.retrieve(payment_method_id)
|
||||||
@ -412,90 +495,69 @@ class StripeManager:
|
|||||||
customer=customer_id
|
customer=customer_id
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if payment method already exists in our database
|
# Save to database
|
||||||
from backend.infra.payment.models import StripePaymentMethodDoc
|
await self.__save_payment_method_to_db(
|
||||||
existing_payment_method = await StripePaymentMethodDoc.find_one(
|
payment_method_id, card_details, customer_id, user_id
|
||||||
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:
|
except stripe.error.InvalidRequestError as attach_error:
|
||||||
if "already attached" in str(attach_error).lower():
|
# Handle specific Stripe attachment errors
|
||||||
# Check if payment method already exists in our database
|
await self.__handle_attachment_error(
|
||||||
from backend.infra.payment.models import StripePaymentMethodDoc
|
attach_error, payment_method_id, card_details, customer_id, user_id
|
||||||
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:
|
except Exception as save_error:
|
||||||
await self.module_logger.log_error(
|
await self.module_logger.log_error(
|
||||||
error=f"Error saving payment method to database: {save_error}",
|
error=f"Error attaching payment method: {save_error}",
|
||||||
properties={"payment_method_id": payment_method_id, "user_id": transaction.from_user}
|
properties={"payment_method_id": payment_method_id, "user_id": user_id}
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as payment_method_error:
|
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(
|
await self.module_logger.log_error(
|
||||||
error=f"Error processing payment method: {payment_method_error}",
|
error=f"Error attaching payment method: {attach_error}",
|
||||||
properties={"session_id": session["id"], "user_id": transaction.from_user}
|
properties={"payment_method_id": payment_method_id, "user_id": user_id}
|
||||||
)
|
)
|
||||||
# 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
|
|
||||||
|
|||||||
@ -47,17 +47,8 @@ class LoggerBase:
|
|||||||
filter=lambda record: record["extra"].get("topic") == self.__logger_name,
|
filter=lambda record: record["extra"].get("topic") == self.__logger_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
|
||||||
host_name = socket.gethostname()
|
host_name = socket.gethostname()
|
||||||
host_ip = socket.gethostbyname(host_name)
|
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"
|
|
||||||
self.logger = guru_logger.bind(
|
self.logger = guru_logger.bind(
|
||||||
topic=self.__logger_name,
|
topic=self.__logger_name,
|
||||||
host_ip=host_ip,
|
host_ip=host_ip,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user