Files
taiji-AI-PAD/services/mcp-server/app/paypal_client.py
T
2026-03-15 15:30:24 +00:00

229 lines
7.9 KiB
Python

"""
PayPal 客户端封装
用于处理 PayPal 支付相关的 API 调用。
支持创建订单、捕获支付、查询订单状态等操作。
重要:这是客户支付的真金白银,所有操作必须有完整的日志记录。
"""
import logging
from typing import Optional
from paypalserversdk.http.auth.o_auth_2 import ClientCredentialsAuthCredentials
from paypalserversdk.logging.configuration.api_logging_configuration import (
LoggingConfiguration,
RequestLoggingConfiguration,
ResponseLoggingConfiguration,
)
from paypalserversdk.paypal_serversdk_client import PaypalServersdkClient
from paypalserversdk.controllers.orders_controller import OrdersController
from paypalserversdk.models.order_request import OrderRequest
from paypalserversdk.models.checkout_payment_intent import CheckoutPaymentIntent
from paypalserversdk.models.purchase_unit_request import PurchaseUnitRequest
from paypalserversdk.models.amount_with_breakdown import AmountWithBreakdown
from paypalserversdk.api_helper import APIHelper
from paypalserversdk.configuration import Environment
from config import settings
import structlog
logger = structlog.get_logger(__name__)
class PayPalClient:
"""PayPal 客户端封装"""
def __init__(self):
self._client: Optional[PaypalServersdkClient] = None
self._orders_controller: Optional[OrdersController] = None
def _get_environment(self) -> Environment:
"""获取 PayPal 环境配置"""
if settings.paypal_environment == "production":
return Environment.PRODUCTION
return Environment.SANDBOX
def _get_client(self) -> PaypalServersdkClient:
"""获取 PayPal SDK 客户端(懒加载)"""
if self._client is None:
self._client = PaypalServersdkClient(
client_credentials_auth_credentials=ClientCredentialsAuthCredentials(
o_auth_client_id=settings.paypal_client_id,
o_auth_client_secret=settings.paypal_client_secret,
),
environment=self._get_environment(),
logging_configuration=LoggingConfiguration(
log_level=logging.INFO,
mask_sensitive_headers=True,
request_logging_config=RequestLoggingConfiguration(
log_body=True
),
response_logging_config=ResponseLoggingConfiguration(
log_body=True
),
),
)
return self._client
@property
def orders_controller(self) -> OrdersController:
"""获取订单控制器"""
if self._orders_controller is None:
self._orders_controller = self._get_client().orders
return self._orders_controller
async def create_order(self, amount: float, currency: str = "USD") -> dict:
"""
创建 PayPal 订单
Args:
amount: 支付金额
currency: 货币类型,默认 USD
Returns:
包含 order_id 和状态的字典
Raises:
Exception: PayPal API 调用失败
"""
order_request = OrderRequest(
intent=CheckoutPaymentIntent.CAPTURE,
purchase_units=[
PurchaseUnitRequest(
amount=AmountWithBreakdown(
currency_code=currency,
value=f"{amount:.2f}",
),
description="Taiji AI Platform - EU Balance Recharge",
)
],
)
try:
response = self.orders_controller.create_order({"body": order_request})
if response.body:
order = response.body
logger.info(
"PayPal 订单创建成功",
order_id=order.id,
status=order.status,
amount=amount
)
return {
"order_id": order.id,
"status": order.status,
"links": [{"rel": link.rel, "href": link.href} for link in order.links] if order.links else []
}
else:
raise Exception("PayPal 返回空响应")
except Exception as e:
logger.error("PayPal 订单创建失败", error=str(e))
raise
async def capture_order(self, order_id: str) -> dict:
"""
捕获 PayPal 订单(完成支付)
Args:
order_id: PayPal 订单 ID
Returns:
包含支付详情的字典
Raises:
Exception: PayPal API 调用失败
"""
try:
response = self.orders_controller.capture_order({"id": order_id})
if response.body:
order = response.body
# 提取支付信息
capture_info = {}
if order.purchase_units and len(order.purchase_units) > 0:
pu = order.purchase_units[0]
if pu.payments and pu.payments.captures and len(pu.payments.captures) > 0:
capture = pu.payments.captures[0]
capture_info = {
"capture_id": capture.id,
"amount": float(capture.amount.value) if capture.amount else 0,
"currency": capture.amount.currency_code if capture.amount else "USD",
}
# 提取付款人信息
payer_info = {}
if order.payer:
payer_info = {
"payer_id": order.payer.payer_id,
"email": order.payer.email_address,
"name": f"{order.payer.name.given_name} {order.payer.name.surname}" if order.payer.name else None,
}
logger.info(
"PayPal 订单捕获成功",
order_id=order.id,
status=order.status,
capture_id=capture_info.get("capture_id")
)
return {
"order_id": order.id,
"status": order.status,
**capture_info,
**payer_info,
"raw_response": APIHelper.json_serialize(order),
}
else:
raise Exception("PayPal 返回空响应")
except Exception as e:
logger.error("PayPal 订单捕获失败", order_id=order_id, error=str(e))
raise
async def get_order(self, order_id: str) -> dict:
"""
查询 PayPal 订单状态
Args:
order_id: PayPal 订单 ID
Returns:
订单详情
Raises:
Exception: PayPal API 调用失败
"""
try:
response = self.orders_controller.get_order({"id": order_id})
if response.body:
order = response.body
return {
"order_id": order.id,
"status": order.status,
"create_time": order.create_time,
"update_time": order.update_time,
}
else:
raise Exception("PayPal 返回空响应")
except Exception as e:
logger.error("PayPal 订单查询失败", order_id=order_id, error=str(e))
raise
# 单例
_paypal_client: Optional[PayPalClient] = None
def get_paypal_client() -> PayPalClient:
"""获取 PayPal 客户端单例"""
global _paypal_client
if _paypal_client is None:
_paypal_client = PayPalClient()
return _paypal_client