最近接入微信支付,微信官方并沒(méi)有提供Python版的服務(wù)端SDK,因而只能根據(jù)文檔手動(dòng)實(shí)現(xiàn)一版,這里記錄一下微信支付的整體流程、踩坑過(guò)程與最終具體實(shí)現(xiàn)。
10余年的江蘇網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開(kāi)發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。營(yíng)銷型網(wǎng)站的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整江蘇建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)從事“江蘇網(wǎng)站設(shè)計(jì)”,“江蘇網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
根據(jù)微信官方文檔: https://pay.weixin.qq.com/wiki/doc/apiv3/open/pay/chapter2_5_2.shtml
下單流程如下:
和支付寶不同,微信多了一個(gè)預(yù)付單的概念,這里把APP下單實(shí)際分為四大部分,其中包含請(qǐng)求微信后端需要的首次簽名和需要返回給APP的二次支付信息簽名--這里踩一個(gè)小坑,流程圖中并沒(méi)把第二次簽名支付信息需要返回給APP的步驟畫(huà)出來(lái)(即下面的步驟6.5),因而一開(kāi)始誤以為只需要返回prepay_id給客戶端,導(dǎo)致校驗(yàn)失敗。
一. 對(duì)應(yīng)步驟1~4,APP 請(qǐng)求業(yè)務(wù)后端,業(yè)務(wù)后臺(tái)進(jìn)行V3簽名后,請(qǐng)求微信后端生成預(yù)付單prepay_id
二. 對(duì)應(yīng)步驟5~6.5,業(yè)務(wù)后端收到微信后端返回prepay_id,將支付相關(guān)參數(shù)打包進(jìn)行二次簽名后返回給APP,這里相比流程圖多了一個(gè)6.5--即業(yè)務(wù)后端返回簽名支付信息到APP
三. 對(duì)應(yīng)步驟7~18,APP收到業(yè)務(wù)后端返回簽名支付信息后調(diào)起SDK發(fā)起支付請(qǐng)求,收到同步消息結(jié)果通知
四. 對(duì)應(yīng)步驟19~22,APP查詢業(yè)務(wù)后端,業(yè)務(wù)后端通過(guò)回調(diào)通知或直接查詢微信后端返回最終支付結(jié)果
第一次請(qǐng)求生成預(yù)付單號(hào)的簽名文檔為:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml, 共5個(gè)部分參與簽名,其組成格式為:
HTTP請(qǐng)求方法\nURL\n請(qǐng)求時(shí)間戳\n請(qǐng)求隨機(jī)串\n請(qǐng)求報(bào)文主體\n
對(duì)應(yīng)簽名代碼:
class WechatPayDALBase(object):
def __init__(self, mch_appid, mchid, v3key, serial_no, client_key):
self.mch_appid = mch_appid
self.mchid = mchid
self.v3key = v3key
# serial_no可通過(guò)openssl直接獲取, 例: openssl x509 -in __cert.pem -noout -serial
self.serial_no = serial_no
with open(client_key, 'r') as ifile:
pkey = RSA.importKey(ifile.read())
self.signer = pkcs1_15.new(pkey)
def compute_sign_v3(self, method, url, body):
'''
V3簽名邏輯
'''
ts = int(time.time())
nonce = self.generate_nonce()
uparts= parse_url(url)
ustr = uparts.path + ('?{}'.format(uparts.query) if uparts.query else '')
content = '{}\n{}\n{}\n{}\n{}\n'.format(method, ustr, ts, nonce, body)
digest = SHA256.new(content.encode('utf-8'))
sign_v = base64.b64encode(self.signer.sign(digest)).decode('utf-8')
sign_str = 'serial_no="{}",mchid="{}",timestamp="{}",nonce_str="{}",signature="{}"'.format(
self.serial_no, self.mchid, ts, nonce, sign_v)
return sign_str
def make_headers_v3(self, url, headers=None, body='', method='GET'):
'''
微信支付V3版本簽名header生成函數(shù)
'''
if not headers:
headers = {}
headers['Accept'] = 'application/json'
sign = self.compute_sign_v3(method, url, body)
auth_info = 'WECHATPAY2-SHA256-RSA2048 {}'.format(sign)
headers['Authorization'] = auth_info
return headers
def generate_nonce(self):
rnd = int(time.time()) + random.randint(, )
nonce = hashlib.md5(str(rnd).encode()).hexdigest()[:16]
return nonce
由業(yè)務(wù)后端返回給APP的二次簽名信息文檔為:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_4.shtml
共4個(gè)部分參與簽名,其組成格式為:
應(yīng)用id\n時(shí)間戳\n隨機(jī)字符串\n預(yù)支付交易會(huì)話ID\n
返回簽名支付信息的對(duì)應(yīng)代碼:
def get_pay_sign_info(self, prepay_id):
ts = int(time.time())
nonce = self.generate_nonce()
content = '{}\n{}\n{}\n{}\n'.format(self.mch_appid, ts, nonce, prepay_id)
digest = SHA256.new(content.encode('utf-8'))
sign_v = base64.b64encode(self.signer.sign(digest)).decode('utf-8')
return {
'appid': self.mch_appid,
'partnerid': self.mchid,
'timestamp': str(ts),
'noncestr': nonce,
'prepay_id': prepay_id,
'package': 'Sign=WXPay',
'sign': sign_v,
}
文檔地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_2.shtml
代碼如下:
def query_order(self, out_trade_no):
'''
查詢指定訂單信息
'''
url = f'https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/{out_trade_no}?mchid={self.mchid}'
headers = self.make_headers_v3(url)
rsp = requests.get(url, headers=headers)
pay_logger.info('out_trade_no:{}, rsp:{}|{}'.format(out_trade_no, rsp.status_code, rsp.text))
rdct = rsp.json()
return rdct
文檔地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_2_1.shtml
代碼如下:
def create_order_info(self, data, callback_url):
'''
創(chuàng)建微信預(yù)支付訂單, 注意包含兩次簽名過(guò)程:
首次簽名用于請(qǐng)求微信后端獲取prepay_id
二次簽名信息返回客戶端用于調(diào)起SDK支付
'''
url = 'https://api.mch.weixin.qq.com/v3/pay/transactions/app'
ndt = datetime.now()
out_trade_no = self.generate_partner_trade_no(ndt)
data = {
'mchid': self.mchid,
'out_trade_no': out_trade_no,
'appid': self.mch_appid,
'description': data['subject'],
'notify_url': callback_url,
'amount': {
'currency': 'CNY',
'total': int(data['price']),
},
'time_expire': (ndt + timedelta(minutes=5)).strftime('%Y-%m-%dT%H:%M:%S+08:00')
}
jdata = json.dumps(data, separators=[',', ':'])
headers = {'Content-Type': 'application/json'}
# 第一次簽名, 直接請(qǐng)求微信后端
headers = self.make_headers_v3(url, headers=headers, body=jdata, method='POST')
rsp = requests.post(url, headers=headers, data=jdata)
pay_logger.info('rsp:{}|{}'.format(rsp.status_code, rsp.text))
rdct = rsp.json()
# 第二次簽名, 返回給客戶端調(diào)用
sign_info = self.get_pay_sign_info(rdct['prepay_id'])
return sign_info
試水代碼開(kāi)源,把相關(guān)代碼分享在了github:https://github.com/liuzhi67/wechat-pay-python
轉(zhuǎn)載請(qǐng)注明出處,原文地址:https://www.cnblogs.com/AcAc-t/p/wechat_pay_by_python.html