python 微信开发入门篇-微信支付回调+对账单下载(三)

安装  django-simpleui,django-import-export

pip install django-simpleui
pip install django-import-export

settings.py 配置文件如下

"""
Django settings for wechatDemo project.

Generated by 'django-admin startproject' using Django 2.2.6.

For more information on this file, see
https://docs.djangoproject.com/en/2.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/2.2/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '#!2+_0h*+v-i&)yw0d(kkj_&p9smf&6^h0vakxv!^#@i&y3cct'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = ["*"]


# Application definition

INSTALLED_APPS = [
    'simpleui',
    'import_export',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app'
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'wechatDemo.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')]
        ,
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
            'libraries': {
                'apptags': 'app.templatetags.apptags'
            },
        },
    },
]

WSGI_APPLICATION = 'wechatDemo.wsgi.application'


# Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/2.2/topics/i18n/

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

USE_I18N = True

USE_L10N = True

USE_TZ = False


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/

STATIC_URL = '/static/'

项目完整目录结构: 

 

数据库设计(商品表,订单表,对账表,对账详细表)

 项目中admin.py 源码如下

from django.db import models


# Create your models here.
# 商品表
class commodity(models.Model):
    name = models.CharField(max_length=225, verbose_name="商品名称", blank=True, default="")
    desc = models.TextField(verbose_name="商品描述", blank=True)
    price = models.FloatField(verbose_name="价格", default=0)

    class Meta:
        verbose_name_plural = "商品表"

    def __str__(self):
        return self.name


# 订单表
class order(models.Model):
    ordercode = models.CharField(max_length=255, verbose_name="订单号")
    openid = models.CharField(max_length=255, verbose_name="openid", default="")
    commodity = models.ManyToManyField(to='commodity', verbose_name="商品表", null=True, blank=True)
    STATE_CHOICES = (
        (0, '待支付'),
        (1, '已支付')
    )
    state = models.CharField(max_length=2, choices=STATE_CHOICES, default=0, )

    # 支付返回结果
    appid = models.CharField(max_length=255, verbose_name="appid", default="")
    bank_type = models.CharField(max_length=255, verbose_name="bank_type", default="")
    cash_fee = models.IntegerField(verbose_name="支付金额(分)", default=0)
    fee_type = models.CharField(max_length=255, verbose_name="支付类型", default="")
    is_subscribe = models.CharField(max_length=255, verbose_name="is_subscribe", default="")
    mch_id = models.CharField(max_length=255, verbose_name="mch_id", default="")
    nonce_str = models.CharField(max_length=255, verbose_name="nonce_str", default="")
    out_trade_no = models.CharField(max_length=255, verbose_name="商户订单号", default="")
    result_code = models.CharField(max_length=255, verbose_name="result_code", default="")
    return_code = models.CharField(max_length=255, verbose_name="return_code", default="")
    time_end = models.CharField(max_length=255, verbose_name="time_end", default="")
    total_fee = models.CharField(max_length=255, verbose_name="total_fee", default="")
    trade_type = models.CharField(max_length=255, verbose_name="trade_type", default="")
    transaction_id = models.CharField(max_length=255, verbose_name="transaction_id", default="")
    sign = models.CharField(max_length=255, verbose_name="sign", default="")


# 对账表
class bill(models.Model):
    jysh = models.CharField(max_length=255, verbose_name="交易时间", default="")
    appid = models.CharField(max_length=255, verbose_name="公众账号ID", default="")
    mch_id = models.CharField(max_length=255, verbose_name="商户号", default="")
    tyshh = models.CharField(max_length=255, verbose_name="特约商户号", default="")
    sbh = models.CharField(max_length=255, verbose_name="设备号", default="")
    wxdd = models.CharField(max_length=255, verbose_name="微信订单号", default="")
    shdd = models.CharField(max_length=255, verbose_name="商户订单号", default="")
    openid = models.CharField(max_length=255, verbose_name="用户标识", default="")
    jylx = models.CharField(max_length=255, verbose_name="交易类型", default="")
    jyzt = models.CharField(max_length=255, verbose_name="交易状态", default="")
    fkyh = models.CharField(max_length=255, verbose_name="付款银行", default="")
    hbzl = models.CharField(max_length=255, verbose_name="货币种类", default="")
    yjddje = models.CharField(max_length=255, verbose_name="应结订单金额", default="")
    djjje = models.CharField(max_length=255, verbose_name="代金券金额", default="")
    wxtkdh = models.CharField(max_length=255, verbose_name="微信退款单号", default="")
    hstkdh = models.CharField(max_length=255, verbose_name="商户退款单号", default="")
    tkje = models.CharField(max_length=255, verbose_name="退款金额", default="")
    czjtkje = models.CharField(max_length=255, verbose_name="充值券退款金额", default="")
    tklx = models.CharField(max_length=255, verbose_name="退款类型", default="")
    tkzt = models.CharField(max_length=255, verbose_name="退款状态", default="")
    spmc = models.CharField(max_length=255, verbose_name="商品名称", default="")
    shsjb = models.CharField(max_length=255, verbose_name="商户数据包", default="")
    sxf = models.CharField(max_length=255, verbose_name="手续费", default="")
    fx = models.CharField(max_length=255, verbose_name="费率", default="")
    ddje = models.CharField(max_length=255, verbose_name="订单金额", default="")
    sqtkje = models.CharField(max_length=255, verbose_name="申请退款金额", default="")
    flbz = models.CharField(max_length=255, verbose_name="费率备注", default="")

执行数据库迁移

python manage.py makemigrations
python manage.py migrate

views.py 源码

from wechatpy.oauth import WeChatOAuth
from django.shortcuts import render, redirect
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
import time
import datetime
from django.conf import settings
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render
import uuid
from wechatpy import WeChatClient
import os
import json
from wechatpy import WeChatPay
from app import models
from wechatpy.pay import dict_to_xml

# Create your views here.

# 公众号id
AppID = "appid"
# 公众号AppSecret
AppSecret = "appsecret"

# 商户id
MCH_ID = 'mch_id'
# 商户API秘钥
API_KEY = 'api_key'
# 接收微信支付异步通知回调地址
notify_url = "http://i157422s94.iok.la/wxjssdk/"


# 微信认证文件,建议通过nginx配置
def wechatauth(request):
    return HttpResponse("b1reLtO1xRzEjqxJ")


# 定义授权装饰器

def getWeChatOAuth(redirect_url):
    return WeChatOAuth(AppID, AppSecret, redirect_url, 'snsapi_userinfo')


def oauth(method):
    def warpper(request):
        if request.session.get('user_info', None) is None:
            code = request.GET.get('code', None)
            wechat_oauth = getWeChatOAuth(request.get_raw_uri())
            url = wechat_oauth.authorize_url
            print(url)
            if code:
                try:
                    wechat_oauth.fetch_access_token(code)
                    user_info = wechat_oauth.get_user_info()
                    print(user_info)
                except Exception as e:
                    print(str(e))
                    # 这里需要处理请求里包含的 code 无效的情况
                    # abort(403)
                else:
                    # 建议存储在用户表
                    request.session['user_info'] = user_info
            else:
                return redirect(url)

        return method(request)

    return warpper


# 获取用户信息UserInfo

@oauth
def userinfo(request):
    user_info = request.session.get('user_info')
    return render(request, 'userinfo.html', {"user_info": user_info})


# 微信JS SDK调用
@oauth
def wxjssdk(request):
    user_info = request.session.get('user_info')
    trade_type = "JSAPI"
    body = "商品描述"
    total_fee = "10"
    notify_url = "http://wwww.wezoz.com/notify_url/"
    user_id = user_info["openid"]
    wechatPay = WeChatPay(
        appid=AppID,
        api_key=API_KEY,
        mch_id=MCH_ID,
    )

    order = wechatPay.order.create(trade_type, body, total_fee, notify_url, user_id=user_id)
    wxpay_params = wechatPay.jsapi.get_jsapi_params(order['prepay_id'])

    print(wxpay_params)
    return render(request, 'index.html', {"wxpay_params": wxpay_params})


@oauth
def commodity(request):
    commoditys = models.commodity.objects.all()
    return render(request, 'commodity.html', {"commoditys": commoditys})


@csrf_exempt
def order_jsapi(request):
    user_info = request.session.get('user_info')
    cid = request.POST.get("cid")
    cty = models.commodity.objects.filter(id=cid).first()
    trade_type = "JSAPI"
    body = cty.desc
    total_fee = int(cty.price * 100)
    notify_url = "http://wwww.wezoz.com/notify_url/"
    user_id = user_info["openid"]
    wechatPay = WeChatPay(
        appid=AppID,
        api_key=API_KEY,
        mch_id=MCH_ID,
    )

    order = wechatPay.order.create(trade_type, body, total_fee, notify_url, user_id=user_id, detail=cty.name,
                                   attach=cid)
    print(order)
    wxpay_params = wechatPay.jsapi.get_jsapi_params(order['prepay_id'])
    print(wxpay_params)
    prepay_id = order["prepay_id"]  # 微信订单号

    params = wechatPay.order.get_appapi_params(prepay_id)
    print(params)

    obj = models.order.objects.create(ordercode=prepay_id, openid=user_id)
    obj.commodity.add(cid)
    return JsonResponse(wxpay_params)


@csrf_exempt
def notify_url(request):
    wechatPay = WeChatPay(
        appid=AppID,
        api_key=API_KEY,
        mch_id=MCH_ID,
    )
    result = wechatPay.parse_payment_result(request.body)
    print(result)
    query = wechatPay.order.query(result["transaction_id"])
    print(query)
    attach = result["attach"]  # 附加参数

    # 此处验证需要添加业务逻辑,只通过openid 验证不严谨

    ord = models.order.objects.filter(openid=result["openid"]).first()
    ord.appid = result["appid"]
    ord.bank_type = result["bank_type"]
    ord.cash_fee = result["cash_fee"]
    ord.fee_type = result["fee_type"]
    ord.is_subscribe = result["is_subscribe"]
    ord.mch_id = result["mch_id"]
    ord.nonce_str = result["nonce_str"]
    ord.out_trade_no = result["out_trade_no"]
    ord.result_code = result["result_code"]
    ord.return_code = result["return_code"]
    ord.time_end = result["time_end"]
    ord.total_fee = result["total_fee"]
    ord.trade_type = result["trade_type"]
    ord.transaction_id = result["transaction_id"]
    ord.sign = result["sign"]
    ord.state = 1
    ord.save()
    print(result)

    return HttpResponse("SUCCESS")


def validate_date_str(date_str):
    try:
        datetime.datetime.strptime(date_str, '%Y-%m-%d')
        return True
    except ValueError:
        return False


# 访问地址如下  http://127.0.0.1:8009/downloadBill/2019-10-23.html
@csrf_exempt
def downloadBill(request, date):
    if validate_date_str(date):
        wechatPay = WeChatPay(
            appid=AppID,
            api_key=API_KEY,
            mch_id=MCH_ID
        )
        result = wechatPay.tools.download_bill(date.replace("-", ""))
        print(result)

        billArray = result.split("\r\n")  # 分割账单,一行为一组数据,分割后第一行为数据标题,倒数第三行为统计标题,倒数第二行为统计金额,最后一行为多余的空行
        titleArray = billArray[0].split(',')  # 第一行为标题
        title_total = billArray[len(billArray) - 2]  # 统计标题
        data_total = billArray[len(billArray) - 1]  # 统计金额
        del billArray[0]  # 去掉标题
        del billArray[len(billArray) - 3]  # 去掉总标题
        del billArray[len(billArray) - 2]  # 去掉总额
        del billArray[len(billArray) - 1]  # 去掉空行,剩下的为账单详情数据
        mybill = []  # 订单详细信息
        # 循环账单详情数据
        for i in billArray:
            _detail = i.split('`')[:-1]
        del _detail[0]  # 去掉前边的空数据
        _detail_temp = []
        for d in _detail:
            # 每一个数据(去掉最后的逗号)
            _detail_val = d[:-1]
            _detail_temp.append(_detail_val)
            # TODO业务处理
            # print(d[:-1])
            # TODO业务处理
            mybill.append(_detail_temp)
        # 账单入库 需要两张表 日账单表-->日账单对应的详细表

        # 对账逻辑需自行处理结合 订单表 + 对账表 逐条勾兑,建议对账操作放在数据库进行

        # 对账调度 Celery 下一章介绍

    else:
        print("日期格式输入有误!")
    return HttpResponse("SUCCESS")


@csrf_exempt
def jsapi_signature(request):
    noncestr = uuid.uuid4()
    timestamp = int(time.time())
    url = request.POST['url']

    client = WeChatClient(AppID, AppSecret)
    ticket_response = client.jsapi.get_ticket()
    signature = client.jsapi.get_jsapi_signature(
        noncestr,
        ticket_response['ticket'],
        timestamp,
        url
    )
    ret_dict = {
        'noncestr': noncestr,
        'timestamp': timestamp,
        'url': url,
        'signature': signature,
    }
    return JsonResponse(ret_dict)


def log(request):
    print('Hello World!')
    return JsonResponse({
        'status': 'ok',
    })

templates-->commodity.html

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html
        PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>商品展示</title>
    <meta content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" name="viewport"/>
    <meta content="yes" name="apple-mobile-web-app-capable"/>
    <meta content="black" name="apple-mobile-web-app-status-bar-style"/>
    <meta content="telephone=no" name="format-detection"/>
    <!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
          integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

    <!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-theme.min.css"
          integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
    <script type="text/javascript" src="https://www.szyfd.xyz/static/js/jquery-3.4.1.js"></script>
    <!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"
            integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa"
            crossorigin="anonymous"></script>
</head>
<body>
<div class="container-fluid">
    <table class="table table-bordered table-striped">
        <thead>
        <tr>
            <th>商品名称</th>
            <th>商品价格</th>
        </tr>
        </thead>
        <tbody>
        {% for c in commoditys %}
            <tr>
                <th scope="row">
                    <code>{{ c.name }}</code>
                </th>
                <td>价格{{ c.price }}
                    <button type="button" class="btn btn-primary requestOrder" cid="{{ c.id }}"
                            style="margin-left: 20px">点击支付
                    </button>
                </td>
            </tr>
        {% endfor %}

        </tbody>
    </table>
</div>
<!--请勿应用外网js 链接-->

<script type="text/javascript">

    $(function () {
        $.ajaxSetup({
            beforeSend: function (xhr, settings) {
                xhr.setRequestHeader('X-CSRFtoken', $.cookie('csrftoken'));
            }
        });


    })

</script>
<script type="text/javascript" src="https://www.szyfd.xyz/static/js/jquery.cookie.js"></script>
<!--end-->
<script type="text/javascript" src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<script type="text/javascript">
    (function (window, $) {
        var fInitWeixin = function (d) {
            wx.config({
                debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
                appId: 'wx975992a7ef6d6c9d', // 必填,公众号的唯一标识
                timestamp: d.timestamp, // 必填,生成签名的时间戳
                nonceStr: d.noncestr, // 必填,生成签名的随机串
                signature: d.signature,// 必填,签名,见附录1
                jsApiList: ['checkJsApi',
                    'onMenuShareTimeline',
                    'onMenuShareAppMessage',
                    'onMenuShareQQ',
                    'onMenuShareWeibo',
                    'onMenuShareQZone',
                    'hideMenuItems',
                    'showMenuItems',
                    'hideAllNonBaseMenuItem',
                    'showAllNonBaseMenuItem',
                    'translateVoice',
                    'startRecord',
                    'stopRecord',
                    'onVoiceRecordEnd',
                    'playVoice',
                    'onVoicePlayEnd',
                    'pauseVoice',
                    'stopVoice',
                    'uploadVoice',
                    'downloadVoice',
                    'chooseImage',
                    'previewImage',
                    'uploadImage',
                    'downloadImage',
                    'getNetworkType',
                    'openLocation',
                    'getLocation',
                    'hideOptionMenu',
                    'showOptionMenu',
                    'closeWindow',
                    'scanQRCode',
                    'chooseWXPay',
                    'openProductSpecificView',
                    'addCard',
                    'chooseCard',
                    'openCard'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
            });
        }
        var s = $.ajax({
            type: 'post',
            url: '/get_signature/',
            dataType: 'json',
            data: {url: location.href},
            success: function (d) {
                fInitWeixin(d)
            }
        })
    })(window, jQuery)
</script>

<script type="text/javascript">

    /*
 * 注意:
 * 1. 所有的JS接口只能在公众号绑定的域名下调用,公众号开发者需要先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。
 * 2. 如果发现在 Android 不能分享自定义内容,请到官网下载最新的包覆盖安装,Android 自定义分享接口需升级至 6.0.2.58 版本及以上。
 * 3. 完整 JS-SDK 文档地址:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
 *
 * 如有问题请通过以下渠道反馈:
 * 邮箱地址:[email protected]
 * 邮件主题:【微信JS-SDK反馈】具体问题
 * 邮件内容说明:用简明的语言描述问题所在,并交代清楚遇到该问题的场景,可附上截屏图片,微信团队会尽快处理你的反馈。
 */
    wx.ready(function () {
        // 1 判断当前版本是否支持指定 JS 接口,支持批量判断
        document.querySelector('#checkJsApi').onclick = function () {
            wx.checkJsApi({
                jsApiList: [
                    'getNetworkType',
                    'previewImage'
                ],
                success: function (res) {
                    alert(JSON.stringify(res));
                }
            });
        };


        shareData = {
            title: '深圳易方达软件',
            desc: '按时交付完美主义者',
            link: location.href,
            imgUrl: 'https://www.szyfd.xyz/static/HOME/style/images/shareLogo.png',
            trigger: function (res) {
                // 不要尝试在trigger中使用ajax异步请求修改本次分享的内容,因为客户端分享操作是一个同步操作,这时候使用ajax的回包会还没有返回

            },
            success: function (res) {

            },
            cancel: function (res) {

            },
            fail: function (res) {
                //alert(JSON.stringify(res));
            }
        }
        // 2. 分享接口

        wx.onMenuShareAppMessage(shareData);

        wx.onMenuShareTimeline(shareData);

        wx.onMenuShareQQ(shareData);

        wx.onMenuShareWeibo(shareData);

        wx.onMenuShareQZone(shareData);

        function decryptCode(code, callback) {

        }

    });

    wx.error(function (res) {
        alert(res.errMsg);
    });
    $(".requestOrder").click(function () {
        currentThis = $(this);
        cid = $(currentThis).attr("cid");
        $.ajax({
            type: 'post',
            url: '/order_jsapi',
            dataType: 'json',
            data: {cid: cid},
            success: function (d) {
                wx.chooseWXPay({
                    timestamp: d.timeStamp,
                    nonceStr: d.nonceStr,
                    package: d.package,
                    signType: d.signType, // 注意:新版支付接口使用 MD5 加密
                    paySign: d.paySign
                });
            }
        })
    })

</script>

</body>
</html>

手机效果图:

后台效果图:

 商品表

订单表

12-28 20:53