更新ios
This commit is contained in:
431
app.py
431
app.py
@@ -42,10 +42,12 @@ else:
|
||||
|
||||
import base64
|
||||
import csv
|
||||
import hashlib
|
||||
import io
|
||||
import threading
|
||||
import time
|
||||
import urllib
|
||||
from urllib.parse import quote
|
||||
import uuid
|
||||
from functools import wraps
|
||||
import qrcode
|
||||
@@ -3646,6 +3648,435 @@ def _parse_xml_callback(xml_data):
|
||||
return None
|
||||
|
||||
|
||||
# ========================================
|
||||
# 微信 H5 JSAPI 支付相关 API
|
||||
# ========================================
|
||||
|
||||
@app.route('/api/payment/wechat/h5/auth-url', methods=['GET'])
|
||||
def get_wechat_h5_auth_url():
|
||||
"""
|
||||
获取微信 H5 网页授权 URL
|
||||
|
||||
用于在微信内置浏览器中获取用户 openid,以便发起 JSAPI 支付
|
||||
|
||||
Query Parameters:
|
||||
plan_name: 套餐名称
|
||||
billing_cycle: 计费周期
|
||||
promo_code: 优惠码(可选)
|
||||
|
||||
Returns:
|
||||
{ "success": true, "auth_url": "https://open.weixin.qq.com/..." }
|
||||
"""
|
||||
try:
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'success': False, 'error': '未登录'}), 401
|
||||
|
||||
plan_name = request.args.get('plan_name')
|
||||
billing_cycle = request.args.get('billing_cycle')
|
||||
promo_code = request.args.get('promo_code', '')
|
||||
|
||||
if not plan_name or not billing_cycle:
|
||||
return jsonify({'success': False, 'error': '参数不完整'}), 400
|
||||
|
||||
# 构建 state 参数(包含订单信息,用于回调后恢复)
|
||||
import base64
|
||||
state_data = {
|
||||
'user_id': session['user_id'],
|
||||
'plan_name': plan_name,
|
||||
'billing_cycle': billing_cycle,
|
||||
'promo_code': promo_code,
|
||||
'timestamp': int(time.time())
|
||||
}
|
||||
state = base64.urlsafe_b64encode(json.dumps(state_data).encode()).decode()
|
||||
|
||||
# 回调地址
|
||||
redirect_uri = quote('https://api.valuefrontier.cn/api/payment/wechat/h5/callback')
|
||||
|
||||
# 构建微信授权 URL(使用 snsapi_base 静默授权,只获取 openid)
|
||||
auth_url = (
|
||||
f"https://open.weixin.qq.com/connect/oauth2/authorize?"
|
||||
f"appid={WECHAT_MP_APPID}"
|
||||
f"&redirect_uri={redirect_uri}"
|
||||
f"&response_type=code"
|
||||
f"&scope=snsapi_base"
|
||||
f"&state={state}"
|
||||
f"#wechat_redirect"
|
||||
)
|
||||
|
||||
print(f"[微信H5支付] 生成授权URL: user_id={session['user_id']}, plan={plan_name}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'auth_url': auth_url
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"[微信H5支付] 生成授权URL失败: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/payment/wechat/h5/callback', methods=['GET'])
|
||||
def wechat_h5_payment_callback():
|
||||
"""
|
||||
微信 H5 网页授权回调
|
||||
|
||||
微信授权后会携带 code 和 state 回调到此接口
|
||||
获取 openid 后重定向到前端支付页面
|
||||
"""
|
||||
try:
|
||||
code = request.args.get('code')
|
||||
state = request.args.get('state')
|
||||
|
||||
if not code:
|
||||
print("[微信H5支付] 回调缺少 code 参数")
|
||||
return redirect('https://valuefrontier.cn/home/pages/account/subscription?wechat_pay_error=missing_code')
|
||||
|
||||
# 解析 state
|
||||
try:
|
||||
import base64
|
||||
state_data = json.loads(base64.urlsafe_b64decode(state).decode())
|
||||
user_id = state_data.get('user_id')
|
||||
plan_name = state_data.get('plan_name')
|
||||
billing_cycle = state_data.get('billing_cycle')
|
||||
promo_code = state_data.get('promo_code', '')
|
||||
except Exception as e:
|
||||
print(f"[微信H5支付] 解析 state 失败: {e}")
|
||||
return redirect('https://valuefrontier.cn/home/pages/account/subscription?wechat_pay_error=invalid_state')
|
||||
|
||||
# 使用 code 换取 openid
|
||||
token_url = "https://api.weixin.qq.com/sns/oauth2/access_token"
|
||||
params = {
|
||||
'appid': WECHAT_MP_APPID,
|
||||
'secret': WECHAT_MP_APPSECRET,
|
||||
'code': code,
|
||||
'grant_type': 'authorization_code'
|
||||
}
|
||||
|
||||
response = requests.get(token_url, params=params, timeout=10)
|
||||
result = response.json()
|
||||
|
||||
if 'openid' not in result:
|
||||
print(f"[微信H5支付] 获取 openid 失败: {result}")
|
||||
error_msg = result.get('errmsg', 'unknown_error')
|
||||
return redirect(f'https://valuefrontier.cn/home/pages/account/subscription?wechat_pay_error={error_msg}')
|
||||
|
||||
openid = result['openid']
|
||||
print(f"[微信H5支付] 获取 openid 成功: {openid[:10]}...")
|
||||
|
||||
# 将 openid 加密存储,避免在 URL 中暴露
|
||||
# 使用简单的 base64 + 时间戳校验
|
||||
openid_token_data = {
|
||||
'openid': openid,
|
||||
'user_id': user_id,
|
||||
'timestamp': int(time.time()),
|
||||
'plan_name': plan_name,
|
||||
'billing_cycle': billing_cycle,
|
||||
'promo_code': promo_code
|
||||
}
|
||||
openid_token = base64.urlsafe_b64encode(json.dumps(openid_token_data).encode()).decode()
|
||||
|
||||
# 重定向到前端支付页面
|
||||
redirect_url = (
|
||||
f"https://valuefrontier.cn/home/pages/account/subscription"
|
||||
f"?wechat_h5_pay=ready"
|
||||
f"&token={openid_token}"
|
||||
)
|
||||
|
||||
print(f"[微信H5支付] 重定向到前端: {redirect_url[:80]}...")
|
||||
return redirect(redirect_url)
|
||||
|
||||
except Exception as e:
|
||||
print(f"[微信H5支付] 回调处理失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return redirect(f'https://valuefrontier.cn/home/pages/account/subscription?wechat_pay_error=callback_failed')
|
||||
|
||||
|
||||
@app.route('/api/payment/wechat/jsapi/create-order', methods=['POST'])
|
||||
def create_wechat_jsapi_order():
|
||||
"""
|
||||
创建微信 JSAPI 支付订单
|
||||
|
||||
用于微信内置浏览器中的 H5 支付
|
||||
|
||||
Request Body:
|
||||
{
|
||||
"token": "xxx", // 从 OAuth 回调获取的 token(包含 openid 等信息)
|
||||
}
|
||||
|
||||
Returns:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"order_id": "xxx",
|
||||
"order_no": "xxx",
|
||||
"amount": 99.00,
|
||||
"payment_params": {
|
||||
"appId": "xxx",
|
||||
"timeStamp": "xxx",
|
||||
"nonceStr": "xxx",
|
||||
"package": "prepay_id=xxx",
|
||||
"signType": "MD5",
|
||||
"paySign": "xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
token = data.get('token')
|
||||
|
||||
if not token:
|
||||
return jsonify({'success': False, 'error': '缺少 token 参数'}), 400
|
||||
|
||||
# 解析 token
|
||||
try:
|
||||
import base64
|
||||
token_data = json.loads(base64.urlsafe_b64decode(token).decode())
|
||||
openid = token_data.get('openid')
|
||||
user_id = token_data.get('user_id')
|
||||
plan_name = token_data.get('plan_name')
|
||||
billing_cycle = token_data.get('billing_cycle')
|
||||
promo_code = token_data.get('promo_code', '') or None
|
||||
timestamp = token_data.get('timestamp', 0)
|
||||
except Exception as e:
|
||||
print(f"[JSAPI支付] 解析 token 失败: {e}")
|
||||
return jsonify({'success': False, 'error': 'token 无效'}), 400
|
||||
|
||||
# 验证 token 时效(30分钟内有效)
|
||||
if time.time() - timestamp > 1800:
|
||||
return jsonify({'success': False, 'error': 'token 已过期,请重新授权'}), 400
|
||||
|
||||
# 验证用户登录状态
|
||||
if 'user_id' not in session or session['user_id'] != user_id:
|
||||
return jsonify({'success': False, 'error': '用户状态异常,请重新登录'}), 401
|
||||
|
||||
print(f"[JSAPI支付] 创建订单: user_id={user_id}, plan={plan_name}, openid={openid[:10]}...")
|
||||
|
||||
# 计算价格
|
||||
price_result = calculate_subscription_price_simple(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']
|
||||
|
||||
# 检查是否为免费升级
|
||||
if amount <= 0 and price_result.get('is_upgrade'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': '当前剩余价值可直接免费升级',
|
||||
'should_free_upgrade': True
|
||||
}), 400
|
||||
|
||||
# 生成订单号
|
||||
order_no = f"WXH5{int(time.time())}{user_id}{uuid.uuid4().hex[:6].upper()}"
|
||||
|
||||
# 创建订单记录
|
||||
order = PaymentOrder(
|
||||
user_id=user_id,
|
||||
order_no=order_no,
|
||||
plan_name=plan_name,
|
||||
billing_cycle=billing_cycle,
|
||||
amount=amount,
|
||||
original_amount=price_result.get('original_price', amount),
|
||||
discount_amount=price_result.get('discount_amount', 0),
|
||||
status='pending',
|
||||
payment_method='wechat_jsapi',
|
||||
payment_source='h5'
|
||||
)
|
||||
|
||||
# 关联优惠码
|
||||
if promo_code and price_result.get('promo_applied'):
|
||||
promo = PromoCode.query.filter_by(code=promo_code.upper()).first()
|
||||
if promo:
|
||||
order.promo_code_id = promo.id
|
||||
|
||||
db.session.add(order)
|
||||
db.session.commit()
|
||||
|
||||
# 调用微信统一下单接口(JSAPI 模式)
|
||||
jsapi_result = _create_wechat_jsapi_prepay(order_no, amount, plan_name, openid)
|
||||
|
||||
if not jsapi_result['success']:
|
||||
# 标记订单失败
|
||||
order.status = 'failed'
|
||||
order.remark = jsapi_result.get('error', '创建预支付订单失败')
|
||||
db.session.commit()
|
||||
return jsonify({'success': False, 'error': jsapi_result.get('error', '创建支付订单失败')}), 500
|
||||
|
||||
# 更新订单的 prepay_id
|
||||
order.prepay_id = jsapi_result.get('prepay_id')
|
||||
db.session.commit()
|
||||
|
||||
print(f"[JSAPI支付] 订单创建成功: order_no={order_no}, amount={amount}")
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': {
|
||||
'order_id': order.id,
|
||||
'order_no': order_no,
|
||||
'amount': amount,
|
||||
'plan_name': plan_name,
|
||||
'payment_params': jsapi_result['payment_params']
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
print(f"[JSAPI支付] 创建订单失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'error': f'创建订单失败: {str(e)}'}), 500
|
||||
|
||||
|
||||
def _create_wechat_jsapi_prepay(order_no, amount, body, openid):
|
||||
"""
|
||||
调用微信统一下单接口创建 JSAPI 预支付订单
|
||||
|
||||
Args:
|
||||
order_no: 商户订单号
|
||||
amount: 金额(元)
|
||||
body: 商品描述
|
||||
openid: 用户的 openid
|
||||
|
||||
Returns:
|
||||
{
|
||||
'success': True/False,
|
||||
'prepay_id': 'xxx',
|
||||
'payment_params': {...} # 前端调用支付需要的参数
|
||||
}
|
||||
"""
|
||||
try:
|
||||
from wechat_pay_config import WECHAT_PAY_CONFIG
|
||||
|
||||
# 金额转换为分
|
||||
total_fee = int(amount * 100)
|
||||
|
||||
# 构建请求参数
|
||||
params = {
|
||||
'appid': WECHAT_MP_APPID, # 使用服务号 AppID
|
||||
'mch_id': WECHAT_PAY_CONFIG['mch_id'],
|
||||
'nonce_str': uuid.uuid4().hex,
|
||||
'body': f'价值前研-{body}订阅',
|
||||
'out_trade_no': order_no,
|
||||
'total_fee': str(total_fee),
|
||||
'spbill_create_ip': request.remote_addr or '127.0.0.1',
|
||||
'notify_url': WECHAT_PAY_CONFIG['notify_url'],
|
||||
'trade_type': 'JSAPI',
|
||||
'openid': openid
|
||||
}
|
||||
|
||||
# 生成签名
|
||||
api_key = WECHAT_PAY_CONFIG['api_key']
|
||||
sign = _generate_wechat_sign(params, api_key)
|
||||
params['sign'] = sign
|
||||
|
||||
# 转换为 XML
|
||||
xml_data = _dict_to_xml(params)
|
||||
|
||||
print(f"[JSAPI支付] 统一下单请求: order_no={order_no}, amount={amount}元={total_fee}分")
|
||||
|
||||
# 发送请求
|
||||
response = requests.post(
|
||||
'https://api.mch.weixin.qq.com/pay/unifiedorder',
|
||||
data=xml_data.encode('utf-8'),
|
||||
headers={'Content-Type': 'text/xml'},
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# 解析响应
|
||||
result = _xml_to_dict(response.text)
|
||||
print(f"[JSAPI支付] 统一下单响应: {result}")
|
||||
|
||||
if result.get('return_code') != 'SUCCESS':
|
||||
error_msg = result.get('return_msg', '通信失败')
|
||||
print(f"[JSAPI支付] 统一下单失败: {error_msg}")
|
||||
return {'success': False, 'error': error_msg}
|
||||
|
||||
if result.get('result_code') != 'SUCCESS':
|
||||
error_msg = result.get('err_code_des') or result.get('err_code', '业务失败')
|
||||
print(f"[JSAPI支付] 统一下单业务失败: {error_msg}")
|
||||
return {'success': False, 'error': error_msg}
|
||||
|
||||
prepay_id = result.get('prepay_id')
|
||||
if not prepay_id:
|
||||
return {'success': False, 'error': '未获取到 prepay_id'}
|
||||
|
||||
# 生成前端调用支付需要的参数
|
||||
timestamp = str(int(time.time()))
|
||||
nonce_str = uuid.uuid4().hex
|
||||
package = f"prepay_id={prepay_id}"
|
||||
|
||||
# 签名参数(注意:这里的 appId 是大写 I)
|
||||
sign_params = {
|
||||
'appId': WECHAT_MP_APPID,
|
||||
'timeStamp': timestamp,
|
||||
'nonceStr': nonce_str,
|
||||
'package': package,
|
||||
'signType': 'MD5'
|
||||
}
|
||||
pay_sign = _generate_wechat_sign(sign_params, api_key)
|
||||
|
||||
payment_params = {
|
||||
'appId': WECHAT_MP_APPID,
|
||||
'timeStamp': timestamp,
|
||||
'nonceStr': nonce_str,
|
||||
'package': package,
|
||||
'signType': 'MD5',
|
||||
'paySign': pay_sign
|
||||
}
|
||||
|
||||
print(f"[JSAPI支付] 支付参数生成成功: prepay_id={prepay_id[:20]}...")
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'prepay_id': prepay_id,
|
||||
'payment_params': payment_params
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"[JSAPI支付] 统一下单异常: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {'success': False, 'error': str(e)}
|
||||
|
||||
|
||||
def _generate_wechat_sign(params, api_key):
|
||||
"""生成微信支付签名"""
|
||||
# 过滤空值并排序
|
||||
filtered = {k: v for k, v in params.items() if v and k != 'sign'}
|
||||
sorted_params = sorted(filtered.items())
|
||||
|
||||
# 拼接字符串
|
||||
string_a = "&".join([f"{k}={v}" for k, v in sorted_params])
|
||||
string_sign_temp = f"{string_a}&key={api_key}"
|
||||
|
||||
# MD5 签名
|
||||
sign = hashlib.md5(string_sign_temp.encode('utf-8')).hexdigest().upper()
|
||||
return sign
|
||||
|
||||
|
||||
def _dict_to_xml(data):
|
||||
"""将字典转换为 XML"""
|
||||
xml = ["<xml>"]
|
||||
for key, value in data.items():
|
||||
xml.append(f"<{key}><![CDATA[{value}]]></{key}>")
|
||||
xml.append("</xml>")
|
||||
return "".join(xml)
|
||||
|
||||
|
||||
def _xml_to_dict(xml_data):
|
||||
"""将 XML 转换为字典"""
|
||||
import xml.etree.ElementTree as ET
|
||||
try:
|
||||
root = ET.fromstring(xml_data)
|
||||
return {child.tag: child.text for child in root}
|
||||
except Exception as e:
|
||||
print(f"XML 解析失败: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
# ========================================
|
||||
# 支付宝支付相关API
|
||||
# ========================================
|
||||
|
||||
@@ -270,6 +270,130 @@ export default function SubscriptionContentNew() {
|
||||
checkAlipayReturn();
|
||||
}, [toast]);
|
||||
|
||||
// 检查是否从微信 H5 支付授权返回
|
||||
useEffect(() => {
|
||||
const handleWechatH5PayReturn = async () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const wechatH5Pay = urlParams.get('wechat_h5_pay');
|
||||
const token = urlParams.get('token');
|
||||
const wechatPayError = urlParams.get('wechat_pay_error');
|
||||
|
||||
// 处理授权错误
|
||||
if (wechatPayError) {
|
||||
toast({
|
||||
title: '微信授权失败',
|
||||
description: wechatPayError === 'missing_code' ? '授权被取消' : wechatPayError,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理授权成功,发起 JSAPI 支付
|
||||
if (wechatH5Pay === 'ready' && token) {
|
||||
console.log('[微信H5支付] 授权成功,准备发起支付');
|
||||
|
||||
toast({
|
||||
title: '授权成功',
|
||||
description: '正在发起微信支付...',
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
try {
|
||||
// 调用 JSAPI 创建订单接口
|
||||
const response = await fetch(`${getApiBase()}/api/payment/wechat/jsapi/create-order`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ token }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error(data.error || '创建订单失败');
|
||||
}
|
||||
|
||||
console.log('[微信H5支付] 订单创建成功,调用 WeixinJSBridge');
|
||||
|
||||
// 调用 WeixinJSBridge 发起支付
|
||||
const paymentParams = data.data.payment_params;
|
||||
|
||||
// 等待 WeixinJSBridge 就绪
|
||||
const invokePay = () => {
|
||||
(window as any).WeixinJSBridge.invoke(
|
||||
'getBrandWCPayRequest',
|
||||
{
|
||||
appId: paymentParams.appId,
|
||||
timeStamp: paymentParams.timeStamp,
|
||||
nonceStr: paymentParams.nonceStr,
|
||||
package: paymentParams.package,
|
||||
signType: paymentParams.signType,
|
||||
paySign: paymentParams.paySign,
|
||||
},
|
||||
(res: any) => {
|
||||
// 清除 URL 参数
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
|
||||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||||
toast({
|
||||
title: '支付成功!',
|
||||
description: '您的订阅已激活',
|
||||
status: 'success',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
setTimeout(() => window.location.reload(), 2000);
|
||||
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
|
||||
toast({
|
||||
title: '支付已取消',
|
||||
description: '您可以稍后重新支付',
|
||||
status: 'warning',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: '支付失败',
|
||||
description: res.err_msg || '请稍后重试',
|
||||
status: 'error',
|
||||
duration: 3000,
|
||||
isClosable: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
if (typeof (window as any).WeixinJSBridge !== 'undefined') {
|
||||
invokePay();
|
||||
} else {
|
||||
document.addEventListener('WeixinJSBridgeReady', invokePay, false);
|
||||
}
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[微信H5支付] 支付失败:', error);
|
||||
toast({
|
||||
title: '支付失败',
|
||||
description: error.message,
|
||||
status: 'error',
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
window.history.replaceState({}, document.title, window.location.pathname);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleWechatH5PayReturn();
|
||||
}, [toast]);
|
||||
|
||||
const fetchSubscriptionPlans = async () => {
|
||||
try {
|
||||
logger.debug('SubscriptionContentNew', '正在获取订阅套餐');
|
||||
@@ -484,6 +608,54 @@ export default function SubscriptionContentNew() {
|
||||
}
|
||||
}
|
||||
|
||||
// 检测是否在微信内置浏览器中
|
||||
const isWechatBrowser = /MicroMessenger/i.test(navigator.userAgent);
|
||||
|
||||
// 微信浏览器内选择微信支付:使用 JSAPI 支付
|
||||
if (paymentMethod === 'wechat' && isWechatBrowser) {
|
||||
console.log('[微信H5支付] 检测到微信浏览器,跳转到 OAuth 授权');
|
||||
|
||||
subscriptionEvents.trackPaymentInitiated({
|
||||
planName: selectedPlan.name,
|
||||
paymentMethod: 'wechat_jsapi',
|
||||
amount: price,
|
||||
billingCycle: selectedCycle,
|
||||
});
|
||||
|
||||
// 获取微信 OAuth 授权 URL
|
||||
const authUrlResponse = await fetch(
|
||||
`${getApiBase()}/api/payment/wechat/h5/auth-url?` +
|
||||
`plan_name=${encodeURIComponent(selectedPlan.name)}` +
|
||||
`&billing_cycle=${encodeURIComponent(selectedCycle)}` +
|
||||
`&promo_code=${encodeURIComponent(promoCodeApplied ? promoCode : '')}`,
|
||||
{ credentials: 'include' }
|
||||
);
|
||||
|
||||
const authUrlData = await authUrlResponse.json();
|
||||
|
||||
if (!authUrlData.success) {
|
||||
throw new Error(authUrlData.error || '获取授权链接失败');
|
||||
}
|
||||
|
||||
toast({
|
||||
title: '正在跳转微信授权',
|
||||
description: '请在弹出的页面中确认授权',
|
||||
status: 'info',
|
||||
duration: 2000,
|
||||
isClosable: true,
|
||||
});
|
||||
|
||||
// 关闭支付弹窗
|
||||
onClose();
|
||||
|
||||
// 跳转到微信授权页面
|
||||
setTimeout(() => {
|
||||
window.location.href = authUrlData.auth_url;
|
||||
}, 500);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const paymentMethodName = paymentMethod === 'alipay' ? 'alipay' : 'wechat_pay';
|
||||
|
||||
subscriptionEvents.trackPaymentInitiated({
|
||||
|
||||
@@ -2,19 +2,9 @@
|
||||
// 使用 Ant Design Modal 保持与现有代码风格一致
|
||||
import React from "react";
|
||||
import { Modal as AntModal, Tag, ConfigProvider, theme } from "antd";
|
||||
import { FileText, Clock } from "lucide-react";
|
||||
import { FileText } from "lucide-react";
|
||||
import { GLASS_BLUR } from "@/constants/glassConfig";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
/**
|
||||
* 格式化事件时间
|
||||
*/
|
||||
const formatEventTime = (time) => {
|
||||
if (!time) return null;
|
||||
const d = dayjs(time);
|
||||
if (!d.isValid()) return null;
|
||||
return d.format("MM-DD HH:mm");
|
||||
};
|
||||
import { getEventDetailUrl } from "@/utils/idEncoder";
|
||||
|
||||
/**
|
||||
* 获取相关度颜色
|
||||
@@ -112,7 +102,7 @@ const RelatedEventsModal = ({
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
onClick={() => {
|
||||
window.open(`/community?event_id=${event.event_id}`, "_blank");
|
||||
window.open(getEventDetailUrl(event.event_id), "_blank");
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.background = "rgba(40,40,70,0.9)";
|
||||
@@ -126,11 +116,10 @@ const RelatedEventsModal = ({
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "12px" }}>
|
||||
{/* 标题 + 时间 */}
|
||||
{/* 标题 */}
|
||||
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start" }}>
|
||||
<div style={{ display: "flex", gap: "8px", flex: 1 }}>
|
||||
<FileText size={16} color="#60A5FA" style={{ flexShrink: 0, marginTop: "2px" }} />
|
||||
<div style={{ flex: 1 }}>
|
||||
<FileText size={16} color="#60A5FA" style={{ flexShrink: 0 }} />
|
||||
<span
|
||||
style={{
|
||||
fontSize: "14px",
|
||||
@@ -144,16 +133,6 @@ const RelatedEventsModal = ({
|
||||
>
|
||||
{event.title}
|
||||
</span>
|
||||
{/* 事件时间 */}
|
||||
{(event.created_at || event.event_time || event.publish_time) && (
|
||||
<div style={{ display: "flex", alignItems: "center", gap: "4px", marginTop: "4px" }}>
|
||||
<Clock size={12} color="rgba(255,255,255,0.4)" />
|
||||
<span style={{ fontSize: "11px", color: "rgba(255,255,255,0.4)" }}>
|
||||
{formatEventTime(event.created_at || event.event_time || event.publish_time)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
style={{
|
||||
@@ -163,7 +142,6 @@ const RelatedEventsModal = ({
|
||||
padding: "2px 8px",
|
||||
borderRadius: "6px",
|
||||
flexShrink: 0,
|
||||
marginLeft: "8px",
|
||||
}}
|
||||
>
|
||||
相关度 {event.relevance_score || 0}
|
||||
|
||||
Reference in New Issue
Block a user