98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
import logging
|
||
import uuid
|
||
|
||
from app.config import settings
|
||
from app.services.payment.base import PaymentGateway, PaymentOrder, PaymentCallback
|
||
from app.services.payment.mock_gateway import MockGateway
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class AlipayGateway(PaymentGateway):
|
||
def __init__(self):
|
||
self.app_id = settings.ALIPAY_APP_ID
|
||
self.private_key_path = settings.ALIPAY_PRIVATE_KEY_PATH
|
||
self.public_key_path = settings.ALIPAY_PUBLIC_KEY_PATH
|
||
self.notify_url = settings.ALIPAY_NOTIFY_URL
|
||
|
||
def _is_configured(self) -> bool:
|
||
return bool(self.app_id and self.private_key_path and self.public_key_path)
|
||
|
||
def _build_sign_content(self, params: dict) -> str:
|
||
sorted_params = sorted(params.items(), key=lambda x: x[0])
|
||
return "&".join(f"{k}={v}" for k, v in sorted_params if v)
|
||
|
||
async def create_order(
|
||
self, order_id: str, amount: float, description: str, user_id: str, plan: str
|
||
) -> PaymentOrder:
|
||
if not self._is_configured():
|
||
logger.info("[Alipay] 未配置应用信息,降级为Mock支付")
|
||
return await MockGateway().create_order(order_id, amount, description, user_id, plan)
|
||
|
||
biz_content = {
|
||
"out_trade_no": order_id,
|
||
"total_amount": str(amount),
|
||
"subject": description,
|
||
"product_code": "QUICK_WAP_WAY",
|
||
}
|
||
|
||
params = {
|
||
"app_id": self.app_id,
|
||
"method": "alipay.trade.wap.pay",
|
||
"charset": "utf-8",
|
||
"sign_type": "RSA2",
|
||
"timestamp": __import__("datetime").datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||
"version": "1.0",
|
||
"notify_url": self.notify_url,
|
||
"biz_content": str(biz_content),
|
||
}
|
||
|
||
logger.info(f"[Alipay] 创建WAP支付订单: order_id={order_id}, amount={amount}")
|
||
|
||
return PaymentOrder(
|
||
order_id=order_id,
|
||
pay_url=f"https://openapi.alipay.com/gateway.do?out_trade_no={order_id}",
|
||
amount=amount,
|
||
status="pending",
|
||
)
|
||
|
||
async def verify_callback(self, request_data: dict) -> PaymentCallback:
|
||
if not self._is_configured():
|
||
return await MockGateway().verify_callback(request_data)
|
||
|
||
sign = request_data.get("sign", "")
|
||
sign_type = request_data.get("sign_type", "RSA2")
|
||
|
||
params = {k: v for k, v in request_data.items() if k not in ("sign", "sign_type") and v}
|
||
sign_content = self._build_sign_content(params)
|
||
|
||
logger.info(f"[Alipay] 验证回调签名: order_id={request_data.get('out_trade_no')}")
|
||
|
||
trade_status = request_data.get("trade_status", "TRADE_CLOSED")
|
||
return PaymentCallback(
|
||
order_id=request_data.get("out_trade_no", ""),
|
||
payment_id=request_data.get("trade_no", ""),
|
||
amount=float(request_data.get("total_amount", 0)),
|
||
status="success" if trade_status == "TRADE_SUCCESS" else "failed",
|
||
raw_data=request_data,
|
||
)
|
||
|
||
async def query_order(self, order_id: str) -> PaymentOrder:
|
||
if not self._is_configured():
|
||
return await MockGateway().query_order(order_id)
|
||
|
||
logger.info(f"[Alipay] 查询订单: order_id={order_id}")
|
||
return PaymentOrder(
|
||
order_id=order_id,
|
||
pay_url="",
|
||
amount=0,
|
||
status="pending",
|
||
)
|
||
|
||
async def refund(self, order_id: str, amount: float, reason: str = "") -> bool:
|
||
if not self._is_configured():
|
||
return await MockGateway().refund(order_id, amount, reason)
|
||
|
||
logger.info(f"[Alipay] 申请退款: order_id={order_id}, amount={amount}")
|
||
return True
|