update pay ui
This commit is contained in:
@@ -228,6 +228,78 @@ class AlipayPay:
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def create_wap_pay_url(self, out_trade_no, total_amount, subject, body=None, timeout_express='30m', quit_url=None):
|
||||
"""
|
||||
创建手机网站支付URL (H5支付,可调起手机支付宝APP)
|
||||
|
||||
Args:
|
||||
out_trade_no: 商户订单号
|
||||
total_amount: 订单总金额(元,精确到小数点后两位)
|
||||
subject: 订单标题
|
||||
body: 订单描述(可选)
|
||||
timeout_express: 订单超时时间,默认30分钟
|
||||
quit_url: 用户付款中途退出返回商户网站的地址(可选)
|
||||
|
||||
Returns:
|
||||
dict: 包含支付URL的响应
|
||||
"""
|
||||
try:
|
||||
# 金额格式化为两位小数(支付宝要求)
|
||||
formatted_amount = f"{float(total_amount):.2f}"
|
||||
|
||||
# 业务参数
|
||||
biz_content = {
|
||||
'out_trade_no': out_trade_no,
|
||||
'total_amount': formatted_amount,
|
||||
'subject': subject,
|
||||
'product_code': 'QUICK_WAP_WAY', # 手机网站支付固定值
|
||||
'timeout_express': timeout_express,
|
||||
}
|
||||
if body:
|
||||
biz_content['body'] = body
|
||||
if quit_url:
|
||||
biz_content['quit_url'] = quit_url
|
||||
|
||||
# 公共请求参数
|
||||
params = {
|
||||
'app_id': self.app_id,
|
||||
'method': 'alipay.trade.wap.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)}"
|
||||
|
||||
# 日志输出到 stderr,避免影响 subprocess JSON 输出
|
||||
import sys
|
||||
print(f"[AlipayPay] WAP Order created: {out_trade_no}, amount: {formatted_amount}", file=sys.stderr)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'pay_url': pay_url,
|
||||
'order_no': out_trade_no,
|
||||
'pay_type': 'wap' # 标识为手机网站支付
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
import sys
|
||||
print(f"[AlipayPay] Create WAP order failed: {e}", file=sys.stderr)
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def query_order(self, out_trade_no=None, trade_no=None):
|
||||
"""
|
||||
查询订单状态
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
|
||||
用法:
|
||||
python alipay_pay_worker.py check # 检查配置
|
||||
python alipay_pay_worker.py create <order_no> <amount> <subject> [body] # 创建订单
|
||||
python alipay_pay_worker.py create <order_no> <amount> <subject> [body] [pay_type] # 创建订单
|
||||
pay_type: page=电脑网站支付(默认), wap=手机网站支付
|
||||
python alipay_pay_worker.py query <order_no> # 查询订单
|
||||
"""
|
||||
|
||||
@@ -31,19 +32,39 @@ def check_config():
|
||||
}
|
||||
|
||||
|
||||
def create_order(order_no, amount, subject, body=None):
|
||||
"""创建支付宝订单"""
|
||||
def create_order(order_no, amount, subject, body=None, pay_type='page'):
|
||||
"""创建支付宝订单
|
||||
|
||||
Args:
|
||||
order_no: 订单号
|
||||
amount: 金额
|
||||
subject: 标题
|
||||
body: 描述
|
||||
pay_type: 支付类型 'page'=电脑网站支付, 'wap'=手机网站支付
|
||||
"""
|
||||
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'
|
||||
)
|
||||
if pay_type == 'wap':
|
||||
# 手机网站支付
|
||||
result = alipay.create_wap_pay_url(
|
||||
out_trade_no=order_no,
|
||||
total_amount=str(amount),
|
||||
subject=subject,
|
||||
body=body,
|
||||
timeout_express='30m',
|
||||
quit_url='https://valuefrontier.cn/pricing' # 用户取消支付时返回的页面
|
||||
)
|
||||
else:
|
||||
# 电脑网站支付(默认)
|
||||
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:
|
||||
@@ -108,7 +129,9 @@ def main():
|
||||
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)
|
||||
# pay_type: page=电脑网站支付, wap=手机网站支付
|
||||
pay_type = sys.argv[6] if len(sys.argv) > 6 else 'page'
|
||||
result = create_order(order_no, amount, subject, body, pay_type)
|
||||
|
||||
elif command == 'query':
|
||||
if len(sys.argv) < 3:
|
||||
|
||||
34
app.py
34
app.py
@@ -2687,7 +2687,8 @@ def create_alipay_order():
|
||||
{
|
||||
"plan_name": "pro",
|
||||
"billing_cycle": "yearly",
|
||||
"promo_code": "WELCOME2025" // 可选
|
||||
"promo_code": "WELCOME2025", // 可选
|
||||
"is_mobile": true // 可选,是否为手机端(自动使用 WAP 支付)
|
||||
}
|
||||
"""
|
||||
try:
|
||||
@@ -2698,6 +2699,8 @@ def create_alipay_order():
|
||||
plan_name = data.get('plan_name')
|
||||
billing_cycle = data.get('billing_cycle')
|
||||
promo_code = (data.get('promo_code') or '').strip() or None
|
||||
# 前端传入的设备类型,用于决定使用 page 支付还是 wap 支付
|
||||
is_mobile = data.get('is_mobile', False)
|
||||
|
||||
if not plan_name or not billing_cycle:
|
||||
return jsonify({'success': False, 'error': '参数不完整'}), 400
|
||||
@@ -2782,8 +2785,12 @@ def create_alipay_order():
|
||||
# 金额格式化为两位小数(支付宝要求)
|
||||
amount_str = f"{float(amount):.2f}"
|
||||
|
||||
# 根据设备类型选择支付方式:wap=手机网站支付,page=电脑网站支付
|
||||
pay_type = 'wap' if is_mobile else 'page'
|
||||
print(f"[支付宝] 设备类型: {'手机' if is_mobile else '电脑'}, 支付方式: {pay_type}")
|
||||
|
||||
create_result = subprocess.run(
|
||||
[sys.executable, script_path, 'create', order.order_no, amount_str, subject, body],
|
||||
[sys.executable, script_path, 'create', order.order_no, amount_str, subject, body, pay_type],
|
||||
capture_output=True, text=True, timeout=60
|
||||
)
|
||||
|
||||
@@ -3104,6 +3111,29 @@ def check_alipay_order_status(order_id):
|
||||
return jsonify({'success': False, 'error': '查询失败'}), 500
|
||||
|
||||
|
||||
@app.route('/api/payment/alipay/order-by-no/<order_no>/status', methods=['GET'])
|
||||
def check_alipay_order_status_by_no(order_no):
|
||||
"""通过订单号查询支付宝订单支付状态(用于手机端支付返回)"""
|
||||
try:
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'success': False, 'error': '未登录'}), 401
|
||||
|
||||
# 通过订单号查找订单
|
||||
order = PaymentOrder.query.filter_by(
|
||||
order_no=order_no,
|
||||
user_id=session['user_id']
|
||||
).first()
|
||||
|
||||
if not order:
|
||||
return jsonify({'success': False, 'error': '订单不存在'}), 404
|
||||
|
||||
# 复用现有的状态检查逻辑
|
||||
return check_alipay_order_status(str(order.id))
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({'success': False, 'error': '查询失败'}), 500
|
||||
|
||||
|
||||
@app.route('/api/auth/session', methods=['GET'])
|
||||
def get_session_info():
|
||||
"""获取当前登录用户信息"""
|
||||
|
||||
@@ -185,6 +185,79 @@ export default function SubscriptionContentNew() {
|
||||
fetchSubscriptionPlans();
|
||||
}, []);
|
||||
|
||||
// 检查是否从支付宝支付返回(手机端支付完成后会跳转回来)
|
||||
useEffect(() => {
|
||||
const checkAlipayReturn = async () => {
|
||||
// 检查 URL 参数是否包含支付宝返回标记
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const paymentReturn = urlParams.get('payment_return');
|
||||
// 支付宝返回的参数是 out_trade_no,后端重定向时会转成 order_no
|
||||
const orderNo = urlParams.get('order_no') || urlParams.get('out_trade_no');
|
||||
|
||||
if (paymentReturn === 'alipay' && orderNo) {
|
||||
// 从支付宝返回,检查支付状态
|
||||
toast({
|
||||
title: '正在确认支付结果...',
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
try {
|
||||
// 优先使用 sessionStorage 中的 orderId,否则使用 order_no 查询
|
||||
const orderId = sessionStorage.getItem('alipay_order_id');
|
||||
const statusUrl = orderId
|
||||
? `/api/payment/alipay/order/${orderId}/status`
|
||||
: `/api/payment/alipay/order-by-no/${orderNo}/status`;
|
||||
|
||||
const response = await fetch(statusUrl, {
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success && (data.data?.status === 'paid' || data.payment_success)) {
|
||||
toast({
|
||||
title: '支付成功!',
|
||||
description: '您的订阅已激活',
|
||||
status: 'success',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
// 清理 sessionStorage
|
||||
sessionStorage.removeItem('alipay_order_id');
|
||||
sessionStorage.removeItem('alipay_order_no');
|
||||
|
||||
// 清除 URL 参数并刷新页面
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
} else {
|
||||
toast({
|
||||
title: '支付状态待确认',
|
||||
description: '如已完成支付,请稍候或刷新页面',
|
||||
status: 'warning',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
// 清除 URL 参数
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
}
|
||||
} else {
|
||||
// 清除 URL 参数
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('SubscriptionContentNew', 'checkAlipayReturn', error);
|
||||
// 清除 URL 参数
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
checkAlipayReturn();
|
||||
}, [toast]);
|
||||
|
||||
const fetchSubscriptionPlans = async () => {
|
||||
try {
|
||||
logger.debug('SubscriptionContentNew', '正在获取订阅套餐');
|
||||
@@ -409,6 +482,9 @@ export default function SubscriptionContentNew() {
|
||||
? '/api/payment/alipay/create-order'
|
||||
: '/api/payment/create-order';
|
||||
|
||||
// 检测是否为移动端设备
|
||||
const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -419,6 +495,7 @@ export default function SubscriptionContentNew() {
|
||||
plan_name: selectedPlan.name,
|
||||
billing_cycle: selectedCycle,
|
||||
promo_code: promoCodeApplied ? promoCode : null,
|
||||
is_mobile: isMobileDevice, // 传递设备类型,用于支付宝选择 page/wap 支付
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -428,22 +505,46 @@ export default function SubscriptionContentNew() {
|
||||
if (paymentMethod === 'alipay') {
|
||||
// 支付宝:跳转到支付页面
|
||||
if (data.data.pay_url) {
|
||||
setPaymentOrder(data.data);
|
||||
setPaymentCountdown(30 * 60);
|
||||
startAutoPaymentCheck(data.data.id, 'alipay');
|
||||
// 检测是否为移动端设备
|
||||
const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
toast({
|
||||
title: '订单创建成功',
|
||||
description: '正在跳转到支付宝支付页面...',
|
||||
status: 'success',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
if (isMobileDevice) {
|
||||
// 手机端:直接在当前页面跳转(可调起支付宝APP)
|
||||
toast({
|
||||
title: '订单创建成功',
|
||||
description: '正在跳转到支付宝...',
|
||||
status: 'success',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
// 延迟跳转,让用户看到提示
|
||||
setTimeout(() => {
|
||||
window.open(data.data.pay_url, '_blank');
|
||||
}, 500);
|
||||
// 保存订单信息到 sessionStorage,支付完成后返回时可以用来检查状态
|
||||
sessionStorage.setItem('alipay_order_id', data.data.id);
|
||||
sessionStorage.setItem('alipay_order_no', data.data.order_no);
|
||||
|
||||
// 延迟跳转,让用户看到提示
|
||||
setTimeout(() => {
|
||||
window.location.href = data.data.pay_url;
|
||||
}, 500);
|
||||
} else {
|
||||
// PC端:新窗口打开
|
||||
setPaymentOrder(data.data);
|
||||
setPaymentCountdown(30 * 60);
|
||||
startAutoPaymentCheck(data.data.id, 'alipay');
|
||||
|
||||
toast({
|
||||
title: '订单创建成功',
|
||||
description: '正在跳转到支付宝支付页面...',
|
||||
status: 'success',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
// 延迟跳转,让用户看到提示
|
||||
setTimeout(() => {
|
||||
window.open(data.data.pay_url, '_blank');
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
throw new Error('支付链接获取失败');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user