微信支付的 Native 支付方式是指商户后台系统调用微信支付接口生成预支付交易单,返回二维码链接,用户通过扫描二维码完成支付。使用 Python 实现微信支付 Native 支付功能时,需要处理以下几个关键步骤:
### 接口调用流程
1. **获取预支付交易单接口(统一下单接口)**
商户系统调用微信支付的统一下单接口 `https://api.mch.weixin.qq.com/pay/unifiedorder`,传入必要的参数,包括 `appid`、`mch_id`、`nonce_str`、`body`、`out_trade_no`、`total_fee`、`spbill_create_ip`、`notify_url`、`trade_type` 等字段。
接口返回的 `prepay_id` 和 `code_url` 是生成二维码的关键数据。
2. **生成二维码**
使用 Python 的二维码生成库(如 `qrcode`)将 `code_url` 转换为二维码图像,用户扫描该二维码即可进入支付页面。
3. **处理支付结果通知**
微信服务器会通过异步通知(`notify_url`)将支付结果发送到商户服务器。商户需要验证通知的签名,并返回 `SUCCESS` 或 `FAIL` 响应以确认接收状态。
4. **查询订单状态(可选)**
如果异步通知未能及时处理,商户可以通过 `https://api.mch.weixin.qq.com/pay/orderquery` 接口主动查询订单状态。
### 签名机制
微信支付采用 MD5 或 HMAC-SHA256 签名算法,确保请求的完整性和安全性。签名的生成规则如下:
- 将请求参数按 ASCII 顺序排列,过滤掉 `sign` 字段;
- 将参数拼接成 `key=value` 形式的字符串,并在末尾追加商户私钥(`key`);
- 使用 MD5 或 HMAC-SHA256 对拼接后的字符串进行加密,生成签名值。
```python
import hashlib
import random
import string
import xml.etree.ElementTree as ET
import requests
import qrcode
import time
def generate_nonce_str(length=32):
"""生成随机字符串"""
return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
def generate_sign(params, key):
"""生成签名"""
sorted_params = sorted(params.items(), key=lambda x: x[0])
sign_str = '&'.join(f'{k}={v}' for k, v in sorted_params if v)
sign_str += f'&key={key}'
return hashlib.md5(sign_str.encode('utf-8')).hexdigest().upper()
def unified_order(appid, mch_id, key, notify_url, body, out_trade_no, total_fee, spbill_create_ip):
"""调用微信统一下单接口"""
url = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
params = {
'appid': appid,
'mch_id': mch_id,
'nonce_str': generate_nonce_str(),
'body': body,
'out_trade_no': out_trade_no,
'total_fee': total_fee,
'spbill_create_ip': spbill_create_ip,
'notify_url': notify_url,
'trade_type': 'NATIVE'
}
params['sign'] = generate_sign(params, key)
xml_data = '<xml>' + ''.join(f'<{k}>{v}</{k}>' for k, v in params.items()) + '</xml>'
response = requests.post(url, data=xml_data.encode('utf-8'))
root = ET.fromstring(response.content)
return {child.tag: child.text for child in root}
def generate_qr_code(code_url):
"""生成二维码"""
qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4)
qr.add_data(code_url)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save('wechat_payment_qr.png')
def handle_payment_callback(request_data, key):
"""处理微信支付回调"""
# 解析回调数据
root = ET.fromstring(request_data)
callback_data = {child.tag: child.text for child in root}
# 验证签名
received_sign = callback_data.pop('sign', None)
calculated_sign = generate_sign(callback_data, key)
if received_sign == calculated_sign:
# 签名验证通过
if callback_data.get('return_code') == 'SUCCESS' and callback_data.get('result_code') == 'SUCCESS':
# 支付成功逻辑
return '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'
else:
return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[Payment Failed]]></return_msg></xml>'
else:
return '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[Invalid Signature]]></return_msg></xml>'
# 示例调用
if __name__ == '__main__':
appid = 'your_appid'
mch_id = 'your_mch_id'
key = 'your_key'
notify_url = 'https://yourdomain.com/wechat/payment/callback'
body = 'Test Product'
out_trade_no = f'{int(time.time())}'
total_fee = 1 # 单位为分
spbill_create_ip = '127.0.0.1'
result = unified_order(appid, mch_id, key, notify_url, body, out_trade_no, total_fee, spbill_create_ip)
if result.get('return_code') == 'SUCCESS' and result.get('result_code') == 'SUCCESS':
code_url = result.get('code_url')
generate_qr_code(code_url)
print("二维码已生成,请扫描支付")
else:
print("下单失败", result.get('return_msg'))
```
### 安全机制
- **签名验证**:所有与微信支付交互的请求和回调都需要进行签名验证,确保数据来源的合法性。
- **敏感信息加密**:涉及用户隐私和交易数据的字段应使用 HTTPS 传输,并在必要时进行加密存储。
- **回调处理**:确保 `notify_url` 接口能够正确处理异步通知,并在接收到通知后返回正确的响应,避免微信重复通知。
### 常见问题排查
- **参数错误**:检查 `appid`、`mch_id`、`key` 是否正确,尤其是 `key` 必须与微信商户平台设置的一致。
- **签名错误**:确保签名生成逻辑正确,参数顺序和拼接方式符合微信支付文档要求。
- **网络问题**:确保服务器能够正常访问微信支付接口,并且 `notify_url` 可以被外部访问。
- **超时问题**:如果用户未在规定时间内完成支付,订单将自动关闭,需重新下单。
### 注意事项
- **微信支付没有测试环境**:微信支付目前没有提供沙箱测试环境,因此开发过程中应使用正式环境进行测试,并确保交易金额较小。
- **交易号唯一性**:`out_trade_no` 必须全局唯一,避免重复下单导致的问题。
- **回调处理**:确保 `notify_url` 接口能够正确处理异步通知,并在接收到通知后返回正确的响应,避免微信重复通知。