• 019 Python实现微信小程序支付功能


    正文

    由于最近自己在做小程序的支付,就在这里简单介绍一下讲一下用python做小程序支付这个流程。当然在进行开发之前还是建议读一下具体的流程,清楚支付的过程。

    1.支付交互流程

    当然具体的参数配置可以参考官方文档https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_3&index=1

     2.获取openid(微信用户标识)

     1 import requests
     2  
     3 from config import APPID, SECRET
     4  
     5  
     6 class OpenidUtils(object):
     7  
     8     def __init__(self, jscode):
     9         self.url = "https://api.weixin.qq.com/sns/jscode2session"
    10         self.appid = APPID  # 小程序id
    11         self.secret = SECRET  # 不要跟后面支付的key搞混
    12         self.jscode = jscode    # 前端传回的动态jscode
    13  
    14     def get_openid(self):
    15         # url一定要拼接,不可用传参方式
    16         url = self.url + "?appid=" + self.appid + "&secret=" + self.secret + "&js_code=" + self.jscode + "&grant_type=authorization_code"
    17         r = requests.get(url)
    18         print(r.json())
    19         openid = r.json()['openid']
    20  
    21         return openid

    3.支付请求

      1 # -*- coding:utf-8 -*-
      2 import requests
      3 import hashlib
      4 import xmltodict
      5 import time
      6 import random
      7 import string
      8 import urllib2
      9 import sys
     10 
     11 
     12 class WX_PayToolUtil():
     13     """ 微信支付工具 """
     14 
     15     def __init__(self, APP_ID, MCH_ID, API_KEY, NOTIFY_URL):
     16         self._APP_ID = APP_ID  # 小程序ID
     17         self._MCH_ID = MCH_ID  # # 商户号
     18         self._API_KEY = API_KEY
     19         self._UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"  # 接口链接
     20         self._NOTIFY_URL = NOTIFY_URL  # 异步通知
     21 
     22     def generate_sign(self, param):
     23             '''生成签名'''
     24             stringA = ''
     25             ks = sorted(param.keys())
     26             # 参数排序
     27             for k in ks:
     28                 stringA += (k + '=' + param[k] + '&')
     29             # 拼接商户KEY
     30             stringSignTemp = stringA + "key=" + self._API_KEY
     31             # md5加密,也可以用其他方式
     32             hash_md5 = hashlib.md5(stringSignTemp.encode('utf8'))
     33             sign = hash_md5.hexdigest().upper()
     34             return sign
     35 
     36     '''
     37     # python2另外一种实现方法
     38     def generate_sign(self, params):
     39         ret = []
     40         for k in sorted(params.keys()):
     41             if (k != 'sign') and (k != '') and (params[k] is not None):
     42                 ret.append('%s=%s' % (k, params[k]))
     43         params_str = '&'.join(ret)
     44         params_str = '%(params_str)s&key=%(partner_key)s' % {'params_str': params_str, 'partner_key': key}
     45 
     46         reload(sys)
     47         sys.setdefaultencoding('utf8')
     48 
     49         params_str = hashlib.md5(params_str.encode('utf-8')).hexdigest()
     50         sign = params_str.upper()
     51         return sign
     52     '''
     53 
     54     def getPayUrl(self, orderid, openid, goodsPrice, **kwargs):
     55         """向微信支付端发出请求,获取url"""
     56         key = self._API_KEY
     57         nonce_str = ''.join(random.sample(string.letters + string.digits, 30))  # 生成随机字符串,小于32位
     58         params = {
     59             'appid': self._APP_ID,  # 小程序ID
     60             'mch_id': self._MCH_ID,  # 商户号
     61             'nonce_str': nonce_str,  # 随机字符串
     62             "body": '测试订单',  # 支付说明
     63             'out_trade_no': orderid,  # 生成的订单号
     64             'total_fee': str(goodsPrice),  # 标价金额
     65             'spbill_create_ip': "127.0.0.1",  # 小程序不能获取客户ip,web用socekt实现
     66             'notify_url': self._NOTIFY_URL,
     67             'trade_type': "JSAPI",  # 支付类型
     68             "openid": openid,  # 用户id
     69          }
     70         # 生成签名
     71         params['sign'] = self.generate_sign(params)
     72 
     73         # python3一种写法
     74         param = {'root': params}
     75         xml = xmltodict.unparse(param)
     76         response = requests.post(self._UFDODER_URL, data=xml.encode('utf-8'), headers={'Content-Type': 'text/xml'})
     77         # xml 2 dict
     78         msg = response.text
     79         xmlmsg = xmltodict.parse(msg)
     80         # 4. 获取prepay_id
     81         if xmlmsg['xml']['return_code'] == 'SUCCESS':
     82             if xmlmsg['xml']['result_code'] == 'SUCCESS':
     83                 prepay_id = xmlmsg['xml']['prepay_id']
     84                 # 时间戳
     85                 timeStamp = str(int(time.time()))
     86                 # 5. 五个参数
     87                 data = {
     88                     "appId": self._APP_ID,
     89                     "nonceStr": nonce_str,
     90                     "package": "prepay_id=" + prepay_id,
     91                     "signType": 'MD5',
     92                     "timeStamp": timeStamp,
     93                 }
     94                 # 6. paySign签名
     95                 paySign = self.generate_sign(data)
     96                 data["paySign"] = paySign  # 加入签名
     97                 # 7. 传给前端的签名后的参数
     98                 return data
     99 
    100         # python2一种写法
    101         '''
    102         request_xml_str = '<xml>'
    103         for key, value in params.items():
    104             if isinstance(value, str):
    105                 request_xml_str = '%s<%s><![CDATA[%s]]></%s>' % (request_xml_str, key, value, key,)
    106             else:
    107                 request_xml_str = '%s<%s>%s</%s>' % (request_xml_str, key, value, key,)
    108         request_xml_str = '%s</xml>' % request_xml_str
    109 
    110         # 向微信支付发出请求,并提取回传数据
    111         res = urllib2.Request(self._UFDODER_URL, data=request_xml_str.encode("utf-8"))
    112         res_data = urllib2.urlopen(res)
    113         res_read = res_data.read()
    114         doc = xmltodict.parse(res_read)
    115         return_code = doc['xml']['return_code']
    116         if return_code == "SUCCESS":
    117             result_code = doc['xml']['result_code']
    118             if result_code == "SUCCESS":
    119                 doc = doc['xml']
    120                 data = {
    121                     "appId": self._APP_ID,
    122                     "nonceStr": nonce_str,
    123                     "package": "prepay_id=" + doc["prepay_id"],
    124                     "signType": 'MD5',
    125                     "timeStamp": str(int(time.time())),
    126                 }
    127                 # paySign签名
    128                 paySign = self.generate_sign(data)
    129                 data["paySign"] = paySign  # 加入签名
    130                 return data
    131             else:
    132                 err_des = doc['xml']['err_code_des']
    133                 return err_des 
    134         else:
    135             fail_des = doc['xml']['return_msg']
    136             return fail_des
    137         '''

    当然你可能会遇到的错误有签名错误,一般的情况是你的appSecret和商户号的API密钥两个弄错了,当然如果不是还有可能是其他问题,解决方案链接https://www.cnblogs.com/wanghuijie/p/wxpay_sign_error.html

    其他的支付方式获取用户的ip地址可以通过socket.gethostbyname(socket.gethostname())方法来获取。

    4.支付回调

     1 # 统一下单回调处理
     2  
     3 import xmltodict
     4  
     5 from django.http import HttpResponse
     6  
     7 def payback(request):
     8     msg = request.body.decode('utf-8')
     9     xmlmsg = xmltodict.parse(msg)
    10  
    11     return_code = xmlmsg['xml']['return_code']
    12  
    13     if return_code == 'FAIL':
    14         # 官方发出错误
    15         return HttpResponse("""<xml><return_code><![CDATA[FAIL]]></return_code>
    16                             <return_msg><![CDATA[Signature_Error]]></return_msg></xml>""",
    17                             content_type='text/xml', status=200)
    18     elif return_code == 'SUCCESS':
    19         # 拿到这次支付的订单号
    20         out_trade_no = xmlmsg['xml']['out_trade_no']
    21  
    22         # 根据需要处理业务逻辑
    23  
    24         return HttpResponse("""<xml><return_code><![CDATA[SUCCESS]]></return_code>
    25                             <return_msg><![CDATA[OK]]></return_msg></xml>""",
    26                             content_type='text/xml', status=200)

    当然微信回调的参数有很多详细可以参考https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_7&index=8

    在回调的时候可能遇到这样一个问题,支付成功以后没有调回调函数,有可能是回调地址是https然后改为http就行,遇到过这个坑,具体原因也不知道。服务器没有屏蔽https访问,https证书也没有问题,把https改为http最后就可以了。

    5.安全问题

    在使用的过程中商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。

    我在开发过程中的解决方式是在向微信支付端发起请求的时候,把订单号,金额,签名等存入数据库,然后在回调函数那里进行校验判断。在确认跟前面订单情况一样的情况下,才进行后续一系列的操作。

    最后送给大家一段祝福

    #                                  _oo8oo_
    #                                 o8888888o
    #                                 88" . "88
    #                                 (| -_- |)
    #                                 0  =  /0
    #                               ___/'==='\___
    #                             .' \|     |# '.
    #                            / \|||  :  |||# 
    #                           / _||||| -:- |||||_ 
    #                          |   | \  -  #/ |   |
    #                          | \_|  ''---/''  |_/ |
    #                            .-\__  '-'  __/-.  /
    #                        ___'. .'  /--.--  '. .'___
    #                     ."" '<  '.___\_<|>_/___.'  >' "".
    #                    | | :  `- \`.:` _ /`:.`/ -`  : | |
    #                       `-.   \_ __ /__ _/   .-` /  /
    #                =====`-.____`.___ \_____/ ___.`____.-`=====
    #                                  `=---=`
    #
    #
    #               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    #
    #                          强大爷保佑         永不宕机/永无bug
    啦啦啦
  • 相关阅读:
    Blue的博客
    透明状态栏和沉浸式状态栏
    Html的label和span的区别
    三个石匠的故事
    OpenSSL 生成自定义证书
    github博客配置
    js作用域其二:预解析
    数据分析常用工具总结
    堆排序
    吴裕雄--天生自然 JAVA开发学习: 循环结构
  • 原文地址:https://www.cnblogs.com/abdm-989/p/11991471.html
Copyright © 2020-2023  润新知