坑爹的微信支付
上节课我们讲了支付宝支付,这节课我们讲微信支付,微信支付比支付宝支付稍微简单一点,原因在于没有各种非对称加密,不过有一点我们要记得,需要我们自己设计密钥。
class NeoTenpayAgent(object):
def __init__(self):
self.partner_id = Config.WEIXIN_MCH_ID
self.partner_key = Config.WEIXIN_MCH_KEY
self.app_id = Config.WEIXIN_APP_ID
self.notify_url = Config.WEIXIN_NOTIFY_URL
self.session = requests.Session()
def generate_params(self, user_id, order_no,
item_name, item_description, price):
params = {
'body': item_name,
'fee_type': 'CNY',
'notify_url': self.notify_url,
'out_trade_no': order_no,
'mch_id': self.partner_id,
'spbill_create_ip': '196.168.1.1',
'total_fee': str(price),
'appid': self.app_id,
'nonce_str': uuid.uuid4().hex,
'trade_type': 'APP',
}
chunk = '&'.join(
'{}={}'.format(k, v)
for k, v in sorted(params.items())
)
chunk += '&key=' + self.partner_key
params['sign'] = hashlib.md5(chunk.encode('utf-8')).hexdigest().upper()
xml_params = E.xml(
E.body(params['body']),
E.fee_type(params['fee_type']),
E.notify_url(params['notify_url']),
E.out_trade_no(params['out_trade_no']),
E.mch_id(params['mch_id']),
E.spbill_create_ip(params['spbill_create_ip']),
E.total_fee(params['total_fee']),
E.appid(params['appid']),
E.nonce_str(params['nonce_str']),
E.sign(params['sign']),
E.trade_type(params['trade_type']),
)
data = lxml.etree.tostring(xml_params, encoding='utf-8')
page = self.session.post(
'https://api.mch.weixin.qq.com/pay/unifiedorder',
data=data,
)
page.encoding = 'utf-8'
resp = lxml.etree.fromstring(page.text)
if resp.xpath('/xml/err_code/text()'):
for item in resp.xpath('/xml/err_code/text()'):
err_code = item
for item in resp.xpath('/xml/err_code_des/text()'):
err_code_des = item
if err_code == 'ORDERPAID':
# 订单已支付,无需再次尝试
raise ValueError(err_code_des)
else:
raise Exception('({}) {}'.format(
err_code, err_code_des
))
# 调起支付的参数
for item in resp.xpath('/xml/prepay_id/text()'):
params = {
'appid': params['appid'],
'noncestr': params['nonce_str'],
'package': 'Sign=WXPay',
'partnerid': self.partner_id,
'prepayid': item,
'timestamp': _timestamp_from_datetime(datetime.now()),
}
chunk = '&'.join(
'{}={}'.format(k, v)
for k, v in sorted(params.items())
)
chunk += '&key=' + self.partner_key
params['sign'] = hashlib.md5(
chunk.encode('utf-8')
).hexdigest().upper()
return params
def verify(self, params):
params = lxml.etree.fromstring(params)
if not params.xpath('/xml/sign/text()'):
raise ValueError('参数 sign 不存在')
# 值为空的字段不参与签名
sign_params = {}
for item in params.xpath('/xml')[0]:
if item.tag not in ['sign'] and item.text:
sign_params[item.tag] = item.text
chunk = '&'.join(
'{}={}'.format(
k.decode('utf-8') if isinstance(k, str) else k,
v.decode('utf-8') if isinstance(v, str) else v,
)
for k, v in sorted(sign_params.items())
)
chunk = (chunk + '&key=' + self.partner_key).encode('utf-8')
signature = hashlib.md5(chunk).hexdigest().upper()
if params.xpath('/xml/sign/text()')[0] != signature:
raise ValueError('签名不正确')
这就是根据微信后台API设计的商户后台服务器。
值得注意的是,商户回调需要我们自己设计。
def handle_weipay():
NeoTenpayAgent().verify(request.data.decode('utf-8'))
import xml.etree.cElementTree as ET
data = ET.fromstring(request.data.decode('utf-8'))
data = {d.tag: d.text for d in data}
if data.get('return_code') == 'SUCCESS':
order_id = data.get('out_trade_no')
Ten_or_Ali_order_id = data.get('transaction_id')
payment_type = '微信'
payment_state = data.get('result_code')
subject = data.get('attach', '证件照')
fee = data.get('total_fee')
"""
写数据库
"""
print(payment_state)
else:
print('支付失败')
with open('static/logg/weipaylog.txt', 'at') as f:
f.write(str(data))
from xml.etree.cElementTree import Element, tostring
elem = Element('xml')
for key, value in {'return_code': 'SUCCESS', 'return_msg': 'OK'}.items():
child = Element(key)
child.text = value
elem.append(child)
return tostring(elem, encoding='utf-8')
最后我们返回给微信服务器必须要像我那样写。其实代码也不复杂,只不顾业务逻辑比较繁琐。