微信支付的开发流程详解


Posted in PHP onSeptember 13, 2016

最近在公司做了微信支付的接入,这里总结下开发的一些经验

注意,我使用的是微信开放平台的支付,与手机app相关,而与公众账号无关。

微信支付的主要操作流程

1.用户浏览app,选定商品然后下单。

2.服务器处理订单逻辑,开始正式发起支付流程

3.首先,后台服务器向weixin服务器发起请求,获取一个token。

4.后台服务器拿到token,使用和其他参数加密,再次向weixin服务器发起请求,获取一个预支付prepayid

5.后台服务器将该prepayid返回给app客户端

6.app调用手机上的微信控件,完成付款流程。

7.app向后台服务器发起一个回调请求,通知服务器交易完成。

8.weixin服务器处理完所有的流程后,向后台服务器发起一个post请求,正式通知后台服务器交易完毕

上面流程的一些注意点:

1.每次获取的token是有时效的,默认是7200s,而且每天最多获取200次,因此最好放到redis中缓存起来,等失效后再去重新获取

2.app发起的回调默认是不可靠的,后台应该尽可能(不是必须)向微信服务器发起订单查询,查询本次交易的结果。

3.weixin服务器向后台发起的notify,才是确保交易完成的最后屏障。后台服务器确认后必须返回“success”,否则weixin服务器会尝试重发请求。

获取token

这步很简单,发送一个get请求即可。只需配置正确参数。

‘‘‘从微信服务器获取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[‘access_token‘]
        expires_in = tokenInfo[‘expires_in‘]
        self._writeWeixinTokenLog(token, self.order_no)
        return token
      except KeyError:
        return None #token获取失败
    return None #http请求失败

获取prepayid

在微信支付的开发流程中,最繁琐的就是获取prepayid。

这一步我们需要组装这样一个参数:

{
"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需要的参数如上面代码所示,所以我们需要准备一个params,然后准备签名,签名流程如下:

1.按照key的字典序,对params进行排序,然后拼接成字符串,注意这些key不包括sign

2.在上面的字符串后面拼接key=paternerKey,然后对整个字符串进行md5签名,然后转换成大写,此时我们就得到了签名

然后我们将所有params的value进行urlencode转码,然后后面拼接上sign=signValue,就得到了package字符串。

这里创建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]])
    #加上财付通商户权限密钥
    stringSignTemp += ‘&key=%s‘ % (self.partnerKey)
    #使用MD5进行签名,然后转化为大写
    stringSign = hashlib.md5(stringSignTemp).hexdigest().upper()  #Upper
    return stringSign

组装package的代码:

def getPackage(self, packageParams):
    ‘‘‘先获取params的sign,然后将params进行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

继续组装参数

得到package后,我们继续组装参数:

这里需要的参数为:

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

注意这里有个坑:

参与签名的是上面的参数,但是最后的参数中不包括appKey,签名后要记得删除。

1.所有参数按照字典序排序,然后拼接

2.进行sha1签名,拼接到上面字符串的后面

3.注意这里要删除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

随后我们获取到这样的参数:

{
"appid":"wxd930ea5d5a258f4f", 
"noncestr":"e7d161ac8d8a76529d39d9f5b4249ccb", 
"package":"Sign=WXpay";
"partnerid":"1900000109" 
"prepayid":"1101000000140429eb40476f8896f4c9", 
"sign":"7ffecb600d7157c5aa49810d2d8f28bc2811827b", 
"timestamp":"1399514976"
}

获取prepayid

代码如下:

‘‘‘获取预支付prepayid‘‘‘
  def gerPrepayId(self, token, requestParams):
    ‘‘‘将参数,包括package,进行json化,然后发起post请求‘‘‘
    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格式应该是这样:

{"prepayid":"1101000000140429eb40476f8896f4c9","errcode":0,"errmsg":"Success"} 

再次签名

这里采用上面sha1的签名方式再次签名,获取到下面的参数:

{
"appid":"wxd930ea5d5a258f4f", 
"noncestr":"e7d161ac8d8a76529d39d9f5b4249ccb", 
"package":"Sign=WXpay";
"partnerid":"1900000109" 
"prepayid":"1101000000140429eb40476f8896f4c9", 
"sign":"7ffecb600d7157c5aa49810d2d8f28bc2811827b", 
"timestamp":"1399514976"
}

后台服务器将该结果返回给app,此时app即可发起支付。

上面的流程代码为:

