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

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

1.支付交互流程
在这里插入图片描述

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

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

import requests
 
from config import APPID, SECRET
 
 
class OpenidUtils(object):
 
    def __init__(self, jscode):
        self.url = "https://api.weixin.qq.com/sns/jscode2session"
        self.appid = APPID  # 小程序id
        self.secret = SECRET  # 不要跟后面支付的key搞混
        self.jscode = jscode    # 前端传回的动态jscode
 
    def get_openid(self):
        # url一定要拼接,不可用传参方式
        url = self.url + "?appid=" + self.appid + "&secret=" + self.secret + "&js_code=" + self.jscode + "&grant_type=authorization_code"
        r = requests.get(url)
        print(r.json())
        openid = r.json()['openid']
 
        return openid

3.支付请求

# -*- coding:utf-8 -*-
import requests
import hashlib
import xmltodict
import time
import random
import string
import urllib2
import sys


class WX_PayToolUtil():
    """ 微信支付工具 """

    def __init__(self, APP_ID, MCH_ID, API_KEY, NOTIFY_URL):
        self._APP_ID = APP_ID  # 小程序ID
        self._MCH_ID = MCH_ID  # # 商户号
        self._API_KEY = API_KEY
        self._UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder"  # 接口链接
        self._NOTIFY_URL = NOTIFY_URL  # 异步通知

    def generate_sign(self, param):
            '''生成签名'''
            stringA = ''
            ks = sorted(param.keys())
            # 参数排序
            for k in ks:
                stringA += (k + '=' + param[k] + '&')
            # 拼接商户KEY
            stringSignTemp = stringA + "key=" + self._API_KEY
            # md5加密,也可以用其他方式
            hash_md5 = hashlib.md5(stringSignTemp.encode('utf8'))
            sign = hash_md5.hexdigest().upper()
            return sign

    '''
    # python2另外一种实现方法
    def generate_sign(self, params):
        ret = []
        for k in sorted(params.keys()):
            if (k != 'sign') and (k != '') and (params[k] is not None):
                ret.append('%s=%s' % (k, params[k]))
        params_str = '&'.join(ret)
        params_str = '%(params_str)s&key=%(partner_key)s' % {'params_str': params_str, 'partner_key': key}

        reload(sys)
        sys.setdefaultencoding('utf8')

        params_str = hashlib.md5(params_str.encode('utf-8')).hexdigest()
        sign = params_str.upper()
        return sign
    '''

    def getPayUrl(self, orderid, openid, goodsPrice, **kwargs):
        """向微信支付端发出请求,获取url"""
        key = self._API_KEY
        nonce_str = ''.join(random.sample(string.letters + string.digits, 30))  # 生成随机字符串,小于32位
        params = {
            'appid': self._APP_ID,  # 小程序ID
            'mch_id': self._MCH_ID,  # 商户号
            'nonce_str': nonce_str,  # 随机字符串
            "body": '测试订单',  # 支付说明
            'out_trade_no': orderid,  # 生成的订单号
            'total_fee': str(goodsPrice),  # 标价金额
            'spbill_create_ip': "127.0.0.1",  # 小程序不能获取客户ip,web用socekt实现
            'notify_url': self._NOTIFY_URL,
            'trade_type': "JSAPI",  # 支付类型
            "openid": openid,  # 用户id
         }
        # 生成签名
        params['sign'] = self.generate_sign(params)

        # python3一种写法
        param = {'root': params}
        xml = xmltodict.unparse(param)
        response = requests.post(self._UFDODER_URL, data=xml.encode('utf-8'), headers={'Content-Type': 'text/xml'})
        # xml 2 dict
        msg = response.text
        xmlmsg = xmltodict.parse(msg)
        # 4. 获取prepay_id
        if xmlmsg['xml']['return_code'] == 'SUCCESS':
            if xmlmsg['xml']['result_code'] == 'SUCCESS':
                prepay_id = xmlmsg['xml']['prepay_id']
                # 时间戳
                timeStamp = str(int(time.time()))
                # 5. 五个参数
                data = {
                    "appId": self._APP_ID,
                    "nonceStr": nonce_str,
                    "package": "prepay_id=" + prepay_id,
                    "signType": 'MD5',
                    "timeStamp": timeStamp,
                }
                # 6. paySign签名
                paySign = self.generate_sign(data)
                data["paySign"] = paySign  # 加入签名
                # 7. 传给前端的签名后的参数
                return data

        # python2一种写法
        '''
        request_xml_str = '<xml>'
        for key, value in params.items():
            if isinstance(value, str):
                request_xml_str = '%s<%s><![CDATA[%s]]></%s>' % (request_xml_str, key, value, key,)
            else:
                request_xml_str = '%s<%s>%s</%s>' % (request_xml_str, key, value, key,)
        request_xml_str = '%s</xml>' % request_xml_str

        # 向微信支付发出请求,并提取回传数据
        res = urllib2.Request(self._UFDODER_URL, data=request_xml_str.encode("utf-8"))
        res_data = urllib2.urlopen(res)
        res_read = res_data.read()
        doc = xmltodict.parse(res_read)
        return_code = doc['xml']['return_code']
        if return_code == "SUCCESS":
            result_code = doc['xml']['result_code']
            if result_code == "SUCCESS":
                doc = doc['xml']
                data = {
                    "appId": self._APP_ID,
                    "nonceStr": nonce_str,
                    "package": "prepay_id=" + doc["prepay_id"],
                    "signType": 'MD5',
                    "timeStamp": str(int(time.time())),
                }
                # paySign签名
                paySign = self.generate_sign(data)
                data["paySign"] = paySign  # 加入签名
                return data
            else:
                err_des = doc['xml']['err_code_des']
                return err_des 
        else:
            fail_des = doc['xml']['return_msg']
            return fail_des
        '''

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

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

4.支付回调

# 统一下单回调处理
 
import xmltodict
 
from django.http import HttpResponse
 
def payback(request):
    msg = request.body.decode('utf-8')
    xmlmsg = xmltodict.parse(msg)
 
    return_code = xmlmsg['xml']['return_code']
 
    if return_code == 'FAIL':
        # 官方发出错误
        return HttpResponse("""<xml><return_code><![CDATA[FAIL]]></return_code>
                            <return_msg><![CDATA[Signature_Error]]></return_msg></xml>""",
                            content_type='text/xml', status=200)
    elif return_code == 'SUCCESS':
        # 拿到这次支付的订单号
        out_trade_no = xmlmsg['xml']['out_trade_no']
 
        # 根据需要处理业务逻辑
 
        return HttpResponse("""<xml><return_code><![CDATA[SUCCESS]]></return_code>
                            <return_msg><![CDATA[OK]]></return_msg></xml>""",
                            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.安全问题
在使用的过程中商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。

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

最后送给大家一段祝福

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值