微信app支付功能-服务端的实现-python3版

一:需求说明

在自有的android app中加入微信支付功能,app内点击支付按钮,需app调起本机上的微信app进行支付。

二:微信app支付处理流程

在这里插入图片描述
上图为微信官方支付流程图,由图可见,商户服务端需要实现3部分业务,其中2部分业务与商户客户端调用相关,另一部分业务与微信服务端回调相关。

注:开发文档路径:访问微信开发文档https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_3

三:所需依赖

3.1 支付配置

APP_ID
在微信开放平台(https://open.weixin.qq.com)获取APPID

如果没有创建应用,请先创建

MCH_ID
在微信商户平台获取MCH_ID

API密钥
获取API密钥

四:接口开发

4.1 创建订单接口

创建订单,对订单数据进行签名,然后向微信服务器发送创建订单请求。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import traceback
import logging
import uuid
import requests
import json
import xmltodict
import time
import pymysql
import datetime
import random

from hashlib import md5

MYSQL = dict(
    host='127.0.0.1', user='mysql_user', passwd='mysql_pwd', db='mydb', charset="utf8mb4"
)
logger = logging.getLogger(__name__)
conn = pymysql.connect(**MYSQL)
cur_dict = conn.cursor(pymysql.cursors.DictCursor)
cur = conn.cursor()

###############################################
#############    微信支付配置   #################
###############################################
# 微信支付APP_ID
WEIXIN_APP_ID = 'wx91f04ffbf8a23431'
# 微信支付MCH_ID 【登录账号】
WEIXIN_MCH_ID = '1535411231'
# 微信支付sign_type
WEIXIN_SIGN_TYPE = 'MD5'
# 服务器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '费用充值'
# 微信KEY值 【API密钥】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信统一下单URL
WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
# 微信查询订单URL
WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
# 微信支付回调API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'


def make_payment_info(notify_url=None, out_trade_no=None, total_fee=None):
    order_info = {'appid': WEIXIN_APP_ID,
                  'mch_id': WEIXIN_MCH_ID,
                  'device_info': 'WEB',
                  'nonce_str': '',
                  'sign_type': WEIXIN_SIGN_TYPE,
                  'body': WEIXIN_BODY,
                  'out_trade_no': str(out_trade_no),
                  'total_fee': total_fee,
                  'spbill_create_ip': WEIXIN_SPBILL_CREATE_IP,
                  'notify_url': notify_url,
                  'trade_type': 'APP'}
    return order_info


def make_payment_request_wx(notify_url, out_trade_no, total_fee):
    """
    微信统一下单,并返回客户端数据
    :param notify_url: 回调地址
    :param out_trade_no: 订单编号
    :param total_fee: 充值金额
    :return: app所需结果数据
    """

    def generate_call_app_data(params_dict, prepay_id):
        """
        客户端APP的数据参数包装
        """
        request_order_info = {'appid': params_dict['appid'],
                              'partnerid': params_dict['mch_id'],
                              'prepayid': prepay_id,
                              'package': 'Sign=WXPay',
                              'noncestr': generate_nonce_str(),
                              'timestamp': str(int(time.time()))}
        request_order_info['sign'] = generate_sign(request_order_info)
        return request_order_info

    def generate_sign(params):
        """
        生成md5签名的参数
        """
        if 'sign' in params:
            params.pop('sign')
        src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
        return md5(src.encode('utf-8')).hexdigest().upper()

    def generate_nonce_str():
        """
        生成随机字符串
        """
        return str(uuid.uuid4()).replace('-', '')

    def generate_request_data(params_dict):
        """
        生成统一下单请求所需要提交的数据
        """
        params_dict['nonce_str'] = generate_nonce_str()
        params_dict['sign'] = generate_sign(params_dict)
        return xmltodict.unparse({'xml': params_dict}, pretty=True, full_document=False).encode('utf-8')

    def make_payment_request(params_dict, unified_order_url):
        """
        生成返回给客户端APP的数据参数
        """
        data = generate_request_data(params_dict)
        headers = {'Content-Type': 'application/xml'}
        res = requests.post(unified_order_url, data=data, headers=headers)
        if res.status_code == 200:
            result = json.loads(json.dumps(xmltodict.parse(res.content)))
            if result['xml']['return_code'] == 'SUCCESS':
                prepay_id = result['xml']['prepay_id']
                return generate_call_app_data(params_dict, prepay_id), result['xml']
            else:
                return result['xml']['return_msg'], None
        return None, None
    
    if float(total_fee) < 0.01:
        raise Exception('充值金额不能小于0.01')
    payment_info = make_payment_info(notify_url=notify_url, out_trade_no=out_trade_no, total_fee=total_fee)
    res, info = make_payment_request(payment_info, WEIXIN_UNIFIED_ORDER_URL)
    return res, info


def create_order(data, out_trade_no):
    """
    创建订单信息,存入库中
    :return:
    """
    insert_sql = ''' insert into {table}(status, app_id, seller_id, device_info, trade_type, prepay_id, trade_status, 
    out_trade_no, total_amount) 
     values (3, '{app_id}', '{seller_id}', '{device_info}', '{trade_type}', '{prepay_id}', '{trade_status}', 
     '{out_trade_no}', '{total_amount}')'''

    app_id = data['appid']  # 应用ID
    seller_id = data['mch_id']  # 商户号
    device_info = data['device_info']  # 微信支付分配的终端设备号
    trade_status = data['result_code']  # 业务结果 SUCCESS/FAIL
    total_amount = data['total_amount']  # 总金额
    if trade_status == "SUCCESS":
        trade_type = data['trade_type']  # 交易类型
        prepay_id = data['prepay_id']  # 预支付交易会话标识
        insert_sql = insert_sql.format(
            app_id=app_id,
            seller_id=seller_id,
            device_info=device_info,
            trade_type=trade_type,
            prepay_id=prepay_id,
            trade_status=trade_status,
            out_trade_no=out_trade_no,
            total_amount=total_amount/100  # 将微信的分转为元
        )
        cur_dict.execute(insert_sql)
        return True
    else:
        return False


def create_order_number():
    """
    生成订单号
    :return:
    """
    date = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
    # 生成4为随机数作为订单号的一部分
    random_str = str(random.randint(1, 9999))
    random_str = random_str.rjust(4, '0')
    rtn = '%s%s%s' % (date, random_str)
    return rtn


def weixin_create_order(request):
    """
    【API】: 创建订单,供商户app调用
    """
    res = {
        'code': 1,
        'msg': 'error'
    }
    try:
        price = 0.99  # 0.99元,微信的单位为分,需要转为分
        out_trade_no = create_order_number()
        order_info, info = make_payment_request_wx(WEIXIN_CALLBACK_API, out_trade_no, int(price * 100))
        if order_info and info:
            info['total_amount'] = int(price * 100)
            if info['result_code'] == "SUCCESS":
                order_info['out_trade_no'] = out_trade_no
                res['order_info'] = order_info
                create_order(info, out_trade_no)
            # 调用统一创建订单接口失败
            else:
                res['msg'] = info['result_code']
        elif order_info:
            res['msg'] = order_info
            res['code'] = -1
        else:
            res['code'] = -2
    except Exception:
        traceback.print_exc()
    finally:
        return json.dumps(res)

4.2 微信异步回调接口

微信异步回调通知商户服务端。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import traceback
import logging
import xmltodict
import pymysql

from hashlib import md5

MYSQL = dict(
    host='127.0.0.1', user='mysql_user', passwd='mysql_pwd', db='mydb', charset="utf8mb4"
)
logger = logging.getLogger(__name__)
conn = pymysql.connect(**MYSQL)
cur_dict = conn.cursor(pymysql.cursors.DictCursor)
cur = conn.cursor()


###############################################
#############    微信支付配置   #################
###############################################
# 微信支付APP_ID
WEIXIN_APP_ID = 'wx91f04ffbf8a23431'
# 微信支付MCH_ID 【登录账号】
WEIXIN_MCH_ID = '1535411231'
# 微信支付sign_type
WEIXIN_SIGN_TYPE = 'MD5'
# 服务器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '费用充值'
# 微信KEY值 【API密钥】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信统一下单URL
WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
# 微信查询订单URL
WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
# 微信支付回调API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'


def weixinpay_call_back(request):
    """
    微信支付回调
    :param request: 回调参数
    :return:
    """

    def generate_sign(params):
        """
        生成md5签名的参数
        """
        if 'sign' in params:
            params.pop('sign')
        src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
        return md5(src.encode('utf-8')).hexdigest().upper()

    def validate_sign(resp_dict):
        """
        验证微信返回的签名
        """
        if 'sign' not in resp_dict:
            return False
        wx_sign = resp_dict['sign']
        sign = generate_sign(resp_dict)
        if sign == wx_sign:
            return True
        return False

    def handle_wx_response_xml(params):
        """
        处理微信支付返回的xml格式数据
        """
        resp_dict = xmltodict.parse(params)['xml']
        return_code = resp_dict.get('return_code')
        if return_code == 'SUCCESS':  # 仅仅判断通信标识成功,非交易标识成功,交易需判断result_code
            if validate_sign(resp_dict):
                return resp_dict
        else:
            print('FAIL')
        return
    args = request.body
    # 验证平台签名
    resp_dict = handle_wx_response_xml(args)
    if resp_dict is None:
        return None
    return resp_dict


def weixinpay_response_xml(params):
    """
    生成交易成功返回信息
    """

    def generate_response_data(resp_dict):
        """
        字典转xml
        """
        return xmltodict.unparse({'xml': resp_dict}, pretty=True, full_document=False).encode('utf-8')

    return_info = {
        'return_code': params,
        'return_msg': 'OK'
    }
    return generate_response_data(return_info)


def weixin_rollback(request):
    """
    【API】: 微信宝支付结果回调接口,供微信服务端调用
    """
    try:
        # 支付异步回调验证
        data = weixinpay_call_back(request)
        if data:
            res = "success"

            trade_status = data['result_code']  # 业务结果  SUCCESS/FAIL
            out_trade_no = data['out_trade_no']  # 商户订单号
            if trade_status == "SUCCESS":
                status = 1
                app_id = data['appid']  # 应用ID
                bank_type = data['bank_type']  # 付款银行
                cash_fee = data['cash_fee']  # 现金支付金额(分)
                device_info = data['device_info']  # 微信支付分配的终端设备号
                fee_type = data['fee_type']  # 货币种类
                gmt_create = data['time_end']  # 支付完成时间
                total_amount = int(data['total_fee'])/100  # 总金额(单位由分转元)
                trade_type = data['trade_type']  # 交易类型
                trade_no = data['transaction_id']  # 微信支付订单号
                seller_id = data['mch_id']  # 商户号
                buyer_id = data['openid']  # 用户标识

                update_sql = ''' update orders set trade_status='{trade_status}', app_id='{app_id}', 
                                    seller_id='{seller_id}', buyer_id='{buyer_id}', total_amount='{total_amount}', 
                                    out_trade_no='{out_trade_no}', gmt_create='{gmt_create}', trade_no='{trade_no}', 
                                    device_info='{device_info}', trade_type='{trade_type}', bank_type='{bank_type}', 
                                    fee_type='{fee_type}', cash_fee='{cash_fee}',  
                                    status='{status}' where out_trade_no='{out_trade_no}' '''
                update_sql = update_sql.format(
                    app_id=app_id,
                    bank_type=bank_type,
                    cash_fee=cash_fee,
                    device_info=device_info,
                    fee_type=fee_type,
                    out_trade_no=out_trade_no,
                    gmt_create=gmt_create,
                    total_amount=total_amount,
                    trade_type=trade_type,
                    trade_no=trade_no,
                    seller_id=seller_id,
                    buyer_id=buyer_id,
                    trade_status=trade_status,
                    status=status
                )
            else:
                res = "error: pay failed! "
                status = 0
                err_code = data['err_code']  # 错误代码
                err_code_des = data['err_code_des']  # 错误代码描述
                update_sql = ''' update orders set trade_status='{trade_status}', err_code='{err_code}', 
                err_code_des='{err_code_des}', status='{status}' where out_trade_no='{out_trade_no}'  '''
                update_sql = update_sql.format(
                    out_trade_no=out_trade_no,
                    trade_status=trade_status,
                    status=status,
                    err_code=err_code,
                    err_code_des=err_code_des,
                )
            
            cur_dict.execute(update_sql)
            conn.commit()
        else:
            res = "error: verify failed! "
    except Exception:
        traceback.print_exc()
    finally:
        return weixinpay_response_xml(res)

4.3 订单状态查询

商户app调用此接口查询【微信服务端】某个订单的支付状态,并同步更新商户服务端订单状态。
可根据需要,将此步骤做成异步任务,实时更新商户服务端订单状态。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import traceback
import logging
import xmltodict
import pymysql
import uuid
import json
import requests

from hashlib import md5

MYSQL = dict(
    host='127.0.0.1', user='mysql_user', passwd='mysql_pwd', db='mydb', charset="utf8mb4"
)
logger = logging.getLogger(__name__)
conn = pymysql.connect(**MYSQL)
cur_dict = conn.cursor(pymysql.cursors.DictCursor)
cur = conn.cursor()


###############################################
#############    微信支付配置   #################
###############################################
# 微信支付APP_ID
WEIXIN_APP_ID = 'wx91f04ffbf8a23431'
# 微信支付MCH_ID 【登录账号】
WEIXIN_MCH_ID = '1535411231'
# 微信支付sign_type
WEIXIN_SIGN_TYPE = 'MD5'
# 服务器IP地址
WEIXIN_SPBILL_CREATE_IP = '32.23.11.34'
# 微信支付用途
WEIXIN_BODY = '费用充值'
# 微信KEY值 【API密钥】
WEIXIN_KEY = 'ZiwcVpWomDqixQdhRgm5FpBKNXqwasde'
# 微信统一下单URL
WEIXIN_UNIFIED_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/unifiedorder'
# 微信查询订单URL
WEIXIN_QUERY_ORDER_URL = 'https://api.mch.weixin.qq.com/pay/orderquery'
# 微信支付回调API
WEIXIN_CALLBACK_API = 'http://xxxx.com/weixinpay_rollback/'


def update_order(data):
    """
    查询支付订单,并更新订单
    :param data:
    :return:
    """
    # err_code = data['err_code']  # 错误代码
    # err_code_des = data['err_code_des']  # 错误代码描述
    trade_status = data['result_code']  # 业务结果 SUCCESS/FAIL
    app_id = data['appid']  # 应用ID
    seller_id = data['mch_id']  # 商户号
    if trade_status == "SUCCESS":
        buyer_id = data['openid']  # 用户标识
        total_amount = int(data['total_fee']) / 100  # 总金额(元)
        out_trade_no = data['out_trade_no']  # 商户订单号
        gmt_create = data['time_end']  # 支付完成时间
        trade_no = data['transaction_id']  # 微信支付订单号
        trade_status = data['trade_state']  # 交易状态
        if trade_status == "SUCCESS":
            status = 1
        elif trade_status == "USERPAYING":
            status = 2
        else:
            status = 0
        msg = data['trade_state_desc']
        # SUCCESS—支付成功
        # REFUND—转入退款
        # NOTPAY—未支付
        # CLOSED—已关闭
        # REVOKED—已撤销(刷卡支付)
        # USERPAYING--用户支付中
        # PAYERROR--支付失败(其他原因,如银行返回失败)
        device_info = data['device_info']  # 微信支付分配的终端设备号
        trade_type = data['trade_type']  # 交易类型
        bank_type = data['bank_type']  # 付款银行
        fee_type = data['fee_type']  # 货币种类
        cash_fee = data['cash_fee']  # 现金支付金额(分)
        # cash_fee_type = data['cash_fee_type']  # 现金支付货币类型
        # nonce_str = data['nonce_str']  # 随机字符串
        # coupon_fee = data['coupon_fee']  # 代金券金额
        # coupon_count = data['coupon_count']  # 代金券使用数量
        # coupon_id_$n = data['coupon_id_$n']  # 代金券ID
        # coupon_fee_$n = data['coupon_fee_$n']  # 单个代金券支付金额

        update_sql = ''' update orders set app_id='{app_id}', 
                            seller_id='{seller_id}', buyer_id='{buyer_id}', total_amount='{total_amount}', 
                            out_trade_no='{out_trade_no}', gmt_create='{gmt_create}', trade_no='{trade_no}', 
                            device_info='{device_info}', trade_type='{trade_type}', bank_type='{bank_type}', 
                            fee_type='{fee_type}', cash_fee='{cash_fee}', 
                            status='{status}', 
                            trade_status='{trade_status}' where out_trade_no='{out_trade_no}'  '''
        update_sql = update_sql.format(
            trade_status=trade_status,
            app_id=app_id,
            seller_id=seller_id,
            buyer_id=buyer_id,
            total_amount=total_amount,
            out_trade_no=out_trade_no,
            gmt_create=gmt_create,
            trade_no=trade_no,
            device_info=device_info,
            trade_type=trade_type,
            bank_type=bank_type,
            fee_type=fee_type,
            cash_fee=cash_fee,
            status=status
            # err_code_des=err_code_des,
            # err_code=err_code
        )
        cur_dict.execute(update_sql)
    else:
        msg = trade_status
        status = 0
        update_sql = '''update {table} set  
        trade_status='{trade_status}', app_id='{app_id}', seller_id='{seller_id}', status='{status}' 
        where id={id} and status!=1 '''
        update_sql = update_sql.format(
            # err_code=err_code,
            # err_code_des=err_code_des,
            trade_status=trade_status,
            app_id=app_id,
            seller_id=seller_id,
            id=id,
            status=status
        )
        cur_dict.execute(update_sql)
    conn.commit()
    return status, msg


def make_querypayment_request(params_dict, query_order_url):
    """
    生成查询订单返回的数据参数
    """

    def generate_nonce_str():
        """
        生成随机字符串
        """
        return str(uuid.uuid4()).replace('-', '')

    def generate_sign(params):
        """
        生成md5签名的参数
        """
        if 'sign' in params:
            params.pop('sign')
        src = '&'.join(['%s=%s' % (k, v) for k, v in sorted(params.items())]) + '&key=%s' % WEIXIN_KEY
        return md5(src.encode('utf-8')).hexdigest().upper()

    def generate_request_data(params_dict):
        """
        生成统一下单请求所需要提交的数据
        """
        params_dict['nonce_str'] = generate_nonce_str()
        params_dict['sign'] = generate_sign(params_dict)
        return xmltodict.unparse({'xml': params_dict}, pretty=True, full_document=False).encode('utf-8')

    data = generate_request_data(params_dict)
    headers = {'Content-Type': 'application/xml'}
    res = requests.post(query_order_url, data=data, headers=headers)
    if res.status_code == 200:
        result = json.loads(json.dumps(xmltodict.parse(res.content)))
        # if result['xml']['return_code'] == 'SUCCESS':
        #     prepay_id = result['xml']['prepay_id']
        #     return generate_call_app_data(params_dict, prepay_id)
        # else:
        return result['xml']
    return None


def weixin_orderquery(request):
    """
    【API】:支付状态查询,供商户客户端app调用
    """
    res = {
        'code': 1,
        'status': 0,
        'msg': '支付失败!未知错误!'
    }
    out_trade_no = request.POST.get('out_trade_no')  # 商户订单号
    try:
        select_sql = '''select id, app_id, trade_no, seller_id, status from orders 
        where out_trade_no={out_trade_no} '''
        select_sql = select_sql.format(out_trade_no=out_trade_no)
        cur_dict.execute(select_sql)
        order_data = cur_dict.fetchone()
        if order_data:
            id = order_data['id']
            # 支付成功
            if order_data['status'] == 1:
                res['status'] = 1
                res['msg'] = '支付成功!'
            # 支付失败
            elif order_data['status'] == 0:
                res['status'] = 0
                res['msg'] = '支付失败!'
            # 支付过程中, 查询微信服务器支付状态
            else:
                params_dict = {
                    'appid': order_data['app_id'],
                    'mch_id': order_data['seller_id'],
                    'transaction_id': order_data['trade_no']
                }
                data = make_querypayment_request(params_dict, WEIXIN_QUERY_ORDER_URL)
                if data:
                    if data['return_code'] == 'SUCCESS':
                        trade_status = data['result_code']  # 业务结果  SUCCESS/FAIL
                        if trade_status == "SUCCESS":
                            res['status'], res['msg'] = update_order(data)
                        elif trade_status == "ORDERNOTEXIST":
                            res['msg'] = "支付错误! 微信服务器返回的订单号不存在!"
                            res['status'] = 0
                        elif trade_status == "SYSTEMERROR":
                            res['msg'] = "支付错误! 微信服务器错误!"
                            res['status'] = 0
                        else:
                            res['status'] = 0
                            res['msg'] = "支付错误! 微信服务器支付错误!"
                    else:
                        res['status'] = 0
                        res['msg'] = data['return_msg']

                else:
                    res['msg'] = "支付错误! 微信服务器通信错误!"
        else:
            res['status'] = 0
            res['msg'] = "订单号不存在!"
    except Exception:
        traceback.print_exc()
    finally:
        return json.dumps(res)

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值