‘‘‘接收app的请求,返回prepayid‘‘‘
class WeixinRequirePrePaidHandler(BasicTemplateHandler):

  ‘‘‘这个方法在OrdersAddHandler中被调用‘‘‘
  @staticmethod
  def getPrePaidResult(order_no, total_pay, product_name, client_ip):
    ‘‘‘封装了常用的签名算法‘‘‘
    weixinRequestHandler = WeixinRequestHandler(order_no)
    ‘‘‘收集订单相关信息‘‘‘
    addtion = str(random.randint(10, 100)) #产生一个两位的数字,拼接在订单号的后面
    out_trade_no = str(order_no) + addtion
    order_price = float(total_pay) #这里必须允许浮点数,后面转化成分之后转化为int
    #order_price = 0.01 #测试
    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)

    ‘‘‘这里的一些参数供下面使用‘‘‘
    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:
      ‘‘‘设置package参数‘‘‘
      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) #订单号
      packageParams[‘partner‘] = config[‘partnerId‘] #商户号
      packageParams[‘total_fee‘] = str(int(order_price*100))  #订单金额,单位是分
      packageParams[‘spbill_create_ip‘] = remote_addr #IP
      packageParams[‘time_start‘] = order_create_time #订单生成时间
      packageParams[‘time_expire‘] = order_deadline #订单失效时间

      ‘‘‘获取package‘‘‘
      package = weixinRequestHandler.getPackage(packageParams)

      ‘‘‘设置支付参数‘‘‘
      signParams = {}
      signParams[‘appid‘] = config[‘appId‘]
      signParams[‘appkey‘] = config[‘paySignKey‘] #delete
      signParams[‘noncestr‘] = noncestr
      signParams[‘package‘] = package
      signParams[‘timestamp‘] = timestamp
      signParams[‘traceid‘] = ‘mytraceid_001‘

      ‘‘‘生成支付签名‘‘‘
      app_signature = weixinRequestHandler.createSHA1Signature(signParams)
      ‘‘‘增加不参与签名的额外参数‘‘‘
      signParams[‘sign_method‘] = ‘sha1‘
      signParams[‘app_signature‘] = app_signature

      ‘‘‘剔除appKey‘‘‘
      del signParams[‘appkey‘]

      ‘‘‘获取prepayid‘‘‘
      prepayid = weixinRequestHandler.gerPrepayId(access_token, signParams)
      if prepayid:

        ‘‘‘使用拿到的prepayid再次准备签名‘‘‘
        pack = ‘sign=WXPay‘
        prepayParams = {}
        prepayParams[‘appid‘] = config[‘appId‘]
        prepayParams[‘appkey‘] = config[‘paySignKey‘]
        prepayParams[‘noncestr‘] = noncestr
        prepayParams[‘package‘] = pack
        prepayParams[‘partnerid‘] = config[‘partnerId‘]
        prepayParams[‘prepayid‘] = prepayid
        prepayParams[‘timestamp‘] = timestamp

        ‘‘‘生成签名‘‘‘
        sign = weixinRequestHandler.createSHA1Signature(prepayParams)

        ‘‘‘准备输出参数‘‘‘
        returnParams = {}
        returnParams[‘status‘] = 0
        returnParams[‘retmsg‘] = ‘success‘
        returnParams[‘appid‘] = config[‘appId‘]
        returnParams[‘noncestr‘] = noncestr
        returnParams[‘package‘] = pack
        returnParams[‘prepayid‘] = prepayid
        returnParams[‘timestamp‘] = timestamp
        returnParams[‘sign‘] = sign
        returnParams[‘partnerId‘] = config[‘partnerId‘]
        returnParams[‘addtion‘] = addtion
        
      else:
        ‘‘‘prepayid获取失败‘‘‘
        returnParams = {}
        returnParams[‘status‘] = -1
        returnParams[‘retmsg‘] = ‘prepayid获取失败‘
    else: 
      ‘‘‘token获取失败‘‘‘
      returnParams = {}
      returnParams[‘status‘] = -1
      returnParams[‘retmsg‘] = ‘token获取失败‘

    ‘‘‘生成json格式文本,然后返回给APP‘‘‘
    return returnParams

后台异步通知

微信服务器发来的notify异步通知,才是支付成功的最终标志,这一步处于安全起见,我们必须进行延签:

延签代码如下:

def isTenpaySign(self, params):
    helper = WeixinRequestHandler()
    sign = helper.createMD5Signature(params)
    return params[‘sign‘] == sign

整体流程如下:

‘‘‘微信服务器向后台发送的异步通知‘‘‘
class WeixinAppNotifyHandler(BasicTemplateHandler):
  def initialize(self):
    self.weixinResponseHandler = WeixinResponseHandler()

  def post(self):
    ‘‘‘解析参数‘‘‘
    params = self.parseQueryString()

    ‘‘‘验证是否是weixin服务器发回的消息‘‘‘
    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) #更新订单使用状态
      consumeCouponByOrderNo(order_no) #优惠券已经使用
      self.write("success")
    else:
      self.write("fail")

  def parseQueryString(self):
    ‘‘‘获取url中所有的参数‘‘‘
    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

