update pay ui
This commit is contained in:
BIN
__pycache__/alipay_config.cpython-310.pyc
Normal file
BIN
__pycache__/alipay_config.cpython-310.pyc
Normal file
Binary file not shown.
BIN
__pycache__/alipay_pay.cpython-310.pyc
Normal file
BIN
__pycache__/alipay_pay.cpython-310.pyc
Normal file
Binary file not shown.
1
alipay/应用公钥.txt
Normal file
1
alipay/应用公钥.txt
Normal file
@@ -0,0 +1 @@
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkBfIjOiu8vfmOSq1BXcjDsAqQ+xtwGO0aCn0VrhVzc0T70nDchaW/TJ4nW8qlRMBlgfTi00jDGFiW4ND9JHc4aES8yiDSNeaBW4gLQhC1isvpOndyu4YgDc+2lMfghv9+6D8uFl9VS8Vk6o7D+5SiE7F8aBn49rrLyvsmdN5i6eLxIuY9NM58/o0xVG5f3ktGqfFKzhtclPbt8ej39EgziCiNFbIk2FnZp9dB56vtmCKht4t3STDpM0RfC8YlQ2WpGu9okLJYSy1rfynhh0hlOy/9y4cYl50wthoQVxH/Hm9abiTMo2u6xWESreavtdDZ8ByKVltnUrRvzDQ4tVkYwIDAQAB
|
||||
1
alipay/应用私钥.txt
Normal file
1
alipay/应用私钥.txt
Normal file
@@ -0,0 +1 @@
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCQF8iM6K7y9+Y5KrUFdyMOwCpD7G3AY7RoKfRWuFXNzRPvScNyFpb9MnidbyqVEwGWB9OLTSMMYWJbg0P0kdzhoRLzKINI15oFbiAtCELWKy+k6d3K7hiANz7aUx+CG/37oPy4WX1VLxWTqjsP7lKITsXxoGfj2usvK+yZ03mLp4vEi5j00znz+jTFUbl/eS0ap8UrOG1yU9u3x6Pf0SDOIKI0VsiTYWdmn10Hnq+2YIqG3i3dJMOkzRF8LxiVDZaka72iQslhLLWt/KeGHSGU7L/3LhxiXnTC2GhBXEf8eb1puJMyja7rFYRKt5q+10NnwHIpWW2dStG/MNDi1WRjAgMBAAECggEAHQ8+2fQfPE70djj/su94+YOVwocPB0rUWmGDrm2UmGGwkISezwZxQvUH0DBYNSJVIo3HgwN2ewu0y2HotY0pL7PNX46fE3Sv0kKIaKyO1iR1glvL6B4mgM0jduJmq1W73iB0dzVNCn3paxNcv/S/XlAMqZNBAHnpDmVcXRWCIMDG1yRN3l5NbfgPoQZI4MfAdthjIcIQmEVjNWy69swsdNndj8yrIu9E9RlWly/xqB/BJ6O53i0n+jyviy2grgHxo9VgWKv89qZiczioLT7aAJITpcMofUkGImZy+DHlf+6GU762UkwbLykYN2RRkw5TPvWt4ZUXeON4flr3ig02yQKBgQDMh82rc3TklOZSCw3lwYE58eeYxe9PXpZzcOyrSZZhCs0y528dmwYbm0mPlpVti1MGKnn2eGVKeGQ8a5CbJCi2T+VwIILWM9U2+h82nJW5KD6CYaE86KK/PlzhTGwmpecv8hafkpn51zvyjI3UkKbv/Ep+Mfy89PLumvh5Ze+EfwKBgQC0Wn1o0JCjgCt3K39tahavBuCPDvk7oLA6JLp2W9437YFeuh9L/gYdAtJU79WpmOfgr228cOlExdGGpHqLPSDFpN3ssx1pkUJ6RlTN8YInc+dbAkC6gsm5XR0Jvj4YqghyWHKhxXErnFGDof2EQq7ghHK9pokpBFPowwlzkpOeHQKBgFbqVwJG/COvCvlObUd3pbzECdEoO/wUjAbetBROHzN57Z12MAf6uuu8X9Q+/50fmdaC8nVE0HaHFsF+TGNBSHPBHBU8G51/RVopjF4eyJl4eqfZaTWC/rYagEnVuhfqZIZBcE+7cudzCayXAiaUmfxd0CI0h9yckyfGf1THdrNtAoGASMtNWwTznEqbQJpZ8HuldDe+Y3+TsTGGb7FrYWJrKv+9+9H719xL82G0K3wyLSX+UX39ONYKESwXCdVRcOnXVG7a9DLHaFitEFVa3VThR7NMajtajO1FJoAivFABGEto5V41xn2+0+9gJ1U20i9oDk7nUQzqx5drlsNCCVfcJTECgYEAlEYIQ3AiVqx0RqZjfbg+nyZQmoUfHPASY2Hu9pJWvLwXsQWSPh1gf03galXzZ46wrCaeLygGaoHXW7W8WwsYBR1tG7voQeSe8mmlWgiscmvmvqSowJ10BnsdWwpOTZ8O6rKy8HdyI8gFJyfJgfz+6KekcdGEnQbmwCvB8j+Y7IQ=
|
||||
1
alipay/支付宝公钥.txt
Normal file
1
alipay/支付宝公钥.txt
Normal file
@@ -0,0 +1 @@
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAjyULL5tuD2cjfNvgn9fvVfn+WbLhoP3wk3bcYb9AabsZc1GCBlnG579Socc3U9dG5IR7C3KHD4kYHnH4tbK2pEWmmfjbVKXWguLqL5K3Dggnl5KVOlVVjrcsmLPqTj7JdeO0AQolmgdr/o6TlhQdsqINQAK5F5wWwIwwcSoJsWZ6zlPPX/Q/eMIN4zGgK2taMhx656zSxsYE5OKRYkTJhVrMktxQdwbUzoFSID++dTpjxF4w5k5qeVW/1WZaaswMFWh2IcJ5oyc+VjTRqZvtQt4gB0Fa0EblfmSJaozhoWHwzwF+1qtv7gp/TcMYj/Ah2+tY0UPxucEcTqY/i7PPfwIDAQAB
|
||||
117
alipay_config.py
Normal file
117
alipay_config.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
支付宝支付配置文件
|
||||
电脑网站支付 (alipay.trade.page.pay)
|
||||
"""
|
||||
import os
|
||||
|
||||
# 获取当前文件所在目录
|
||||
_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# 支付宝支付配置
|
||||
ALIPAY_CONFIG = {
|
||||
# 应用ID - 从支付宝开放平台获取
|
||||
'app_id': '2021005183650009',
|
||||
|
||||
# 支付宝网关 - 正式环境
|
||||
'gateway_url': 'https://openapi.alipay.com/gateway.do',
|
||||
# 沙箱环境网关(测试用)
|
||||
# 'gateway_url': 'https://openapi-sandbox.dl.alipaydev.com/gateway.do',
|
||||
|
||||
# 签名方式 - 必须使用RSA2
|
||||
'sign_type': 'RSA2',
|
||||
|
||||
# 编码格式
|
||||
'charset': 'utf-8',
|
||||
|
||||
# 返回格式
|
||||
'format': 'json',
|
||||
|
||||
# 密钥文件路径
|
||||
'app_private_key_path': os.path.join(_BASE_DIR, 'alipay', '应用私钥.txt'),
|
||||
'alipay_public_key_path': os.path.join(_BASE_DIR, 'alipay', '支付宝公钥.txt'),
|
||||
|
||||
# 回调地址 - 替换为你的实际域名
|
||||
'notify_url': 'https://valuefrontier.cn/api/payment/alipay/callback', # 异步通知地址(后端)
|
||||
'return_url': 'https://valuefrontier.cn/pricing?payment_return=alipay', # 同步跳转地址(前端页面)
|
||||
|
||||
# 产品码 - 电脑网站支付固定为此值
|
||||
'product_code': 'FAST_INSTANT_TRADE_PAY',
|
||||
}
|
||||
|
||||
|
||||
def load_key_from_file(file_path):
|
||||
"""从文件读取密钥内容"""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return f.read().strip()
|
||||
except FileNotFoundError:
|
||||
print(f"❌ 密钥文件不存在: {file_path}")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f"❌ 读取密钥文件失败: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_app_private_key():
|
||||
"""获取应用私钥"""
|
||||
return load_key_from_file(ALIPAY_CONFIG['app_private_key_path'])
|
||||
|
||||
|
||||
def get_alipay_public_key():
|
||||
"""获取支付宝公钥"""
|
||||
return load_key_from_file(ALIPAY_CONFIG['alipay_public_key_path'])
|
||||
|
||||
|
||||
def validate_config():
|
||||
"""验证配置是否完整"""
|
||||
issues = []
|
||||
|
||||
# 检查 app_id
|
||||
if not ALIPAY_CONFIG.get('app_id') or ALIPAY_CONFIG['app_id'].startswith('your_'):
|
||||
issues.append("❌ app_id 未配置,请替换为真实的支付宝应用ID")
|
||||
|
||||
# 检查密钥文件
|
||||
if not os.path.exists(ALIPAY_CONFIG['app_private_key_path']):
|
||||
issues.append(f"❌ 应用私钥文件不存在: {ALIPAY_CONFIG['app_private_key_path']}")
|
||||
else:
|
||||
key = get_app_private_key()
|
||||
if not key or len(key) < 100:
|
||||
issues.append("❌ 应用私钥内容异常,请检查文件格式")
|
||||
|
||||
if not os.path.exists(ALIPAY_CONFIG['alipay_public_key_path']):
|
||||
issues.append(f"❌ 支付宝公钥文件不存在: {ALIPAY_CONFIG['alipay_public_key_path']}")
|
||||
else:
|
||||
key = get_alipay_public_key()
|
||||
if not key or len(key) < 100:
|
||||
issues.append("❌ 支付宝公钥内容异常,请检查文件格式")
|
||||
|
||||
return len(issues) == 0, issues
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("支付宝支付配置验证")
|
||||
print("=" * 50)
|
||||
|
||||
is_valid, issues = validate_config()
|
||||
|
||||
if is_valid:
|
||||
print("✅ 配置验证通过!")
|
||||
print(f" App ID: {ALIPAY_CONFIG['app_id']}")
|
||||
print(f" 网关: {ALIPAY_CONFIG['gateway_url']}")
|
||||
print(f" 回调地址: {ALIPAY_CONFIG['notify_url']}")
|
||||
print(f" 同步跳转: {ALIPAY_CONFIG['return_url']}")
|
||||
else:
|
||||
print("⚠️ 配置存在问题:")
|
||||
for issue in issues:
|
||||
print(f" {issue}")
|
||||
|
||||
print("\n📋 配置步骤:")
|
||||
print("1. 登录支付宝开放平台 (open.alipay.com)")
|
||||
print("2. 创建应用并获取 App ID")
|
||||
print("3. 在开发设置中配置RSA2密钥")
|
||||
print("4. 将应用私钥、支付宝公钥放到 ./alipay/ 文件夹")
|
||||
print("5. 更新本文件中的配置信息")
|
||||
|
||||
print("=" * 50)
|
||||
440
alipay_pay.py
Normal file
440
alipay_pay.py
Normal file
@@ -0,0 +1,440 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
支付宝支付集成模块
|
||||
电脑网站支付 (alipay.trade.page.pay)
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import base64
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from urllib.parse import quote_plus, urlencode
|
||||
from cryptography.hazmat.primitives import hashes, serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import padding
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
|
||||
class AlipayPay:
|
||||
"""支付宝电脑网站支付类"""
|
||||
|
||||
def __init__(self, app_id, app_private_key, alipay_public_key, notify_url, return_url,
|
||||
gateway_url='https://openapi.alipay.com/gateway.do',
|
||||
sign_type='RSA2', charset='utf-8'):
|
||||
"""
|
||||
初始化支付宝支付配置
|
||||
|
||||
Args:
|
||||
app_id: 支付宝应用ID
|
||||
app_private_key: 应用私钥(Base64编码的字符串)
|
||||
alipay_public_key: 支付宝公钥(Base64编码的字符串)
|
||||
notify_url: 异步通知地址
|
||||
return_url: 同步跳转地址
|
||||
gateway_url: 支付宝网关URL
|
||||
sign_type: 签名类型,默认RSA2
|
||||
charset: 编码,默认utf-8
|
||||
"""
|
||||
self.app_id = app_id
|
||||
self.notify_url = notify_url
|
||||
self.return_url = return_url
|
||||
self.gateway_url = gateway_url
|
||||
self.sign_type = sign_type
|
||||
self.charset = charset
|
||||
|
||||
# 加载密钥
|
||||
self.app_private_key = self._load_private_key(app_private_key)
|
||||
self.alipay_public_key = self._load_public_key(alipay_public_key)
|
||||
|
||||
print(f"✅ 支付宝支付初始化成功")
|
||||
print(f" App ID: {app_id}")
|
||||
print(f" 网关: {gateway_url}")
|
||||
|
||||
def _load_private_key(self, key_str):
|
||||
"""加载RSA私钥(支持 PKCS#1 和 PKCS#8 格式)"""
|
||||
try:
|
||||
# 如果密钥不包含头尾,尝试添加PEM格式头尾
|
||||
if '-----BEGIN' not in key_str:
|
||||
# 支付宝通常使用 PKCS#8 格式(MIIEv 开头)
|
||||
# 先尝试 PKCS#8 格式
|
||||
key_str_pkcs8 = f"-----BEGIN PRIVATE KEY-----\n{key_str}\n-----END PRIVATE KEY-----"
|
||||
try:
|
||||
private_key = serialization.load_pem_private_key(
|
||||
key_str_pkcs8.encode('utf-8'),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
return private_key
|
||||
except Exception:
|
||||
# 如果 PKCS#8 失败,尝试 PKCS#1 格式
|
||||
key_str = f"-----BEGIN RSA PRIVATE KEY-----\n{key_str}\n-----END RSA PRIVATE KEY-----"
|
||||
|
||||
private_key = serialization.load_pem_private_key(
|
||||
key_str.encode('utf-8'),
|
||||
password=None,
|
||||
backend=default_backend()
|
||||
)
|
||||
return private_key
|
||||
except Exception as e:
|
||||
print(f"[AlipayPay] Load private key failed: {e}")
|
||||
raise
|
||||
|
||||
def _load_public_key(self, key_str):
|
||||
"""加载RSA公钥"""
|
||||
try:
|
||||
# 如果密钥不包含头尾,添加PEM格式头尾
|
||||
if '-----BEGIN' not in key_str:
|
||||
key_str = f"-----BEGIN PUBLIC KEY-----\n{key_str}\n-----END PUBLIC KEY-----"
|
||||
|
||||
public_key = serialization.load_pem_public_key(
|
||||
key_str.encode('utf-8'),
|
||||
backend=default_backend()
|
||||
)
|
||||
return public_key
|
||||
except Exception as e:
|
||||
print(f"❌ 加载公钥失败: {e}")
|
||||
raise
|
||||
|
||||
def _sign(self, unsigned_string):
|
||||
"""
|
||||
RSA2签名
|
||||
|
||||
Args:
|
||||
unsigned_string: 待签名字符串
|
||||
|
||||
Returns:
|
||||
Base64编码的签名
|
||||
"""
|
||||
try:
|
||||
signature = self.app_private_key.sign(
|
||||
unsigned_string.encode('utf-8'),
|
||||
padding.PKCS1v15(),
|
||||
hashes.SHA256()
|
||||
)
|
||||
return base64.b64encode(signature).decode('utf-8')
|
||||
except Exception as e:
|
||||
print(f"❌ 签名失败: {e}")
|
||||
raise
|
||||
|
||||
def _verify(self, message, signature):
|
||||
"""
|
||||
验证支付宝签名
|
||||
|
||||
Args:
|
||||
message: 原始消息
|
||||
signature: Base64编码的签名
|
||||
|
||||
Returns:
|
||||
bool: 验证是否通过
|
||||
"""
|
||||
try:
|
||||
self.alipay_public_key.verify(
|
||||
base64.b64decode(signature),
|
||||
message.encode('utf-8'),
|
||||
padding.PKCS1v15(),
|
||||
hashes.SHA256()
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"❌ 签名验证失败: {e}")
|
||||
return False
|
||||
|
||||
def _get_sign_content(self, params):
|
||||
"""
|
||||
获取待签名字符串
|
||||
|
||||
Args:
|
||||
params: 参数字典
|
||||
|
||||
Returns:
|
||||
排序后的参数字符串
|
||||
"""
|
||||
# 过滤空值和sign参数,按key排序
|
||||
filtered_params = {k: v for k, v in params.items()
|
||||
if v is not None and v != '' and k != 'sign'}
|
||||
sorted_params = sorted(filtered_params.items())
|
||||
|
||||
# 拼接字符串
|
||||
sign_content = '&'.join([f'{k}={v}' for k, v in sorted_params])
|
||||
return sign_content
|
||||
|
||||
def create_page_pay_url(self, out_trade_no, total_amount, subject, body=None, timeout_express='30m'):
|
||||
"""
|
||||
创建电脑网站支付URL
|
||||
|
||||
Args:
|
||||
out_trade_no: 商户订单号
|
||||
total_amount: 订单总金额(元,精确到小数点后两位)
|
||||
subject: 订单标题
|
||||
body: 订单描述(可选)
|
||||
timeout_express: 订单超时时间,默认30分钟
|
||||
|
||||
Returns:
|
||||
dict: 包含支付URL的响应
|
||||
"""
|
||||
try:
|
||||
# 业务参数
|
||||
biz_content = {
|
||||
'out_trade_no': out_trade_no,
|
||||
'total_amount': str(total_amount),
|
||||
'subject': subject,
|
||||
'product_code': 'FAST_INSTANT_TRADE_PAY', # 电脑网站支付固定值
|
||||
'timeout_express': timeout_express,
|
||||
}
|
||||
if body:
|
||||
biz_content['body'] = body
|
||||
|
||||
# 公共请求参数
|
||||
params = {
|
||||
'app_id': self.app_id,
|
||||
'method': 'alipay.trade.page.pay',
|
||||
'format': 'json',
|
||||
'charset': self.charset,
|
||||
'sign_type': self.sign_type,
|
||||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'version': '1.0',
|
||||
'notify_url': self.notify_url,
|
||||
'return_url': self.return_url,
|
||||
'biz_content': json.dumps(biz_content, ensure_ascii=False),
|
||||
}
|
||||
|
||||
# 生成签名
|
||||
sign_content = self._get_sign_content(params)
|
||||
params['sign'] = self._sign(sign_content)
|
||||
|
||||
# 构建完整的支付URL
|
||||
pay_url = f"{self.gateway_url}?{urlencode(params)}"
|
||||
|
||||
print(f"✅ 支付宝订单创建成功: {out_trade_no}")
|
||||
print(f" 金额: {total_amount}元")
|
||||
print(f" 标题: {subject}")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'pay_url': pay_url,
|
||||
'order_no': out_trade_no
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 创建支付宝订单失败: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def query_order(self, out_trade_no=None, trade_no=None):
|
||||
"""
|
||||
查询订单状态
|
||||
|
||||
Args:
|
||||
out_trade_no: 商户订单号
|
||||
trade_no: 支付宝交易号
|
||||
|
||||
Returns:
|
||||
dict: 订单状态信息
|
||||
"""
|
||||
import requests
|
||||
|
||||
try:
|
||||
if not out_trade_no and not trade_no:
|
||||
return {'success': False, 'error': '订单号和交易号不能同时为空'}
|
||||
|
||||
# 业务参数
|
||||
biz_content = {}
|
||||
if out_trade_no:
|
||||
biz_content['out_trade_no'] = out_trade_no
|
||||
if trade_no:
|
||||
biz_content['trade_no'] = trade_no
|
||||
|
||||
# 公共请求参数
|
||||
params = {
|
||||
'app_id': self.app_id,
|
||||
'method': 'alipay.trade.query',
|
||||
'format': 'json',
|
||||
'charset': self.charset,
|
||||
'sign_type': self.sign_type,
|
||||
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'version': '1.0',
|
||||
'biz_content': json.dumps(biz_content, ensure_ascii=False),
|
||||
}
|
||||
|
||||
# 生成签名
|
||||
sign_content = self._get_sign_content(params)
|
||||
params['sign'] = self._sign(sign_content)
|
||||
|
||||
# 发送请求
|
||||
response = requests.get(self.gateway_url, params=params, timeout=30)
|
||||
result = response.json()
|
||||
|
||||
print(f"📡 支付宝查询响应: {result}")
|
||||
|
||||
# 解析响应
|
||||
query_response = result.get('alipay_trade_query_response', {})
|
||||
if query_response.get('code') == '10000':
|
||||
trade_status = query_response.get('trade_status')
|
||||
return {
|
||||
'success': True,
|
||||
'trade_status': trade_status,
|
||||
'trade_no': query_response.get('trade_no'),
|
||||
'out_trade_no': query_response.get('out_trade_no'),
|
||||
'total_amount': query_response.get('total_amount'),
|
||||
'buyer_logon_id': query_response.get('buyer_logon_id'),
|
||||
'send_pay_date': query_response.get('send_pay_date'),
|
||||
# 状态映射
|
||||
'is_paid': trade_status == 'TRADE_SUCCESS' or trade_status == 'TRADE_FINISHED',
|
||||
'is_closed': trade_status == 'TRADE_CLOSED',
|
||||
'is_waiting': trade_status == 'WAIT_BUYER_PAY',
|
||||
}
|
||||
else:
|
||||
error_msg = query_response.get('sub_msg') or query_response.get('msg', '查询失败')
|
||||
return {
|
||||
'success': False,
|
||||
'error': error_msg,
|
||||
'code': query_response.get('code'),
|
||||
'sub_code': query_response.get('sub_code')
|
||||
}
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"❌ 支付宝API请求失败: {e}")
|
||||
return {'success': False, 'error': f'网络请求失败: {e}'}
|
||||
except Exception as e:
|
||||
print(f"❌ 查询订单异常: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
def verify_callback(self, params):
|
||||
"""
|
||||
验证支付宝异步回调
|
||||
|
||||
Args:
|
||||
params: 回调参数字典
|
||||
|
||||
Returns:
|
||||
dict: 验证结果
|
||||
"""
|
||||
try:
|
||||
# 获取签名
|
||||
sign = params.pop('sign', None)
|
||||
sign_type = params.pop('sign_type', 'RSA2')
|
||||
|
||||
if not sign:
|
||||
return {'success': False, 'error': '缺少签名参数'}
|
||||
|
||||
# 构建待验签字符串
|
||||
sign_content = self._get_sign_content(params)
|
||||
|
||||
# 验证签名
|
||||
if self._verify(sign_content, sign):
|
||||
print(f"✅ 支付宝回调签名验证通过")
|
||||
return {
|
||||
'success': True,
|
||||
'data': params
|
||||
}
|
||||
else:
|
||||
print(f"❌ 支付宝回调签名验证失败")
|
||||
return {
|
||||
'success': False,
|
||||
'error': '签名验证失败'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 验证回调异常: {e}")
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
def verify_return(self, params):
|
||||
"""
|
||||
验证支付宝同步返回(用户支付后跳转回来)
|
||||
|
||||
Args:
|
||||
params: 同步返回参数字典
|
||||
|
||||
Returns:
|
||||
dict: 验证结果
|
||||
"""
|
||||
# 同步返回的验签逻辑与异步回调相同
|
||||
return self.verify_callback(params)
|
||||
|
||||
|
||||
# 工厂函数
|
||||
def create_alipay_instance():
|
||||
"""创建支付宝支付实例"""
|
||||
try:
|
||||
from alipay_config import ALIPAY_CONFIG, get_app_private_key, get_alipay_public_key, validate_config
|
||||
|
||||
# 验证配置
|
||||
is_valid, issues = validate_config()
|
||||
if not is_valid:
|
||||
raise ValueError(f"支付宝配置错误: {'; '.join(issues)}")
|
||||
|
||||
# 获取密钥
|
||||
app_private_key = get_app_private_key()
|
||||
alipay_public_key = get_alipay_public_key()
|
||||
|
||||
if not app_private_key or not alipay_public_key:
|
||||
raise ValueError("密钥加载失败")
|
||||
|
||||
return AlipayPay(
|
||||
app_id=ALIPAY_CONFIG['app_id'],
|
||||
app_private_key=app_private_key,
|
||||
alipay_public_key=alipay_public_key,
|
||||
notify_url=ALIPAY_CONFIG['notify_url'],
|
||||
return_url=ALIPAY_CONFIG['return_url'],
|
||||
gateway_url=ALIPAY_CONFIG['gateway_url'],
|
||||
sign_type=ALIPAY_CONFIG['sign_type'],
|
||||
charset=ALIPAY_CONFIG['charset']
|
||||
)
|
||||
|
||||
except ImportError as e:
|
||||
raise ValueError(f"无法导入支付宝配置: {e}")
|
||||
except Exception as e:
|
||||
raise ValueError(f"创建支付宝实例失败: {e}")
|
||||
|
||||
|
||||
def check_alipay_ready():
|
||||
"""检查支付宝支付是否就绪"""
|
||||
try:
|
||||
instance = create_alipay_instance()
|
||||
return True, "支付宝支付配置正确"
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""测试代码"""
|
||||
import sys
|
||||
|
||||
print("=" * 60)
|
||||
print("支付宝支付测试")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 检查配置
|
||||
is_ready, message = check_alipay_ready()
|
||||
print(f"\n配置状态: {'✅ 就绪' if is_ready else '❌ 未就绪'}")
|
||||
print(f"详情: {message}")
|
||||
|
||||
if is_ready:
|
||||
# 创建实例
|
||||
alipay = create_alipay_instance()
|
||||
|
||||
# 测试创建订单
|
||||
test_order_no = f"TEST{int(time.time())}"
|
||||
result = alipay.create_page_pay_url(
|
||||
out_trade_no=test_order_no,
|
||||
total_amount='0.01',
|
||||
subject='测试商品',
|
||||
body='这是一个测试订单'
|
||||
)
|
||||
|
||||
print(f"\n创建订单结果:")
|
||||
print(json.dumps(result, indent=2, ensure_ascii=False))
|
||||
|
||||
if result['success']:
|
||||
print(f"\n🔗 支付链接(复制到浏览器打开):")
|
||||
print(result['pay_url'][:200] + '...')
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ 测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
140
alipay_pay_worker.py
Normal file
140
alipay_pay_worker.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
支付宝支付工作脚本
|
||||
用于在 subprocess 中执行,绕过 eventlet DNS 问题
|
||||
|
||||
用法:
|
||||
python alipay_pay_worker.py check # 检查配置
|
||||
python alipay_pay_worker.py create <order_no> <amount> <subject> [body] # 创建订单
|
||||
python alipay_pay_worker.py query <order_no> # 查询订单
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
|
||||
def check_config():
|
||||
"""检查支付宝配置"""
|
||||
try:
|
||||
from alipay_pay import check_alipay_ready
|
||||
is_ready, message = check_alipay_ready()
|
||||
return {
|
||||
'success': is_ready,
|
||||
'message': message if is_ready else None,
|
||||
'error': None if is_ready else message
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def create_order(order_no, amount, subject, body=None):
|
||||
"""创建支付宝订单"""
|
||||
try:
|
||||
from alipay_pay import create_alipay_instance
|
||||
alipay = create_alipay_instance()
|
||||
|
||||
result = alipay.create_page_pay_url(
|
||||
out_trade_no=order_no,
|
||||
total_amount=str(amount),
|
||||
subject=subject,
|
||||
body=body,
|
||||
timeout_express='30m'
|
||||
)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def query_order(order_no):
|
||||
"""查询支付宝订单"""
|
||||
try:
|
||||
from alipay_pay import create_alipay_instance
|
||||
alipay = create_alipay_instance()
|
||||
|
||||
result = alipay.query_order(out_trade_no=order_no)
|
||||
|
||||
# 转换状态为统一格式
|
||||
if result.get('success'):
|
||||
trade_status = result.get('trade_status')
|
||||
# 映射支付宝状态到统一状态
|
||||
if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
|
||||
result['trade_state'] = 'SUCCESS'
|
||||
elif trade_status == 'WAIT_BUYER_PAY':
|
||||
result['trade_state'] = 'NOTPAY'
|
||||
elif trade_status == 'TRADE_CLOSED':
|
||||
result['trade_state'] = 'CLOSED'
|
||||
else:
|
||||
result['trade_state'] = trade_status
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
if len(sys.argv) < 2:
|
||||
print(json.dumps({
|
||||
'success': False,
|
||||
'error': '缺少命令参数。用法: check | create <order_no> <amount> <subject> | query <order_no>'
|
||||
}))
|
||||
sys.exit(1)
|
||||
|
||||
command = sys.argv[1].lower()
|
||||
|
||||
try:
|
||||
if command == 'check':
|
||||
result = check_config()
|
||||
|
||||
elif command == 'create':
|
||||
if len(sys.argv) < 5:
|
||||
result = {
|
||||
'success': False,
|
||||
'error': '创建订单需要参数: order_no, amount, subject'
|
||||
}
|
||||
else:
|
||||
order_no = sys.argv[2]
|
||||
amount = sys.argv[3]
|
||||
subject = sys.argv[4]
|
||||
body = sys.argv[5] if len(sys.argv) > 5 else None
|
||||
result = create_order(order_no, amount, subject, body)
|
||||
|
||||
elif command == 'query':
|
||||
if len(sys.argv) < 3:
|
||||
result = {
|
||||
'success': False,
|
||||
'error': '查询订单需要参数: order_no'
|
||||
}
|
||||
else:
|
||||
order_no = sys.argv[2]
|
||||
result = query_order(order_no)
|
||||
|
||||
else:
|
||||
result = {
|
||||
'success': False,
|
||||
'error': f'未知命令: {command}'
|
||||
}
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
|
||||
except Exception as e:
|
||||
print(json.dumps({
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}, ensure_ascii=False))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
455
app.py
455
app.py
@@ -1063,9 +1063,12 @@ class PaymentOrder(db.Model):
|
||||
original_amount = db.Column(db.Numeric(10, 2), nullable=True) # 原价
|
||||
discount_amount = db.Column(db.Numeric(10, 2), nullable=True, default=0) # 折扣金额
|
||||
promo_code_id = db.Column(db.Integer, db.ForeignKey('promo_codes.id'), nullable=True) # 优惠码ID
|
||||
wechat_order_id = db.Column(db.String(64), nullable=True)
|
||||
payment_method = db.Column(db.String(20), default='wechat') # 支付方式: wechat/alipay
|
||||
wechat_order_id = db.Column(db.String(64), nullable=True) # 微信交易号
|
||||
alipay_trade_no = db.Column(db.String(64), nullable=True) # 支付宝交易号
|
||||
prepay_id = db.Column(db.String(64), nullable=True)
|
||||
qr_code_url = db.Column(db.String(200), nullable=True)
|
||||
qr_code_url = db.Column(db.String(200), nullable=True) # 微信支付二维码URL
|
||||
pay_url = db.Column(db.String(2000), nullable=True) # 支付宝支付链接(较长)
|
||||
status = db.Column(db.String(20), default='pending')
|
||||
created_at = db.Column(db.DateTime, default=beijing_now)
|
||||
paid_at = db.Column(db.DateTime, nullable=True)
|
||||
@@ -1097,10 +1100,25 @@ class PaymentOrder(db.Model):
|
||||
except Exception as e:
|
||||
return False
|
||||
|
||||
def mark_as_paid(self, wechat_order_id, transaction_id=None):
|
||||
def mark_as_paid(self, transaction_id, payment_method=None):
|
||||
"""
|
||||
标记订单为已支付
|
||||
|
||||
Args:
|
||||
transaction_id: 交易号(微信或支付宝)
|
||||
payment_method: 支付方式(可选,如果已设置则不覆盖)
|
||||
"""
|
||||
self.status = 'paid'
|
||||
self.paid_at = beijing_now()
|
||||
self.wechat_order_id = wechat_order_id
|
||||
|
||||
# 根据支付方式存储交易号
|
||||
if payment_method:
|
||||
self.payment_method = payment_method
|
||||
|
||||
if self.payment_method == 'alipay':
|
||||
self.alipay_trade_no = transaction_id
|
||||
else:
|
||||
self.wechat_order_id = transaction_id
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
@@ -1113,7 +1131,9 @@ class PaymentOrder(db.Model):
|
||||
'original_amount': float(self.original_amount) if self.original_amount else None,
|
||||
'discount_amount': float(self.discount_amount) if self.discount_amount else 0,
|
||||
'promo_code': self.promo_code.code if self.promo_code else None,
|
||||
'payment_method': self.payment_method or 'wechat',
|
||||
'qr_code_url': self.qr_code_url,
|
||||
'pay_url': self.pay_url,
|
||||
'status': self.status,
|
||||
'is_expired': self.is_expired(),
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
@@ -2645,6 +2665,433 @@ def _parse_xml_callback(xml_data):
|
||||
return None
|
||||
|
||||
|
||||
# ========================================
|
||||
# 支付宝支付相关API
|
||||
# ========================================
|
||||
|
||||
@app.route('/api/payment/alipay/create-order', methods=['POST'])
|
||||
def create_alipay_order():
|
||||
"""
|
||||
创建支付宝支付订单
|
||||
|
||||
Request Body:
|
||||
{
|
||||
"plan_name": "pro",
|
||||
"billing_cycle": "yearly",
|
||||
"promo_code": "WELCOME2025" // 可选
|
||||
}
|
||||
"""
|
||||
try:
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'success': False, 'error': '未登录'}), 401
|
||||
|
||||
data = request.get_json()
|
||||
plan_name = data.get('plan_name')
|
||||
billing_cycle = data.get('billing_cycle')
|
||||
promo_code = (data.get('promo_code') or '').strip() or None
|
||||
|
||||
if not plan_name or not billing_cycle:
|
||||
return jsonify({'success': False, 'error': '参数不完整'}), 400
|
||||
|
||||
# 使用简化价格计算
|
||||
price_result = calculate_subscription_price_simple(session['user_id'], plan_name, billing_cycle, promo_code)
|
||||
|
||||
if 'error' in price_result:
|
||||
return jsonify({'success': False, 'error': price_result['error']}), 400
|
||||
|
||||
amount = price_result['final_amount']
|
||||
subscription_type = price_result.get('subscription_type', 'new')
|
||||
|
||||
# 检查是否为免费升级
|
||||
if amount <= 0 and price_result.get('is_upgrade'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '当前剩余价值可直接免费升级,请使用免费升级功能',
|
||||
'should_free_upgrade': True,
|
||||
'price_info': price_result
|
||||
}), 400
|
||||
|
||||
# 创建订单
|
||||
try:
|
||||
original_amount = price_result.get('original_amount', amount)
|
||||
discount_amount = price_result.get('discount_amount', 0)
|
||||
|
||||
order = PaymentOrder(
|
||||
user_id=session['user_id'],
|
||||
plan_name=plan_name,
|
||||
billing_cycle=billing_cycle,
|
||||
amount=amount,
|
||||
original_amount=original_amount,
|
||||
discount_amount=discount_amount
|
||||
)
|
||||
|
||||
# 设置支付方式为支付宝
|
||||
order.payment_method = 'alipay'
|
||||
order.remark = f"{subscription_type}订阅" if subscription_type == 'renew' else "新购订阅"
|
||||
|
||||
# 关联优惠码
|
||||
if promo_code and price_result.get('promo_code'):
|
||||
promo_obj = PromoCode.query.filter_by(code=promo_code.upper()).first()
|
||||
if promo_obj:
|
||||
order.promo_code_id = promo_obj.id
|
||||
print(f"📦 订单关联优惠码: {promo_obj.code} (ID: {promo_obj.id})")
|
||||
|
||||
db.session.add(order)
|
||||
db.session.commit()
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': f'订单创建失败: {str(e)}'}), 500
|
||||
|
||||
# 调用支付宝支付API(使用 subprocess 绕过 eventlet DNS 问题)
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'alipay_pay_worker.py')
|
||||
|
||||
# 先检查配置
|
||||
check_result = subprocess.run(
|
||||
[sys.executable, script_path, 'check'],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
|
||||
if check_result.returncode != 0:
|
||||
check_data = json.loads(check_result.stdout) if check_result.stdout else {}
|
||||
error_msg = check_data.get('error', check_data.get('message', '支付宝配置错误'))
|
||||
order.remark = f"支付宝配置错误 - {error_msg}"
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'支付宝支付暂不可用: {error_msg}'
|
||||
}), 500
|
||||
|
||||
# 创建支付宝订单
|
||||
plan_display_name = f"{plan_name.upper()}版本-{billing_cycle}"
|
||||
subject = f"VFr-{plan_display_name}"
|
||||
body = f"价值前沿订阅服务-{plan_display_name}"
|
||||
|
||||
create_result = subprocess.run(
|
||||
[sys.executable, script_path, 'create', order.order_no, str(float(amount)), subject, body],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
|
||||
print(f"[支付宝] 创建订单返回: {create_result.stdout}")
|
||||
if create_result.stderr:
|
||||
print(f"[支付宝] 错误输出: {create_result.stderr}")
|
||||
|
||||
alipay_result = json.loads(create_result.stdout) if create_result.stdout else {'success': False, 'error': '无返回'}
|
||||
|
||||
if alipay_result.get('success'):
|
||||
# 获取支付宝返回的支付链接
|
||||
pay_url = alipay_result['pay_url']
|
||||
order.pay_url = pay_url
|
||||
order.remark = f"支付宝支付 - 订单已创建"
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': '订单创建成功'
|
||||
})
|
||||
else:
|
||||
order.remark = f"支付宝支付失败: {alipay_result.get('error')}"
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f"支付宝订单创建失败: {alipay_result.get('error')}"
|
||||
}), 500
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
order.remark = "支付宝支付超时"
|
||||
db.session.commit()
|
||||
return jsonify({'success': False, 'error': '支付宝支付超时'}), 500
|
||||
except json.JSONDecodeError as e:
|
||||
order.remark = f"支付宝返回解析失败: {str(e)}"
|
||||
db.session.commit()
|
||||
return jsonify({'success': False, 'error': '支付宝返回数据异常'}), 500
|
||||
except Exception as e:
|
||||
import traceback
|
||||
print(f"[支付宝] Exception: {e}")
|
||||
traceback.print_exc()
|
||||
order.remark = f"支付异常: {str(e)}"
|
||||
db.session.commit()
|
||||
return jsonify({'success': False, 'error': '支付异常'}), 500
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return jsonify({'success': False, 'error': '创建订单失败'}), 500
|
||||
|
||||
|
||||
@app.route('/api/payment/alipay/callback', methods=['POST'])
|
||||
def alipay_payment_callback():
|
||||
"""支付宝异步回调处理"""
|
||||
try:
|
||||
# 获取POST参数
|
||||
callback_params = request.form.to_dict()
|
||||
print(f"📥 收到支付宝支付回调: {callback_params}")
|
||||
|
||||
# 验证回调数据
|
||||
try:
|
||||
from alipay_pay import create_alipay_instance
|
||||
alipay = create_alipay_instance()
|
||||
verify_result = alipay.verify_callback(callback_params.copy())
|
||||
|
||||
if not verify_result['success']:
|
||||
print(f"❌ 支付宝回调签名验证失败: {verify_result['error']}")
|
||||
return 'fail'
|
||||
|
||||
callback_data = verify_result['data']
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 支付宝回调处理异常: {e}")
|
||||
return 'fail'
|
||||
|
||||
# 获取关键字段
|
||||
trade_status = callback_data.get('trade_status')
|
||||
out_trade_no = callback_data.get('out_trade_no') # 商户订单号
|
||||
trade_no = callback_data.get('trade_no') # 支付宝交易号
|
||||
total_amount = callback_data.get('total_amount')
|
||||
|
||||
print(f"📦 支付宝回调数据解析:")
|
||||
print(f" 交易状态: {trade_status}")
|
||||
print(f" 订单号: {out_trade_no}")
|
||||
print(f" 交易号: {trade_no}")
|
||||
print(f" 金额: {total_amount}")
|
||||
|
||||
if not out_trade_no:
|
||||
print("❌ 缺少订单号")
|
||||
return 'fail'
|
||||
|
||||
# 查找订单
|
||||
order = PaymentOrder.query.filter_by(order_no=out_trade_no).first()
|
||||
if not order:
|
||||
print(f"❌ 订单不存在: {out_trade_no}")
|
||||
return 'fail'
|
||||
|
||||
# 只处理交易成功的回调
|
||||
if trade_status in ['TRADE_SUCCESS', 'TRADE_FINISHED']:
|
||||
print(f"🎉 支付宝支付成功: 订单 {out_trade_no}")
|
||||
|
||||
# 检查订单是否已经处理过
|
||||
if order.status == 'paid':
|
||||
print(f"ℹ️ 订单已处理过: {out_trade_no}")
|
||||
return 'success'
|
||||
|
||||
# 更新订单状态
|
||||
old_status = order.status
|
||||
order.mark_as_paid(trade_no, 'alipay')
|
||||
print(f"📝 订单状态已更新: {old_status} -> paid")
|
||||
|
||||
# 激活用户订阅
|
||||
subscription = activate_user_subscription(order.user_id, order.plan_name, order.billing_cycle)
|
||||
|
||||
if subscription:
|
||||
print(f"✅ 用户订阅已激活: 用户{order.user_id}, 套餐{order.plan_name}")
|
||||
else:
|
||||
print(f"⚠️ 订阅激活失败,但订单已标记为已支付")
|
||||
|
||||
# 记录优惠码使用情况
|
||||
if order.promo_code_id:
|
||||
try:
|
||||
existing_usage = PromoCodeUsage.query.filter_by(order_id=order.id).first()
|
||||
|
||||
if not existing_usage:
|
||||
usage = PromoCodeUsage(
|
||||
promo_code_id=order.promo_code_id,
|
||||
user_id=order.user_id,
|
||||
order_id=order.id,
|
||||
original_amount=order.original_amount or order.amount,
|
||||
discount_amount=order.discount_amount or 0,
|
||||
final_amount=order.amount
|
||||
)
|
||||
db.session.add(usage)
|
||||
|
||||
promo = PromoCode.query.get(order.promo_code_id)
|
||||
if promo:
|
||||
promo.current_uses = (promo.current_uses or 0) + 1
|
||||
print(f"🎫 优惠码使用记录已创建: {promo.code}, 当前使用次数: {promo.current_uses}")
|
||||
else:
|
||||
print(f"ℹ️ 优惠码使用记录已存在,跳过")
|
||||
except Exception as e:
|
||||
print(f"⚠️ 记录优惠码使用失败: {e}")
|
||||
|
||||
db.session.commit()
|
||||
|
||||
elif trade_status == 'TRADE_CLOSED':
|
||||
# 交易关闭
|
||||
if order.status not in ['paid', 'cancelled']:
|
||||
order.status = 'cancelled'
|
||||
db.session.commit()
|
||||
print(f"📝 订单已关闭: {out_trade_no}")
|
||||
|
||||
# 返回成功响应给支付宝
|
||||
return 'success'
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"❌ 支付宝回调处理失败: {e}")
|
||||
import traceback
|
||||
app.logger.error(f"支付宝回调处理错误: {e}", exc_info=True)
|
||||
return 'fail'
|
||||
|
||||
|
||||
@app.route('/api/payment/alipay/return', methods=['GET'])
|
||||
def alipay_payment_return():
|
||||
"""支付宝同步返回处理(用户支付后跳转回来)"""
|
||||
try:
|
||||
# 获取GET参数
|
||||
return_params = request.args.to_dict()
|
||||
print(f"📥 支付宝同步返回: {return_params}")
|
||||
|
||||
out_trade_no = return_params.get('out_trade_no')
|
||||
|
||||
if out_trade_no:
|
||||
# 重定向到前端支付结果页面
|
||||
return redirect(f'/pricing?payment_return=alipay&order_no={out_trade_no}')
|
||||
else:
|
||||
return redirect('/pricing?payment_return=alipay&error=missing_order')
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 支付宝同步返回处理失败: {e}")
|
||||
return redirect('/pricing?payment_return=alipay&error=exception')
|
||||
|
||||
|
||||
@app.route('/api/payment/alipay/order/<order_id>/status', methods=['GET'])
|
||||
def check_alipay_order_status(order_id):
|
||||
"""查询支付宝订单支付状态"""
|
||||
try:
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'success': False, 'error': '未登录'}), 401
|
||||
|
||||
# 查找订单
|
||||
order = PaymentOrder.query.filter_by(
|
||||
id=order_id,
|
||||
user_id=session['user_id']
|
||||
).first()
|
||||
|
||||
if not order:
|
||||
return jsonify({'success': False, 'error': '订单不存在'}), 404
|
||||
|
||||
# 如果订单已经是已支付状态,直接返回
|
||||
if order.status == 'paid':
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': '订单已支付',
|
||||
'payment_success': True
|
||||
})
|
||||
|
||||
# 如果订单过期,标记为过期
|
||||
if order.is_expired():
|
||||
order.status = 'expired'
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': '订单已过期'
|
||||
})
|
||||
|
||||
# 调用支付宝API查询真实状态
|
||||
try:
|
||||
import subprocess
|
||||
|
||||
script_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'alipay_pay_worker.py')
|
||||
|
||||
query_proc = subprocess.run(
|
||||
[sys.executable, script_path, 'query', order.order_no],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
|
||||
query_result = json.loads(query_proc.stdout) if query_proc.stdout else {'success': False, 'error': '无返回'}
|
||||
|
||||
if query_result.get('success'):
|
||||
trade_state = query_result.get('trade_state')
|
||||
trade_no = query_result.get('trade_no')
|
||||
|
||||
if trade_state == 'SUCCESS':
|
||||
# 支付成功,更新订单状态
|
||||
order.mark_as_paid(trade_no, 'alipay')
|
||||
|
||||
# 激活用户订阅
|
||||
activate_user_subscription(order.user_id, order.plan_name, order.billing_cycle)
|
||||
|
||||
# 记录优惠码使用情况
|
||||
if order.promo_code_id:
|
||||
try:
|
||||
existing_usage = PromoCodeUsage.query.filter_by(order_id=order.id).first()
|
||||
if not existing_usage:
|
||||
usage = PromoCodeUsage(
|
||||
promo_code_id=order.promo_code_id,
|
||||
user_id=order.user_id,
|
||||
order_id=order.id,
|
||||
original_amount=order.original_amount or order.amount,
|
||||
discount_amount=order.discount_amount or 0,
|
||||
final_amount=order.amount
|
||||
)
|
||||
db.session.add(usage)
|
||||
promo = PromoCode.query.get(order.promo_code_id)
|
||||
if promo:
|
||||
promo.current_uses = (promo.current_uses or 0) + 1
|
||||
print(f"🎫 优惠码使用记录已创建: {promo.code}")
|
||||
except Exception as e:
|
||||
print(f"⚠️ 记录优惠码使用失败: {e}")
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': '支付成功!订阅已激活',
|
||||
'payment_success': True
|
||||
})
|
||||
elif trade_state in ['NOTPAY', 'WAIT_BUYER_PAY']:
|
||||
# 未支付或等待支付
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': '等待支付...',
|
||||
'payment_success': False
|
||||
})
|
||||
elif trade_state in ['CLOSED', 'TRADE_CLOSED']:
|
||||
# 交易关闭
|
||||
order.status = 'cancelled'
|
||||
db.session.commit()
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': '交易已关闭',
|
||||
'payment_success': False
|
||||
})
|
||||
else:
|
||||
# 其他状态
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': f'当前状态: {trade_state}',
|
||||
'payment_success': False
|
||||
})
|
||||
else:
|
||||
# 支付宝查询失败,返回当前状态
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': f"查询失败: {query_result.get('error')}",
|
||||
'payment_success': False
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
# 查询失败,返回当前订单状态
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': order.to_dict(),
|
||||
'message': '无法查询支付状态,请稍后重试',
|
||||
'payment_success': False
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': '查询失败'}), 500
|
||||
|
||||
|
||||
@app.route('/api/auth/session', methods=['GET'])
|
||||
def get_session_info():
|
||||
"""获取当前登录用户信息"""
|
||||
|
||||
@@ -496,7 +496,7 @@ export default function WechatRegister() {
|
||||
width="300"
|
||||
height="350"
|
||||
scrolling="no"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation"
|
||||
sandbox="allow-scripts allow-same-origin allow-forms allow-popups"
|
||||
allow="clipboard-write"
|
||||
style={{
|
||||
border: 'none',
|
||||
|
||||
Reference in New Issue
Block a user