refractor: a current working version before cleaning up.
This commit is contained in:
parent
40e0fafc2c
commit
ccc995f599
24
.env
Normal file
24
.env
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
APP_NAME=payment
|
||||||
|
export SERVICE_API_ACCESS_HOST=0.0.0.0
|
||||||
|
export SERVICE_API_ACCESS_PORT=8006
|
||||||
|
export CONTAINER_APP_ROOT=/app
|
||||||
|
export LOG_BASE_PATH=$CONTAINER_APP_ROOT/log/$APP_NAME
|
||||||
|
export BACKEND_LOG_FILE_NAME=$APP_NAME
|
||||||
|
export APPLICATION_ACTIVITY_LOG=$APP_NAME-activity
|
||||||
|
export MONGODB_NAME=freeleaps2
|
||||||
|
export MONGODB_PORT=27017
|
||||||
|
GIT_REPO_ROOT=/Users/sunhaolou/Downloads/Freeleaps/freeleaps-service-hub
|
||||||
|
CODEBASE_ROOT=/Users/sunhaolou/Downloads/Freeleaps/freeleaps-service-hub/apps/payment
|
||||||
|
SITE_DEPLOY_FOLDER=/Users/sunhaolou/Downloads/Freeleaps/freeleaps-service-hub/sites/payment/deploy
|
||||||
|
#!/bin/bash
|
||||||
|
export VENV_DIR=venv_t
|
||||||
|
export VENV_ACTIVATE=venv_t/bin/activate
|
||||||
|
export DOCKER_HOME=/var/lib/docker
|
||||||
|
export DOCKER_APP_HOME=$DOCKER_HOME/app
|
||||||
|
export DOCKER_BACKEND_HOME=$DOCKER_APP_HOME/$APP_NAME
|
||||||
|
export DOCKER_BACKEND_LOG_HOME=$DOCKER_BACKEND_HOME/log
|
||||||
|
export MONGODB_URI=mongodb://localhost:27017/
|
||||||
|
export FREELEAPS_ENV=local
|
||||||
|
export SITE_URL_ROOT=http://localhost:5173/
|
||||||
|
export LOG_BASE_PATH=${CODEBASE_ROOT}/log
|
||||||
|
export STRIPE_API_KEY=sk_test_51Ogsw5B0IyqaSJBrwczlr820jnmvA1qQQGoLZ2XxOsIzikpmXo4pRLjw4XVMTEBR8DdVTYySiAv1XX53Zv5xqynF00GfMqttFd
|
||||||
@ -7,9 +7,9 @@ export BACKEND_LOG_FILE_NAME=$APP_NAME
|
|||||||
export APPLICATION_ACTIVITY_LOG=$APP_NAME-activity
|
export APPLICATION_ACTIVITY_LOG=$APP_NAME-activity
|
||||||
export MONGODB_NAME=freeleaps2
|
export MONGODB_NAME=freeleaps2
|
||||||
export MONGODB_PORT=27017
|
export MONGODB_PORT=27017
|
||||||
GIT_REPO_ROOT=/mnt/freeleaps/freeleaps-service-hub
|
GIT_REPO_ROOT=/Users/sunhaolou/Downloads/Freeleaps/freeleaps-service-hub
|
||||||
CODEBASE_ROOT=/mnt/freeleaps/freeleaps-service-hub/apps/payment
|
CODEBASE_ROOT=/Users/sunhaolou/Downloads/Freeleaps/freeleaps-service-hub/apps/payment
|
||||||
SITE_DEPLOY_FOLDER=/mnt/freeleaps/freeleaps-service-hub/sites/payment/deploy
|
SITE_DEPLOY_FOLDER=/Users/sunhaolou/Downloads/Freeleaps/freeleaps-service-hub/sites/payment/deploy
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export VENV_DIR=venv_t
|
export VENV_DIR=venv_t
|
||||||
export VENV_ACTIVATE=venv_t/bin/activate
|
export VENV_ACTIVATE=venv_t/bin/activate
|
||||||
|
|||||||
@ -9,9 +9,6 @@ class PaymentHub:
|
|||||||
self.stripe_manager = StripeManager()
|
self.stripe_manager = StripeManager()
|
||||||
return
|
return
|
||||||
|
|
||||||
async def fetch_wechat_qr_code(self, project_id: str) -> Optional[Dict[str, any]]:
|
|
||||||
return await self.payment_manager.fetch_wechat_qr_code(project_id)
|
|
||||||
|
|
||||||
async def fetch_stripe_account_id(self, user_id: str) -> Optional[str]:
|
async def fetch_stripe_account_id(self, user_id: str) -> Optional[str]:
|
||||||
return await self.payment_manager.fetch_stripe_account_id(user_id)
|
return await self.payment_manager.fetch_stripe_account_id(user_id)
|
||||||
|
|
||||||
|
|||||||
@ -9,18 +9,6 @@ class PaymentManager:
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.module_logger = ModuleLogger(sender_id=PaymentManager)
|
self.module_logger = ModuleLogger(sender_id=PaymentManager)
|
||||||
|
|
||||||
async def fetch_wechat_qr_code(self, project_id: str) -> Optional[Dict[str, any]]:
|
|
||||||
project = await ProjectDoc.get(project_id)
|
|
||||||
proposer = project.proposer_id
|
|
||||||
income_profile = await IncomeProfileDoc.find_one(
|
|
||||||
IncomeProfileDoc.user_id == proposer
|
|
||||||
)
|
|
||||||
if income_profile:
|
|
||||||
return income_profile.bank_account.money_collecting_methods[
|
|
||||||
0
|
|
||||||
].wechat_qr_code
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def fetch_stripe_account_id(self, user_id: str) -> Optional[str]:
|
async def fetch_stripe_account_id(self, user_id: str) -> Optional[str]:
|
||||||
income_profile = await IncomeProfileDoc.find_one(IncomeProfileDoc.user_id == user_id)
|
income_profile = await IncomeProfileDoc.find_one(IncomeProfileDoc.user_id == user_id)
|
||||||
if income_profile:
|
if income_profile:
|
||||||
|
|||||||
@ -8,6 +8,7 @@ from stripe.error import SignatureVerificationError
|
|||||||
from common.log.module_logger import ModuleLogger
|
from common.log.module_logger import ModuleLogger
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import json
|
import json
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
stripe.api_key = app_settings.STRIPE_API_KEY
|
stripe.api_key = app_settings.STRIPE_API_KEY
|
||||||
@ -15,7 +16,7 @@ stripe.api_key = app_settings.STRIPE_API_KEY
|
|||||||
|
|
||||||
class StripeManager:
|
class StripeManager:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.site_url_root = app_settings.SITE_URL_ROOT.rstrip("/")
|
self.site_url_root = "http://localhost:8888"
|
||||||
self.module_logger = ModuleLogger(sender_id="StripeManager")
|
self.module_logger = ModuleLogger(sender_id="StripeManager")
|
||||||
|
|
||||||
async def create_stripe_account(self) -> Optional[str]:
|
async def create_stripe_account(self) -> Optional[str]:
|
||||||
@ -25,26 +26,18 @@ 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,
|
||||||
redirect_url="{}/work".format(self.site_url_root)
|
redirect_url="http://localhost:8888/work"
|
||||||
)
|
)
|
||||||
return login_link.url
|
return login_link.url
|
||||||
|
|
||||||
# Otherwise show onboarding
|
# Otherwise show onboarding
|
||||||
account_link = stripe.AccountLink.create(
|
account_link = stripe.AccountLink.create(
|
||||||
account=account_id,
|
account=account_id,
|
||||||
refresh_url="{}/front-door".format(self.site_url_root),
|
refresh_url="http://localhost:8888/front-door",
|
||||||
return_url="{}/work".format(self.site_url_root),
|
return_url="http://localhost:8888/work",
|
||||||
type="account_onboarding",
|
type="account_onboarding",
|
||||||
)
|
)
|
||||||
return account_link.url
|
return account_link.url
|
||||||
@ -78,10 +71,20 @@ class StripeManager:
|
|||||||
async def __fetch_transaction_by_session_id(
|
async def __fetch_transaction_by_session_id(
|
||||||
self, session_id: str
|
self, session_id: str
|
||||||
) -> Optional[StripeTransactionDoc]:
|
) -> Optional[StripeTransactionDoc]:
|
||||||
|
await self.module_logger.log_info(
|
||||||
|
f"Looking up transaction for session_id: {session_id}",
|
||||||
|
properties={"session_id": session_id}
|
||||||
|
)
|
||||||
|
|
||||||
transactions = await StripeTransactionDoc.find(
|
transactions = await StripeTransactionDoc.find(
|
||||||
StripeTransactionDoc.stripe_checkout_session_id == session_id
|
StripeTransactionDoc.stripe_checkout_session_id == session_id
|
||||||
).to_list()
|
).to_list()
|
||||||
|
|
||||||
|
await self.module_logger.log_info(
|
||||||
|
f"Found {len(transactions)} transactions for session_id: {session_id}",
|
||||||
|
properties={"session_id": session_id, "transaction_count": len(transactions)}
|
||||||
|
)
|
||||||
|
|
||||||
if len(transactions) > 1:
|
if len(transactions) > 1:
|
||||||
await self.module_logger.log_error(
|
await self.module_logger.log_error(
|
||||||
error="More than one transaction found for session_id: {}".format(
|
error="More than one transaction found for session_id: {}".format(
|
||||||
@ -90,9 +93,24 @@ class StripeManager:
|
|||||||
properties={"session_id": session_id},
|
properties={"session_id": session_id},
|
||||||
)
|
)
|
||||||
elif len(transactions) == 0:
|
elif len(transactions) == 0:
|
||||||
|
await self.module_logger.log_error(
|
||||||
|
error="No transaction found for session_id: {}".format(session_id),
|
||||||
|
properties={"session_id": session_id},
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return transactions[0]
|
transaction = transactions[0]
|
||||||
|
await self.module_logger.log_info(
|
||||||
|
f"Found transaction: project_id={transaction.project_id}, milestone_index={transaction.milestone_index}, status={transaction.status}",
|
||||||
|
properties={
|
||||||
|
"session_id": session_id,
|
||||||
|
"project_id": transaction.project_id,
|
||||||
|
"milestone_index": transaction.milestone_index,
|
||||||
|
"status": transaction.status
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return transaction
|
||||||
|
|
||||||
async def fetch_transaction_by_session_id(
|
async def fetch_transaction_by_session_id(
|
||||||
self, session_id: str
|
self, session_id: str
|
||||||
@ -203,19 +221,25 @@ class StripeManager:
|
|||||||
transaction.stripe_price_id = price.id
|
transaction.stripe_price_id = price.id
|
||||||
await transaction.save()
|
await transaction.save()
|
||||||
|
|
||||||
payment_link = stripe.PaymentLink.create(
|
# Prepare payment link parameters with conditional application_fee_amount
|
||||||
line_items=[
|
payment_link_params = {
|
||||||
|
"line_items": [
|
||||||
{
|
{
|
||||||
"price": transaction.stripe_price_id,
|
"price": transaction.stripe_price_id,
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
application_fee_amount=transaction.application_fee_amount,
|
"on_behalf_of": transaction.to_stripe_account_id,
|
||||||
on_behalf_of=transaction.to_stripe_account_id,
|
"transfer_data": {
|
||||||
transfer_data={
|
|
||||||
"destination": transaction.to_stripe_account_id,
|
"destination": transaction.to_stripe_account_id,
|
||||||
},
|
},
|
||||||
)
|
}
|
||||||
|
|
||||||
|
# Only add application_fee_amount if it's greater than 0
|
||||||
|
if transaction.application_fee_amount and transaction.application_fee_amount > 0:
|
||||||
|
payment_link_params["application_fee_amount"] = transaction.application_fee_amount
|
||||||
|
|
||||||
|
payment_link = stripe.PaymentLink.create(**payment_link_params)
|
||||||
|
|
||||||
if payment_link:
|
if payment_link:
|
||||||
transaction.stripe_payment_link = payment_link.url
|
transaction.stripe_payment_link = payment_link.url
|
||||||
@ -276,27 +300,37 @@ class StripeManager:
|
|||||||
transaction.stripe_price_id = price.id
|
transaction.stripe_price_id = price.id
|
||||||
await transaction.save()
|
await transaction.save()
|
||||||
|
|
||||||
session = stripe.checkout.Session.create(
|
# Prepare payment_intent_data with conditional application_fee_amount
|
||||||
payment_method_types=["card"],
|
payment_intent_data = {
|
||||||
line_items=[
|
"on_behalf_of": transaction.to_stripe_account_id,
|
||||||
|
"transfer_data": {
|
||||||
|
"destination": transaction.to_stripe_account_id,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Only add application_fee_amount if it's greater than 0
|
||||||
|
if transaction.application_fee_amount and transaction.application_fee_amount > 0:
|
||||||
|
payment_intent_data["application_fee_amount"] = transaction.application_fee_amount
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
session_params = {
|
||||||
|
"payment_method_types": ["card"],
|
||||||
|
"line_items": [
|
||||||
{
|
{
|
||||||
"price": transaction.stripe_price_id,
|
"price": transaction.stripe_price_id,
|
||||||
"quantity": 1,
|
"quantity": 1,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
payment_intent_data={
|
"payment_intent_data": payment_intent_data,
|
||||||
"on_behalf_of": transaction.to_stripe_account_id,
|
"mode": "payment",
|
||||||
"application_fee_amount": transaction.application_fee_amount,
|
"success_url": "http://localhost:8888/projects",
|
||||||
"transfer_data": {
|
"cancel_url": "http://localhost:8888/projects",
|
||||||
"destination": transaction.to_stripe_account_id,
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
mode="payment",
|
|
||||||
success_url="{}/projects".format(
|
session = stripe.checkout.Session.create(**session_params)
|
||||||
self.site_url_root
|
|
||||||
), # needs to be set, local: http://localhost/
|
|
||||||
cancel_url="{}/projects".format(self.site_url_root),
|
|
||||||
)
|
|
||||||
|
|
||||||
if session:
|
if session:
|
||||||
transaction.stripe_checkout_session_id = session.id
|
transaction.stripe_checkout_session_id = session.id
|
||||||
@ -335,18 +369,220 @@ class StripeManager:
|
|||||||
# Handle the checkout.session.completed event
|
# Handle the checkout.session.completed event
|
||||||
if event["type"] == "checkout.session.completed":
|
if event["type"] == "checkout.session.completed":
|
||||||
session = event["data"]["object"]
|
session = event["data"]["object"]
|
||||||
|
await self.module_logger.log_info(
|
||||||
|
f"Processing checkout.session.completed webhook for session_id: {session['id']}",
|
||||||
|
properties={"session_id": session["id"]}
|
||||||
|
)
|
||||||
|
|
||||||
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(
|
||||||
error="Transaction not found for session_id: {}".format(session["id"]),
|
error="Transaction not found for session_id: {}".format(session["id"]),
|
||||||
properties={"session_id": session["id"]},
|
properties={"session_id": session["id"]},
|
||||||
)
|
)
|
||||||
return False
|
return False, None, None
|
||||||
|
|
||||||
|
# Update transaction status
|
||||||
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()
|
||||||
|
|
||||||
|
await self.module_logger.log_info(
|
||||||
|
f"Successfully updated transaction status to COMPLETED for project_id: {transaction.project_id}, milestone_index: {transaction.milestone_index}",
|
||||||
|
properties={
|
||||||
|
"project_id": transaction.project_id,
|
||||||
|
"milestone_index": transaction.milestone_index,
|
||||||
|
"session_id": session["id"]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Save payment method information
|
||||||
|
payment_method_saved = False
|
||||||
|
try:
|
||||||
|
print("=" * 50)
|
||||||
|
print("STARTING PAYMENT METHOD PROCESSING")
|
||||||
|
print("=" * 50)
|
||||||
|
print(f"Starting payment method processing for session {session['id']}")
|
||||||
|
|
||||||
|
# Get the Stripe session to extract payment method details
|
||||||
|
try:
|
||||||
|
stripe_session = stripe.checkout.Session.retrieve(session["id"])
|
||||||
|
print(f"Successfully retrieved Stripe session: {session['id']}")
|
||||||
|
except Exception as session_error:
|
||||||
|
print(f"Failed to retrieve Stripe session {session['id']}: {session_error}")
|
||||||
|
raise session_error
|
||||||
|
|
||||||
|
payment_intent_id = stripe_session.get('payment_intent')
|
||||||
|
print(f"Payment intent ID from session: {payment_intent_id}")
|
||||||
|
|
||||||
|
if payment_intent_id:
|
||||||
|
try:
|
||||||
|
payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
|
||||||
|
print(f"Successfully retrieved payment intent: {payment_intent_id}")
|
||||||
|
except Exception as pi_error:
|
||||||
|
print(f"Failed to retrieve payment intent {payment_intent_id}: {pi_error}")
|
||||||
|
raise pi_error
|
||||||
|
|
||||||
|
payment_method_id = payment_intent.get('payment_method')
|
||||||
|
print(f"Payment method ID from payment intent: {payment_method_id}")
|
||||||
|
|
||||||
|
if payment_method_id:
|
||||||
|
try:
|
||||||
|
payment_method = stripe.PaymentMethod.retrieve(payment_method_id)
|
||||||
|
print(f"Successfully retrieved payment method: {payment_method_id}")
|
||||||
|
except Exception as pm_error:
|
||||||
|
print(f"Failed to retrieve payment method {payment_method_id}: {pm_error}")
|
||||||
|
raise pm_error
|
||||||
|
|
||||||
|
card_details = payment_method.get('card', {})
|
||||||
|
print(f"Card details: {card_details}")
|
||||||
|
|
||||||
|
# Get user email (use a fallback since we don't have access to user profile)
|
||||||
|
user_email = f"user_{transaction.from_user}@freeleaps.com"
|
||||||
|
print(f"User email for customer creation: {user_email}")
|
||||||
|
|
||||||
|
# Get or create customer for the user
|
||||||
|
# Try to find existing customer first
|
||||||
|
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
|
||||||
|
print(f"Found existing customer: {customer_id}")
|
||||||
|
else:
|
||||||
|
# Create new customer
|
||||||
|
customer = stripe.Customer.create(
|
||||||
|
email=user_email,
|
||||||
|
metadata={"user_id": transaction.from_user}
|
||||||
|
)
|
||||||
|
customer_id = customer.id
|
||||||
|
print(f"Created new customer: {customer_id}")
|
||||||
|
except Exception as customer_error:
|
||||||
|
print(f"Error creating/finding customer: {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:
|
||||||
|
print(f"Payment method {payment_method_id} already attached to customer {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
|
||||||
|
)
|
||||||
|
print(f"Successfully attached payment method {payment_method_id} to 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:
|
||||||
|
print(f"Payment method {payment_method_id} already exists in database, skipping save")
|
||||||
|
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
|
||||||
|
print(f"Successfully saved payment method {payment_method_id} for user {transaction.from_user}")
|
||||||
|
except stripe.error.InvalidRequestError as attach_error:
|
||||||
|
if "already attached" in str(attach_error).lower():
|
||||||
|
print(f"Payment method {payment_method_id} already attached to 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:
|
||||||
|
print(f"Payment method {payment_method_id} already exists in database, skipping save")
|
||||||
|
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
|
||||||
|
print(f"Successfully saved payment method {payment_method_id} for user {transaction.from_user}")
|
||||||
|
elif "may not be used again" in str(attach_error).lower():
|
||||||
|
print(f"Payment method {payment_method_id} was already used and cannot be attached to customer")
|
||||||
|
print(f"This is normal for one-time payment methods. Saving card details to database anyway.")
|
||||||
|
# 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:
|
||||||
|
print(f"Payment method {payment_method_id} already exists in database, skipping save")
|
||||||
|
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
|
||||||
|
print(f"Successfully saved payment method {payment_method_id} for user {transaction.from_user} (one-time use)")
|
||||||
|
else:
|
||||||
|
print(f"Error attaching payment method: {attach_error}")
|
||||||
|
except Exception as save_error:
|
||||||
|
print(f"Error saving payment method to database: {save_error}")
|
||||||
|
else:
|
||||||
|
print(f"Could not create customer for user {transaction.from_user}, skipping payment method save")
|
||||||
|
else:
|
||||||
|
print(f"No payment method found in payment intent {payment_intent_id}")
|
||||||
|
else:
|
||||||
|
print(f"No payment intent found in session {session['id']}")
|
||||||
|
|
||||||
|
except Exception as payment_method_error:
|
||||||
|
print(f"Error processing payment method: {payment_method_error}")
|
||||||
|
import traceback
|
||||||
|
print(f"Full traceback for payment method error:")
|
||||||
|
print(traceback.format_exc())
|
||||||
|
# Don't fail the webhook if payment method saving fails, but log it
|
||||||
|
|
||||||
|
print(f"Payment method saved: {payment_method_saved}")
|
||||||
return True, transaction.project_id, transaction.milestone_index
|
return True, transaction.project_id, transaction.milestone_index
|
||||||
|
|
||||||
|
await self.module_logger.log_info(
|
||||||
|
f"Received non-checkout.session.completed webhook event: {event['type']}",
|
||||||
|
properties={"event_type": event["type"]}
|
||||||
|
)
|
||||||
return False, None, None
|
return False, None, None
|
||||||
|
|||||||
@ -12,7 +12,6 @@ class MoneyCollectionType(IntEnum):
|
|||||||
UNSPECIFIED = 0
|
UNSPECIFIED = 0
|
||||||
MARKED_AS_PAID = 1
|
MARKED_AS_PAID = 1
|
||||||
UPLOAD_PROOF = 2
|
UPLOAD_PROOF = 2
|
||||||
WECHAT_QR_CODE = 3
|
|
||||||
STRIPE_CHECKOUT = 4
|
STRIPE_CHECKOUT = 4
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -24,3 +24,18 @@ class StripeTransactionDoc(Document):
|
|||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
name = "stripe_transaction"
|
name = "stripe_transaction"
|
||||||
|
|
||||||
|
|
||||||
|
class StripePaymentMethodDoc(Document):
|
||||||
|
user_id: str
|
||||||
|
stripe_customer_id: str
|
||||||
|
stripe_payment_method_id: str
|
||||||
|
card_last4: Optional[str] = None
|
||||||
|
card_brand: Optional[str] = None
|
||||||
|
card_exp_month: Optional[int] = None
|
||||||
|
card_exp_year: Optional[int] = None
|
||||||
|
created_time: datetime
|
||||||
|
updated_time: datetime
|
||||||
|
|
||||||
|
class Settings:
|
||||||
|
name = "stripe_payment_method"
|
||||||
|
|||||||
@ -5,9 +5,9 @@
|
|||||||
# TODO: Add all models to backend_models
|
# TODO: Add all models to backend_models
|
||||||
from backend.services.payment.models import IncomeProfileDoc, PaymentProfileDoc
|
from backend.services.payment.models import IncomeProfileDoc, PaymentProfileDoc
|
||||||
from backend.services.project.models import ProjectDoc
|
from backend.services.project.models import ProjectDoc
|
||||||
from backend.infra.payment.models import StripeTransactionDoc
|
from backend.infra.payment.models import StripeTransactionDoc, StripePaymentMethodDoc
|
||||||
|
|
||||||
backend_models = [IncomeProfileDoc, PaymentProfileDoc, ProjectDoc, StripeTransactionDoc]
|
backend_models = [IncomeProfileDoc, PaymentProfileDoc, ProjectDoc, StripeTransactionDoc, StripePaymentMethodDoc]
|
||||||
# backend_models.extend(code_models)
|
# backend_models.extend(code_models)
|
||||||
# backend_models.extend(user_models)
|
# backend_models.extend(user_models)
|
||||||
# backend_models.extend(profile_models)
|
# backend_models.extend(profile_models)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ from typing import List, Dict, Optional
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from beanie import Document
|
from beanie import Document
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from backend.services.payment.constants import PaymentGateway
|
from backend.services.payment.constants import PaymentGateway
|
||||||
from backend.infra.payment.constants import MoneyCollectionType, PaymentLocation
|
from backend.infra.payment.constants import MoneyCollectionType, PaymentLocation
|
||||||
@ -23,7 +24,6 @@ class MoneyCollectingMethod(BaseModel):
|
|||||||
location: Optional[PaymentLocation]
|
location: Optional[PaymentLocation]
|
||||||
priority: int = 0 # less number has high priority to be used.
|
priority: int = 0 # less number has high priority to be used.
|
||||||
stripe_account_id: Optional[str]
|
stripe_account_id: Optional[str]
|
||||||
wechat_qr_code: Optional[str]
|
|
||||||
last_update_time: Optional[int] = None
|
last_update_time: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
@ -61,3 +61,6 @@ class PaymentProfileDoc(Document):
|
|||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
name = "payment_profile"
|
name = "payment_profile"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -3,4 +3,3 @@ from enum import IntEnum
|
|||||||
|
|
||||||
class PaymentGateway(IntEnum):
|
class PaymentGateway(IntEnum):
|
||||||
STRIP = 1
|
STRIP = 1
|
||||||
WECHAT = 2
|
|
||||||
|
|||||||
@ -27,7 +27,6 @@ class MoneyCollectingMethod(BaseModel):
|
|||||||
location: Optional[PaymentLocation]
|
location: Optional[PaymentLocation]
|
||||||
priority: int = 0 # less number has high priority to be used.
|
priority: int = 0 # less number has high priority to be used.
|
||||||
stripe_account_id: Optional[str]
|
stripe_account_id: Optional[str]
|
||||||
wechat_qr_code: Optional[str]
|
|
||||||
last_update_time: Optional[int] = None
|
last_update_time: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -47,8 +47,17 @@ 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,
|
||||||
|
|||||||
@ -6,19 +6,6 @@ from fastapi.encoders import jsonable_encoder
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
payment_hub = PaymentHub()
|
payment_hub = PaymentHub()
|
||||||
|
|
||||||
# Web API
|
|
||||||
# Fetch wechat qr code
|
|
||||||
@router.get(
|
|
||||||
"/fetch_wechat_qr_code/{project_id}",
|
|
||||||
operation_id="fetch_wechat_qr_code",
|
|
||||||
summary="Fetch wechat qr code",
|
|
||||||
description="Fetch wechat qr code",
|
|
||||||
)
|
|
||||||
async def fetch_wechat_qr_code(
|
|
||||||
project_id: str
|
|
||||||
):
|
|
||||||
return await payment_hub.fetch_wechat_qr_code(project_id)
|
|
||||||
|
|
||||||
# Web API
|
# Web API
|
||||||
# Fetch stripe account id
|
# Fetch stripe account id
|
||||||
@router.get(
|
@router.get(
|
||||||
|
|||||||
@ -209,3 +209,23 @@ async def handle_account_webhook(
|
|||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
|
||||||
return JSONResponse(content={"status": "success"})
|
return JSONResponse(content={"status": "success"})
|
||||||
|
|
||||||
|
|
||||||
|
# Web API
|
||||||
|
# Detach payment method
|
||||||
|
@router.delete(
|
||||||
|
"/detach_payment_method/{payment_method_id}",
|
||||||
|
operation_id="detach_payment_method",
|
||||||
|
summary="Detach payment method from customer",
|
||||||
|
description="Detach a payment method from a Stripe customer",
|
||||||
|
)
|
||||||
|
async def detach_payment_method(payment_method_id: str):
|
||||||
|
try:
|
||||||
|
# Detach the payment method from Stripe
|
||||||
|
stripe.PaymentMethod.detach(payment_method_id)
|
||||||
|
return JSONResponse(content={"success": True, "message": "Payment method detached successfully"})
|
||||||
|
except Exception as e:
|
||||||
|
return JSONResponse(
|
||||||
|
status_code=400,
|
||||||
|
content={"success": False, "message": f"Failed to detach payment method: {str(e)}"}
|
||||||
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user