使用二维码进行支付宝交易
This commit is contained in:
@@ -234,6 +234,90 @@ def build_wap_pay_url(out_trade_no, amount, subject, body='', club_id=None, expi
|
|||||||
return f"{cfg['GATEWAY']}?{query_string}"
|
return f"{cfg['GATEWAY']}?{query_string}"
|
||||||
|
|
||||||
|
|
||||||
|
# ==================== 当面付二维码(precreate)====================
|
||||||
|
|
||||||
|
def build_precreate_qr(out_trade_no, amount, subject, body='', club_id=None, expire_minutes=30):
|
||||||
|
"""
|
||||||
|
调用 alipay.trade.precreate 生成当面付二维码链接。
|
||||||
|
|
||||||
|
与 wap.pay 不同:precreate 是服务器主动 POST 调用,返回 qr_code 链接,
|
||||||
|
由商户自行生成二维码展示;用户用支付宝 APP 扫码完成支付。
|
||||||
|
适用于微信小程序等无法跳转 alipay:// scheme 的环境。
|
||||||
|
|
||||||
|
返回:
|
||||||
|
dict: {'qr_code': 'https://qr.alipay.com/...', 'out_trade_no': ...}
|
||||||
|
失败抛异常。
|
||||||
|
"""
|
||||||
|
import requests
|
||||||
|
|
||||||
|
cfg = get_alipay_config(club_id)
|
||||||
|
|
||||||
|
biz_content = {
|
||||||
|
'out_trade_no': out_trade_no,
|
||||||
|
'total_amount': str(amount),
|
||||||
|
'subject': subject or '订单支付',
|
||||||
|
'body': body or '',
|
||||||
|
'timeout_express': f'{expire_minutes}m',
|
||||||
|
}
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'app_id': cfg['APP_ID'],
|
||||||
|
'method': 'alipay.trade.precreate',
|
||||||
|
'charset': 'utf-8',
|
||||||
|
'sign_type': 'RSA2',
|
||||||
|
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||||
|
'version': '1.0',
|
||||||
|
'biz_content': json.dumps(biz_content, separators=(',', ':'), ensure_ascii=False),
|
||||||
|
'notify_url': cfg['NOTIFY_URL'],
|
||||||
|
}
|
||||||
|
|
||||||
|
# 签名(sign_type 参与签名,与 wap.pay 一致)
|
||||||
|
sign_content = _build_sign_content(params)
|
||||||
|
params['sign'] = sign_rsa2(sign_content, cfg['APP_PRIVATE_KEY'])
|
||||||
|
|
||||||
|
# 服务器主动调用网关
|
||||||
|
resp = requests.post(cfg['GATEWAY'], data=params, timeout=10)
|
||||||
|
try:
|
||||||
|
result = resp.json()
|
||||||
|
except Exception:
|
||||||
|
raise Exception(f"支付宝 precreate 返回非 JSON: {resp.text[:500]}")
|
||||||
|
|
||||||
|
biz_resp = result.get('alipay_trade_precreate_response', {})
|
||||||
|
code = biz_resp.get('code', '')
|
||||||
|
|
||||||
|
if code == '10000':
|
||||||
|
return {
|
||||||
|
'qr_code': biz_resp.get('qr_code', ''),
|
||||||
|
'out_trade_no': biz_resp.get('out_trade_no', out_trade_no),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 失败:拼接完整错误信息
|
||||||
|
sub_code = biz_resp.get('sub_code', '')
|
||||||
|
sub_msg = biz_resp.get('sub_msg', '')
|
||||||
|
raise Exception(
|
||||||
|
f"支付宝预下单失败: code={code}, msg={biz_resp.get('msg', '')}, "
|
||||||
|
f"sub_code={sub_code}, sub_msg={sub_msg}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def qr_link_to_base64(qr_link):
|
||||||
|
"""
|
||||||
|
把二维码链接内容转成 PNG base64 data URI(供前端 image 直接展示)。
|
||||||
|
依赖 qrcode + Pillow;缺失时抛 ImportError 提示安装。
|
||||||
|
"""
|
||||||
|
import io
|
||||||
|
import base64
|
||||||
|
try:
|
||||||
|
import qrcode
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError("缺少 qrcode 库,请执行: pip install qrcode[pil]")
|
||||||
|
|
||||||
|
img = qrcode.make(qr_link)
|
||||||
|
buf = io.BytesIO()
|
||||||
|
img.save(buf, format='PNG')
|
||||||
|
return 'data:image/png;base64,' + base64.b64encode(buf.getvalue()).decode('ascii')
|
||||||
|
|
||||||
|
|
||||||
# ==================== 辅助 ====================
|
# ==================== 辅助 ====================
|
||||||
|
|
||||||
def club_id_from_order(order_id):
|
def club_id_from_order(order_id):
|
||||||
|
|||||||
@@ -969,6 +969,13 @@ class CreateOrderView(APIView):
|
|||||||
KEY = pay_cfg['key']
|
KEY = pay_cfg['key']
|
||||||
NOTIFY_URL = getattr(settings, 'WEIXIN_NOTIFY_URL', '')
|
NOTIFY_URL = getattr(settings, 'WEIXIN_NOTIFY_URL', '')
|
||||||
|
|
||||||
|
# NOAUTH 诊断日志:打印生效的 club_id / appid / mchid / openid 指纹
|
||||||
|
_oid_preview = (openid or '')[:6] + '...' + (openid or '')[-4:] if (openid or '') else '(empty)'
|
||||||
|
logger.warning(
|
||||||
|
"[WX_PAY_NOAUTH_DIAG] dingdanid=%s club_id=%s appid=%s mch_id=%s openid_preview=%s openid_len=%d",
|
||||||
|
dingdanid, club_id, APPID, MCHID, _oid_preview, len(openid or ''),
|
||||||
|
)
|
||||||
|
|
||||||
if not all([APPID, MCHID, KEY, NOTIFY_URL]):
|
if not all([APPID, MCHID, KEY, NOTIFY_URL]):
|
||||||
raise Exception('微信支付配置不完整')
|
raise Exception('微信支付配置不完整')
|
||||||
|
|
||||||
@@ -1029,6 +1036,7 @@ class CreateOrderView(APIView):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# 解析返回的XML
|
# 解析返回的XML
|
||||||
|
logger.warning("[WX_PAY_NOAUTH_DIAG] wechat_raw_response=%s", response.content.decode('utf-8', errors='replace'))
|
||||||
root = ET.fromstring(response.content)
|
root = ET.fromstring(response.content)
|
||||||
|
|
||||||
return_code = root.find('return_code').text
|
return_code = root.find('return_code').text
|
||||||
@@ -1087,22 +1095,23 @@ class CreateOrderView(APIView):
|
|||||||
|
|
||||||
def generate_alipay_pay_params(self, dingdanid, jine, subject, club_id=None):
|
def generate_alipay_pay_params(self, dingdanid, jine, subject, club_id=None):
|
||||||
"""
|
"""
|
||||||
生成支付宝 wap.pay 跳转 URL,存入 cache 供中转页取用。
|
生成支付宝当面付二维码支付参数。
|
||||||
同时返回 pay_url 供前端直接加载(绕过中转页跳转限制)。
|
返回 qr_image(base64 PNG)供前端 image 直接展示,用户用支付宝扫码支付。
|
||||||
|
适用于微信小程序 web-view 无法跳转 alipay:// scheme 的场景。
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
from jituan.services.alipay_pay import build_wap_pay_url
|
from jituan.services.alipay_pay import build_precreate_qr, qr_link_to_base64
|
||||||
pay_url = build_wap_pay_url(
|
result = build_precreate_qr(
|
||||||
out_trade_no=dingdanid,
|
out_trade_no=dingdanid,
|
||||||
amount=jine,
|
amount=jine,
|
||||||
subject=subject,
|
subject=subject,
|
||||||
club_id=club_id,
|
club_id=club_id,
|
||||||
)
|
)
|
||||||
# 存入 cache,30 分钟过期(供中转页/查询接口判断支付方式)
|
qr_code = result['qr_code']
|
||||||
cache_key = f'alipay_pay_url_{dingdanid}'
|
qr_image = qr_link_to_base64(qr_code)
|
||||||
cache.set(cache_key, pay_url, 60 * 30)
|
|
||||||
return {
|
return {
|
||||||
'pay_url': pay_url,
|
'qr_code': qr_code,
|
||||||
|
'qr_image': qr_image,
|
||||||
'pay_method': 'alipay',
|
'pay_method': 'alipay',
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user