最后说明一点,用户在手机上付完款,并不算支付成功,只有weixin服务器收到notify通知返回的success时,才算交易最终成功,此时我们的手机可以收到微信官方发来的一条消息。

以上就是对微信支付开发流程的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!

PHP 相关文章推荐
实时抓取YAHOO股票报价的代码
Oct 09 PHP
php下连接mssql2005的代码
Jan 17 PHP
mysql_num_rows VS COUNT 效率问题分析
Apr 23 PHP
用PHP实现 上一篇、下一篇的代码
Sep 29 PHP
PHP仿博客园 个人博客(1) 数据库与界面设计
Jul 05 PHP
让codeigniter与swfupload整合的最佳解决方案
Jun 12 PHP
Django 中 cookie的使用
Aug 17 PHP
基于Laravel实现的用户动态模块开发
Sep 21 PHP
PHP JWT初识及其简单示例
Oct 10 PHP
PHP让网站移动访问更加友好方法
Feb 14 PHP
Yii框架getter与setter方法功能与用法分析
Oct 22 PHP
一文搞懂php的垃圾回收机制
Jun 18 PHP
PHP仿微信多图片预览上传实例代码
Sep 13 #PHP
微信支付PHP SDK ―― 公众号支付代码详解
Sep 13 #PHP
PHP基于单例模式编写PDO类的方法
Sep 13 #PHP
利用PHP将图片转换成base64编码的实现方法
Sep 13 #PHP
PHP将URL转换成短网址的算法分享
Sep 13 #PHP
PHP使用FFmpeg获取视频播放总时长与码率等信息
Sep 13 #PHP
php微信公众平台开发之微信群发信息
Sep 13 #PHP
You might like
php读取文件内容的方法汇总
2015/01/24 PHP
php去掉文件前几行的方法
2015/07/29 PHP
php需登录的文件上传管理系统
2020/03/21 PHP
php单链表实现代码分享
2016/07/04 PHP
PHP自定义函数实现assign()数组分配到模板及extract()变量分配到模板功能示例
2018/05/23 PHP
PHP实现简易图形计算器
2020/08/28 PHP
几行代码轻松搞定jquery实现flash8类似的连接效果
2007/05/03 Javascript
javascript入门基础之私有变量
2010/02/23 Javascript
JS解析json数据并将json字符串转化为数组的实现方法
2012/12/25 Javascript
jQuery简单实现遍历数组的方法
2015/04/14 Javascript
JavaScript 轮播图和自定义滚动条配合鼠标滚轮分享代码贴
2016/10/28 Javascript
js编写简单的计时器功能
2017/07/15 Javascript
webpack-url-loader 解决项目中图片打包路径问题
2019/02/15 Javascript
微信小程序调用微信支付接口的实现方法
2019/04/29 Javascript
微信小程序全选多选效果实现代码解析
2020/01/21 Javascript
vue全屏事件开发详解
2020/06/17 Javascript
JavaScript Window浏览器对象模型原理解析
2020/05/30 Javascript
Vue 使用typescript如何优雅的调用swagger API
2020/09/01 Javascript
Python 修改列表中的元素方法
2018/06/26 Python
Python文件监听工具pyinotify与watchdog实例
2018/10/15 Python
Python笔记之工厂模式
2019/11/20 Python
使用python matplotlib 画图导入到word中如何保证分辨率
2020/04/16 Python
详解python metaclass(元类)
2020/08/13 Python
python各种excel写入方式的速度对比
2020/11/10 Python
Groupon法国官方网站:特卖和网上购物高达-70%
2019/09/02 全球购物
美国体育用品商店:Academy Sports + Outdoors
2020/01/04 全球购物
美国在线艺术商店:HandmadePiece
2020/11/06 全球购物
String和StringBuffer的区别
2015/08/13 面试题
读群众路线心得体会
2014/03/07 职场文书
经济信息系毕业生自荐信范文
2014/03/15 职场文书
田径运动会开幕式及主持词
2014/03/28 职场文书
党员学习群众路线心得体会
2014/11/04 职场文书
2014年企业党支部工作总结
2014/12/04 职场文书
2014年企业党建工作总结
2014/12/18 职场文书
SONY AN-LP1 短波有源天线放大器
2021/04/22 无线电
Python获取江苏疫情实时数据及爬虫分析
2021/08/02 Python