微信支付的開發(fā)流程詳解
最近在公司做了微信支付的接入,這里總結(jié)下開發(fā)的一些經(jīng)驗(yàn)
注意,我使用的是微信開放平臺(tái)的支付,與手機(jī)app相關(guān),而與公眾賬號(hào)無關(guān)。
微信支付的主要操作流程
1.用戶瀏覽app,選定商品然后下單。
2.服務(wù)器處理訂單邏輯,開始正式發(fā)起支付流程
3.首先,后臺(tái)服務(wù)器向weixin服務(wù)器發(fā)起請(qǐng)求,獲取一個(gè)token。
4.后臺(tái)服務(wù)器拿到token,使用和其他參數(shù)加密,再次向weixin服務(wù)器發(fā)起請(qǐng)求,獲取一個(gè)預(yù)支付prepayid
5.后臺(tái)服務(wù)器將該prepayid返回給app客戶端
6.app調(diào)用手機(jī)上的微信控件,完成付款流程。
7.app向后臺(tái)服務(wù)器發(fā)起一個(gè)回調(diào)請(qǐng)求,通知服務(wù)器交易完成。
8.weixin服務(wù)器處理完所有的流程后,向后臺(tái)服務(wù)器發(fā)起一個(gè)post請(qǐng)求,正式通知后臺(tái)服務(wù)器交易完畢
上面流程的一些注意點(diǎn):
1.每次獲取的token是有時(shí)效的,默認(rèn)是7200s,而且每天最多獲取200次,因此最好放到redis中緩存起來,等失效后再去重新獲取
2.app發(fā)起的回調(diào)默認(rèn)是不可靠的,后臺(tái)應(yīng)該盡可能(不是必須)向微信服務(wù)器發(fā)起訂單查詢,查詢本次交易的結(jié)果。
3.weixin服務(wù)器向后臺(tái)發(fā)起的notify,才是確保交易完成的最后屏障。后臺(tái)服務(wù)器確認(rèn)后必須返回“success”,否則weixin服務(wù)器會(huì)嘗試重發(fā)請(qǐng)求。
獲取token
這步很簡(jiǎn)單,發(fā)送一個(gè)get請(qǐng)求即可。只需配置正確參數(shù)。
‘‘‘從微信服務(wù)器獲取token‘‘‘
def _getAccessTokenFromWeixin(self):
response = requests.get(self.tokenUrl % (self.appId, self.appSecret))
if response.status_code == 200:
text = response.text
tokenInfo = json.loads(text)
try:
token = tokenInfo[‘a(chǎn)ccess_token‘]
expires_in = tokenInfo[‘expires_in‘]
self._writeWeixinTokenLog(token, self.order_no)
return token
except KeyError:
return None #token獲取失敗
return None #http請(qǐng)求失敗
獲取prepayid
在微信支付的開發(fā)流程中,最繁瑣的就是獲取prepayid。
這一步我們需要組裝這樣一個(gè)參數(shù):
{
"appid":"wxd930ea5d5a258f4f",
"traceid":"test_1399514976",
"noncestr":"e7d161ac8d8a76529d39d9f5b4249ccb ",
"timestamp":1399514976, "package":"bank_type=WX&body=%E6%94%AF%E4%BB%98%E6%B5%8B%E8%AF%
95&fee_type=1&input_charset=UTF-8¬ify_url=http%3A%2F%2Fweixin.qq.com&out_trade_ no=7240b65810859cbf2a8d9f76a638c0a3&partner=1900000109&spbill_create_ip=196.168.1.1& total_fee=1&sign=7F77B507B755B3262884291517E380F8",
"sign_method":"sha1", "app_signature":"7f77b507b755b3262884291517e380f8"
}
組裝package
這里的第一步就是組裝package:
"package":"bank_type=WX&body=%E6%94%AF%E4%BB%98%E6%B5%8B%E8%AF% 95&fee_type=1&input_charset=UTF-8¬ify_url=http%3A%2F%2Fweixin.qq.com&out_trade_ no=7240b65810859cbf2a8d9f76a638c0a3&partner=1900000109&spbill_create_ip=196.168.1.1& total_fee=1&sign=7F77B507B755B3262884291517E380F8",
組裝package需要的參數(shù)如上面代碼所示,所以我們需要準(zhǔn)備一個(gè)params,然后準(zhǔn)備簽名,簽名流程如下:
1.按照key的字典序,對(duì)params進(jìn)行排序,然后拼接成字符串,注意這些key不包括sign
2.在上面的字符串后面拼接key=paternerKey,然后對(duì)整個(gè)字符串進(jìn)行md5簽名,然后轉(zhuǎn)換成大寫,此時(shí)我們就得到了簽名
然后我們將所有params的value進(jìn)行urlencode轉(zhuǎn)碼,然后后面拼接上sign=signValue,就得到了package字符串。
這里創(chuàng)建MD5的過如下:
def createMD5Signature(self, signParams):
‘‘‘先排序‘‘‘
sortedParams = sorted(signParams.iteritems(), key=lambda d:d[0])
‘‘‘拼接‘‘‘
stringSignTemp = "&".join(["%s=%s" % (item[0], item[1]) for item in sortedParams if item[0] != ‘sign‘ and ‘‘ != item[1]])
#加上財(cái)付通商戶權(quán)限密鑰
stringSignTemp += ‘&key=%s‘ % (self.partnerKey)
#使用MD5進(jìn)行簽名,然后轉(zhuǎn)化為大寫
stringSign = hashlib.md5(stringSignTemp).hexdigest().upper() #Upper
return stringSign
組裝package的代碼:
def getPackage(self, packageParams):
‘‘‘先獲取params的sign,然后將params進(jìn)行urlencode,最后拼接,加上sign‘‘‘
sign = self.createMD5Signature(packageParams)
packageParams = sorted(packageParams.iteritems(), key=lambda d:d[0])
stringParams = "&".join(["%s=%s" % (item[0], urllib.quote(str(item[1]))) for item in packageParams])
stringParams += ‘&sign=%s‘ % (sign)
return stringParams
繼續(xù)組裝參數(shù)
得到package后,我們繼續(xù)組裝參數(shù):
這里需要的參數(shù)為:
appid=wxd930ea5d5a258f4f appkey=L8LrMqqeGRxST5reouB0K66CaY A WpqhA Vsq7ggKkxHCOastWksvuX1uvmvQcl xaHoYd3ElNBrNO2DHnnzgfVG9Qs473M3DTOZug5er46FhuGofumV8H2FVR9qkjSlC5K noncestr=e7d161ac8d8a76529d39d9f5b4249ccb package=bank_type=WX&body=%E6%94%AF%E4%BB%98%E6%B5%8B%E8%AF%95 &fee_type=1&input_charset=UTF-8¬ify_url=http%3A%2F%2Fweixin.qq.com&out_trade_no =7240b65810859cbf2a8d9f76a638c0a3&partner=1900000109&spbill_create_ip=196.168.1.1&tot al_fee=1&sign=7F77B507B755B3262884291517E380F8 timestamp=1399514976
traceid=test_1399514976
注意這里有個(gè)坑:
參與簽名的是上面的參數(shù),但是最后的參數(shù)中不包括appKey,簽名后要記得刪除。
1.所有參數(shù)按照字典序排序,然后拼接
2.進(jìn)行sha1簽名,拼接到上面字符串的后面
3.注意這里要?jiǎng)h除appKey,然后加上sign
獲取sha1簽名的代碼如下:
def createSHA1Signature(self, params):
‘‘‘先排序,然后拼接‘‘‘
sortedParams = sorted(params.iteritems(), key=lambda d:d[0])
stringSignTemp = "&".join(["%s=%s" % (item[0], item[1]) for item in sortedParams])
stringSign = hashlib.sha1(stringSignTemp).hexdigest()
return stringSign
隨后我們獲取到這樣的參數(shù):
{
"appid":"wxd930ea5d5a258f4f",
"noncestr":"e7d161ac8d8a76529d39d9f5b4249ccb",
"package":"Sign=WXpay";
"partnerid":"1900000109"
"prepayid":"1101000000140429eb40476f8896f4c9",
"sign":"7ffecb600d7157c5aa49810d2d8f28bc2811827b",
"timestamp":"1399514976"
}
獲取prepayid
代碼如下:
‘‘‘獲取預(yù)支付prepayid‘‘‘
def gerPrepayId(self, token, requestParams):
‘‘‘將參數(shù),包括package,進(jìn)行json化,然后發(fā)起post請(qǐng)求‘‘‘
data = json.dumps(requestParams)
response = requests.post(self.gateUrl % (token), data=data)
if response.status_code == 200:
text = response.text
text = json.loads(text)
errcode = text[‘errcode‘]
if errcode == 0:
return text[‘prepayid‘]
return None
我們獲取的prepayid格式應(yīng)該是這樣:
{"prepayid":"1101000000140429eb40476f8896f4c9","errcode":0,"errmsg":"Success"}
再次簽名
這里采用上面sha1的簽名方式再次簽名,獲取到下面的參數(shù):
{
"appid":"wxd930ea5d5a258f4f",
"noncestr":"e7d161ac8d8a76529d39d9f5b4249ccb",
"package":"Sign=WXpay";
"partnerid":"1900000109"
"prepayid":"1101000000140429eb40476f8896f4c9",
"sign":"7ffecb600d7157c5aa49810d2d8f28bc2811827b",
"timestamp":"1399514976"
}
后臺(tái)服務(wù)器將該結(jié)果返回給app,此時(shí)app即可發(fā)起支付。
上面的流程代碼為:
‘‘‘接收app的請(qǐng)求,返回prepayid‘‘‘
class WeixinRequirePrePaidHandler(BasicTemplateHandler):
‘‘‘這個(gè)方法在OrdersAddHandler中被調(diào)用‘‘‘
@staticmethod
def getPrePaidResult(order_no, total_pay, product_name, client_ip):
‘‘‘封裝了常用的簽名算法‘‘‘
weixinRequestHandler = WeixinRequestHandler(order_no)
‘‘‘收集訂單相關(guān)信息‘‘‘
addtion = str(random.randint(10, 100)) #產(chǎn)生一個(gè)兩位的數(shù)字,拼接在訂單號(hào)的后面
out_trade_no = str(order_no) + addtion
order_price = float(total_pay) #這里必須允許浮點(diǎn)數(shù),后面轉(zhuǎn)化成分之后轉(zhuǎn)化為int
#order_price = 0.01 #測(cè)試
remote_addr = client_ip #客戶端的IP地址
print remote_addr
current_time = int(time.time())
order_create_time = str(current_time)
order_deadline = str(current_time + 20*60)
‘‘‘這里的一些參數(shù)供下面使用‘‘‘
noncestr = hashlib.md5(str(random.random())).hexdigest()
timestamp = str(int(time.time()))
pack = ‘Sign=WXPay‘
‘‘‘獲取token‘‘‘
access_token = weixinRequestHandler.getAccessToken()
logging.info("get token: %s" % access_token)
if access_token:
‘‘‘設(shè)置package參數(shù)‘‘‘
packageParams = {}
packageParams[‘bank_type‘] = ‘WX‘ #支付類型
packageParams[‘body‘] = product_name #商品名稱
packageParams[‘fee_type‘] = ‘1‘ #人民幣 fen
packageParams[‘input_charset‘] = ‘GBK‘ #GBK
packageParams[‘notify_url‘] = config[‘notify_url‘] #post異步消息通知
packageParams[‘out_trade_no‘] = str(out_trade_no) #訂單號(hào)
packageParams[‘partner‘] = config[‘partnerId‘] #商戶號(hào)
packageParams[‘total_fee‘] = str(int(order_price*100)) #訂單金額,單位是分
packageParams[‘spbill_create_ip‘] = remote_addr #IP
packageParams[‘time_start‘] = order_create_time #訂單生成時(shí)間
packageParams[‘time_expire‘] = order_deadline #訂單失效時(shí)間
‘‘‘獲取package‘‘‘
package = weixinRequestHandler.getPackage(packageParams)
‘‘‘設(shè)置支付參數(shù)‘‘‘
signParams = {}
signParams[‘a(chǎn)ppid‘] = config[‘a(chǎn)ppId‘]
signParams[‘a(chǎn)ppkey‘] = config[‘paySignKey‘] #delete
signParams[‘noncestr‘] = noncestr
signParams[‘package‘] = package
signParams[‘timestamp‘] = timestamp
signParams[‘traceid‘] = ‘mytraceid_001‘
‘‘‘生成支付簽名‘‘‘
app_signature = weixinRequestHandler.createSHA1Signature(signParams)
‘‘‘增加不參與簽名的額外參數(shù)‘‘‘
signParams[‘sign_method‘] = ‘sha1‘
signParams[‘a(chǎn)pp_signature‘] = app_signature
‘‘‘剔除appKey‘‘‘
del signParams[‘a(chǎn)ppkey‘]
‘‘‘獲取prepayid‘‘‘
prepayid = weixinRequestHandler.gerPrepayId(access_token, signParams)
if prepayid:
‘‘‘使用拿到的prepayid再次準(zhǔn)備簽名‘‘‘
pack = ‘sign=WXPay‘
prepayParams = {}
prepayParams[‘a(chǎn)ppid‘] = config[‘a(chǎn)ppId‘]
prepayParams[‘a(chǎn)ppkey‘] = config[‘paySignKey‘]
prepayParams[‘noncestr‘] = noncestr
prepayParams[‘package‘] = pack
prepayParams[‘partnerid‘] = config[‘partnerId‘]
prepayParams[‘prepayid‘] = prepayid
prepayParams[‘timestamp‘] = timestamp
‘‘‘生成簽名‘‘‘
sign = weixinRequestHandler.createSHA1Signature(prepayParams)
‘‘‘準(zhǔn)備輸出參數(shù)‘‘‘
returnParams = {}
returnParams[‘status‘] = 0
returnParams[‘retmsg‘] = ‘success‘
returnParams[‘a(chǎn)ppid‘] = config[‘a(chǎn)ppId‘]
returnParams[‘noncestr‘] = noncestr
returnParams[‘package‘] = pack
returnParams[‘prepayid‘] = prepayid
returnParams[‘timestamp‘] = timestamp
returnParams[‘sign‘] = sign
returnParams[‘partnerId‘] = config[‘partnerId‘]
returnParams[‘a(chǎn)ddtion‘] = addtion
else:
‘‘‘prepayid獲取失敗‘‘‘
returnParams = {}
returnParams[‘status‘] = -1
returnParams[‘retmsg‘] = ‘prepayid獲取失敗‘
else:
‘‘‘token獲取失敗‘‘‘
returnParams = {}
returnParams[‘status‘] = -1
returnParams[‘retmsg‘] = ‘token獲取失敗‘
‘‘‘生成json格式文本,然后返回給APP‘‘‘
return returnParams
后臺(tái)異步通知
微信服務(wù)器發(fā)來的notify異步通知,才是支付成功的最終標(biāo)志,這一步處于安全起見,我們必須進(jìn)行延簽:
延簽代碼如下:
def isTenpaySign(self, params):
helper = WeixinRequestHandler()
sign = helper.createMD5Signature(params)
return params[‘sign‘] == sign
整體流程如下:
‘‘‘微信服務(wù)器向后臺(tái)發(fā)送的異步通知‘‘‘
class WeixinAppNotifyHandler(BasicTemplateHandler):
def initialize(self):
self.weixinResponseHandler = WeixinResponseHandler()
def post(self):
‘‘‘解析參數(shù)‘‘‘
params = self.parseQueryString()
‘‘‘驗(yàn)證是否是weixin服務(wù)器發(fā)回的消息‘‘‘
verifyWeixinSign = self.weixinResponseHandler.isTenpaySign(params)
‘‘‘處理訂單‘‘‘
if verifyWeixinSign:
‘‘‘訂單邏輯‘‘‘
order_no = str(params[‘out_trade_no‘])
order_no = order_no[0:-2]
print ‘%s paied successfully‘ % order_no
self.saveWeixinReceipt(params)
updateOrdersPaidByWeixin(order_no) #更新訂單使用狀態(tài)
consumeCouponByOrderNo(order_no) #優(yōu)惠券已經(jīng)使用
self.write("success")
else:
self.write("fail")
def parseQueryString(self):
‘‘‘獲取url中所有的參數(shù)‘‘‘
uri = self.request.uri
‘‘‘解析出URI中的query字符串‘‘‘
parseResult = urlparse.urlparse(uri)
query = parseResult.query
‘‘‘解析query字符串‘‘‘
params = urlparse.parse_qs(query)
for item in params:
params[item] = params[item][0].strip()
return params
最后說明一點(diǎn),用戶在手機(jī)上付完款,并不算支付成功,只有weixin服務(wù)器收到notify通知返回的success時(shí),才算交易最終成功,此時(shí)我們的手機(jī)可以收到微信官方發(fā)來的一條消息。
以上就是對(duì)微信支付開發(fā)流程的資料整理,后續(xù)繼續(xù)補(bǔ)充相關(guān)資料,謝謝大家對(duì)本站的支持!
- 基于OpenCart 開發(fā)支付寶,財(cái)付通,微信支付參數(shù)錯(cuò)誤問題
- 微信公眾號(hào)可通過現(xiàn)金紅包接口發(fā)放微信支付現(xiàn)金紅包(附開發(fā)教程)
- 微信支付PHP SDK —— 公眾號(hào)支付代碼詳解
- iOS微信支付開發(fā)案例
- iOS微信支付交互圖分析
- android微信支付源碼分享
- Android高仿微信支付密碼輸入控件
- 利用Python開發(fā)微信支付的注意事項(xiàng)
- 微信支付僅能成功調(diào)用一次問題的解決方法(Android)
- Android 高仿微信支付數(shù)字鍵盤功能
- 微信支付 :curl出錯(cuò),錯(cuò)誤碼:60兩個(gè)問題的解決
相關(guān)文章
ADODB結(jié)合SMARTY使用~超級(jí)強(qiáng)
ADODB結(jié)合SMARTY使用~超級(jí)強(qiáng)...2006-11-11
PHP使用preg_split()分割特殊字符(元字符等)的方法分析
這篇文章主要介紹了PHP使用preg_split()分割特殊字符(元字符等)的方法,結(jié)合具體實(shí)例形式分析了php正則分割的操作技巧與注意事項(xiàng),需要的朋友可以參考下2017-02-02
PHP的mysqli_query參數(shù)MYSQLI_STORE_RESULT和MYSQLI_USE_RESULT的區(qū)別
這篇文章主要介紹了PHP的mysqli_query參數(shù)MYSQLI_STORE_RESULT和MYSQLI_USE_RESULT的區(qū)別,本文給出了這兩個(gè)參數(shù)的5個(gè)區(qū)別,需要的朋友可以參考下2014-09-09
php 微信開發(fā)獲取用戶信息如何實(shí)現(xiàn)
這篇文章主要介紹了php 微信開發(fā)獲取用戶信息如何實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2016-12-12
PHP實(shí)現(xiàn)網(wǎng)頁內(nèi)容html標(biāo)簽補(bǔ)全和過濾的方法小結(jié)【2種方法】
這篇文章主要介紹了PHP實(shí)現(xiàn)網(wǎng)頁內(nèi)容html標(biāo)簽補(bǔ)全和過濾的方法,結(jié)合實(shí)例形式分析了php常見的標(biāo)簽檢查、補(bǔ)全、閉合、過濾等相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
PHP數(shù)據(jù)庫操作二:memcache用法分析
這篇文章主要介紹了PHP數(shù)據(jù)庫操作memcache用法,結(jié)合實(shí)例形式詳細(xì)分析了memcache的下載、安裝、配置及相關(guān)使用技巧,需要的朋友可以參考下2017-08-08

