坑爹的微信支付
上節課我們講了支付寶支付,這節課我們講微信支付,微信支付比支付寶支付稍微簡單一點,原因在於沒有各種非對稱加密,不過有一點我們要記得,需要我們自己設計密鑰。
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')
最後我們返回給微信服務器必須要像我那樣寫。其實代碼也不復雜,只不顧業務邏輯比較繁瑣。