forked from xiaohei/taiji-AI-PAD
229 lines
7.9 KiB
Python
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
|