diff --git a/jituan/services/wechat_native_pay.py b/jituan/services/wechat_native_pay.py index e9158fd..49d64e5 100644 --- a/jituan/services/wechat_native_pay.py +++ b/jituan/services/wechat_native_pay.py @@ -166,12 +166,43 @@ def build_native_bridge_url(dingdanid: str, club_id=None) -> str: return f'https://www.abas.asia/hqhd/dingdan/wx-native-pay/?{q}' -def build_official_jsapi_pay_params(**kwargs): - raise Exception('已改为官方 Native 付款码,不再走服务号 H5 JSAPI') +def build_official_jsapi_pay_params( + *, + dingdanid, + jine, + body, + notify_url='', + openid, + club_id=None, + client_ip='127.0.0.1', +): + """服务号 JSAPI 统一下单(H5 / 相册中转页)。notify_url 由订单侧 settings 接管。""" + from orders.views.payment import CreateOrderView + + helper = CreateOrderView() + return helper.generate_wechat_pay_params( + dingdanid=dingdanid, + jine=jine, + beizhu=(body or '微信支付')[:40] or '微信支付', + user_ip=client_ip or '127.0.0.1', + openid=openid, + club_id=club_id, + app_source='official', + ) def build_mini_jsapi_pay_params(**kwargs): - raise Exception('已改为官方 Native 付款码,不再走小程序码 JSAPI') + raise Exception('小程序请走 CreateOrderView.generate_wechat_pay_params(app_source=mini)') + + +def build_h5_jsapi_bridge_url(dingdanid: str, club_id=None, return_url='') -> str: + from urllib.parse import quote + q = f'out_trade_no={quote(dingdanid or "", safe="")}' + if club_id: + q += f'&club_id={quote(str(club_id), safe="")}' + if return_url: + q += f'&return_url={quote(str(return_url), safe="")}' + return f'https://www.abas.asia/hqhd/dingdan/wx-h5-pay/?{q}' def resolve_scene_to_dingdanid(scene: str) -> str: diff --git a/orders/views/payment.py b/orders/views/payment.py index 806d975..d6c345e 100644 --- a/orders/views/payment.py +++ b/orders/views/payment.py @@ -533,11 +533,101 @@ def alipay_pay_page(request): def wx_h5_jsapi_pay_page(request): """H5 点单密码支付中转页(落在已登记的 /hqhd/dingdan/ 授权目录)。 - 不是扫码。仅把 WeixinJSBridge 调起挪到商户已配置的目录下, - 避免 /xqh5/ 报「当前页面的URL未注册」。 - 支付参数由同域 sessionStorage 传入(前端写入)。 + 不是扫码。流程: + 1) 带 out_trade_no 进入 → 服务号静默 OAuth 取当前微信 openid + 2) 用该 openid 对未付款订单重新统一下单,服务端注入 pay 参数 + 3) 页面仅用户点击后 WeixinJSBridge.invoke(避免自动调起被微信秒关) + + sessionStorage 仅作无 OAuth / 旧链接兜底。 """ - return render(request, 'wechat/h5_jsapi_pay.html', {}) + from urllib.parse import quote + from jituan.services.club_resolver import get_club_official_credentials, oauth2_access_token + from jituan.services.wechat_native_pay import ( + build_official_jsapi_pay_params, + build_h5_jsapi_bridge_url, + ) + + out_trade_no = (request.GET.get('out_trade_no') or request.GET.get('dingdanid') or '').strip() + club_id = (request.GET.get('club_id') or '').strip() or 'ufo' + code = (request.GET.get('code') or '').strip() + return_url = (request.GET.get('return_url') or '').strip() + + ctx = { + 'server_pay': None, + 'server_error': '', + 'out_trade_no': out_trade_no, + 'club_id': club_id, + 'return_url': return_url, + 'oauth_openid_preview': '', + 'pay_source': 'sessionStorage', + } + + if not out_trade_no: + # 兼容仅 sessionStorage 的旧跳转 + return render(request, 'wechat/h5_jsapi_pay.html', ctx) + + # 查未付款订单 + order = None + jine = None + try: + from orders.models import Order + order = Order.query.filter(OrderID=out_trade_no).first() + if order and int(getattr(order, 'Status', -1) or -1) == 9: + jine = getattr(order, 'Amount', None) + club_id = (getattr(order, 'ClubID', None) or club_id or '').strip() or club_id + except Exception: + logger.exception('[WX_H5_PAY] 查订单失败 dingdan=%s', out_trade_no) + + if order is None or jine is None: + ctx['server_error'] = '订单不存在或已支付/已取消,请返回点单页重新下单' + return render(request, 'wechat/h5_jsapi_pay.html', ctx) + + appid, secret, _club = get_club_official_credentials(club_id) + if not appid or not secret: + ctx['server_error'] = f'俱乐部 {club_id} 未配置服务号 official_appid/secret' + return render(request, 'wechat/h5_jsapi_pay.html', ctx) + + if not code: + redirect_uri = build_h5_jsapi_bridge_url(out_trade_no, club_id, return_url) + oauth = ( + 'https://open.weixin.qq.com/connect/oauth2/authorize' + f'?appid={quote(appid)}' + f'&redirect_uri={quote(redirect_uri, safe="")}' + '&response_type=code&scope=snsapi_base&state=h5pay#wechat_redirect' + ) + from django.shortcuts import redirect + return redirect(oauth) + + wx = oauth2_access_token(code, club_id=club_id) + openid = (wx or {}).get('openid') or '' + if not openid: + ctx['server_error'] = (wx or {}).get('errmsg') or '微信授权失败,请返回重试' + return render(request, 'wechat/h5_jsapi_pay.html', ctx) + + oid = openid.strip() + ctx['oauth_openid_preview'] = (oid[:6] + '...' + oid[-4:]) if len(oid) > 10 else oid + + xff = request.META.get('HTTP_X_FORWARDED_FOR') + client_ip = (xff.split(',')[0].strip() if xff else '') or request.META.get('REMOTE_ADDR') or '127.0.0.1' + try: + pay = build_official_jsapi_pay_params( + dingdanid=out_trade_no, + jine=jine, + body=getattr(settings, 'WEIXIN_PAY_DESCRIPTION', '星阙订单'), + openid=oid, + club_id=club_id, + client_ip=client_ip, + ) + except Exception as exc: + logger.exception('[WX_H5_PAY] OAuth 重下单失败 dingdan=%s', out_trade_no) + ctx['server_error'] = f'统一下单失败:{exc}' + return render(request, 'wechat/h5_jsapi_pay.html', ctx) + + # 去掉前端不需要的私有字段副本;_diag 保留给诊断页 + ctx['server_pay'] = pay + ctx['club_id'] = club_id + ctx['pay_source'] = 'oauth_server' + return render(request, 'wechat/h5_jsapi_pay.html', ctx) def wx_native_pay_page(request): @@ -1331,6 +1421,9 @@ class CreateOrderView(APIView): 'club_id': str(club_id or ''), 'appid': str(APPID or ''), 'app_source': str(source or ''), + 'openid_preview': _oid_preview, + 'openid_len': len(openid or ''), + 'key_source': str(pay_cfg.get('_key_source') or ''), }, } diff --git a/templates/wechat/h5_jsapi_pay.html b/templates/wechat/h5_jsapi_pay.html index e7d1813..7513745 100644 --- a/templates/wechat/h5_jsapi_pay.html +++ b/templates/wechat/h5_jsapi_pay.html @@ -29,17 +29,27 @@ background: #3a0000; border: 2px solid #ff2d2d; color: #ffe0e0; font-size: 15px; font-weight: 700; line-height: 1.5; word-break: break-all; } + .warn { + margin: 0 0 16px; padding: 12px; border-radius: 10px; + background: #2a2200; border: 1px solid #c9a227; color: #ffe9a8; + font-size: 13px; line-height: 1.55; + }
微信支付(诊断模式)
+
+ 请手动点下方绿色按钮调起支付(不要依赖自动弹窗)。
+ 若闪一下就关:看有没有灰字「当前页面的URL未注册」——有=目录仍未生效;没有=把下方绿日志整段复制发回。 +
初始化…
loading…
+{% if server_pay %}{{ server_pay|json_script:"server-pay-data" }}{% endif %}