2. 网站应用

cookies 获取后,我们要做的就是web应用了:

商品下单 -> 生成订单并产生唯一的编号 -> 用户输入备注支付 -> 确认完成 -> 调用支付验证接口

分析得到需要做的几个步骤:

  • 数据库 orders 存放订单

  • index.html 下单页支付页面

  • /api/101 验证 101 备注订单是否支付成功API

2.1. 项目结构

项目虽小,五脏还是得让他有点样子,这样开发效率与维护才会更舒服。

这一章需要新增加五个扩展:

pipenv install flask requests flask-sqlalchemy pymysql flask-migrate

篇幅有限,这几个扩展就不做详细说明了。

做项目,先整理项目结构:

├── Pipfile
├── Pipfile.lock
├── app # 也是入口文件
│   ├── templates #模板文件,里面一般是按模块分
│   └── __init__.py #app应用的入口文件,一般工厂函数都在这
├── migrations #数据库的版本与迁移,这个目录是flask-migrate自动管理的
└── fetch_cookies.py # 获取 cookies

2.2. 创建支付页面

前端页面我们使用bootstrap制作 app/templates/index.html:

<!doctype html>
<html lang="zh-CN">
<head>
   <!-- Required meta tags -->
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
   <!-- Bootstrap CSS -->
   <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
   <title>支付测试页</title>
   <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>

<body>
   <div id="app" class="container">
      <div class="card mt-4">
            <div class="card-header">
               商品列表
            </div>
            <ul class="list-group list-group-flush">
               <li class="list-group-item">
                  <button type="button" @click="create_order" class="btn btn-primary btn-sm btn-block">下单</button>
               </li>
            </ul>
      </div>

      <div class="card mt-4">
            <div class="card-header"> 订单记录 </div>
            <div class="card-body">
               <div class="table-responsive">

               <table class="table table-bordered text-nowrap">
                  <thead>
                        <tr>
                           <th>#</th>
                           <th>商城订单号</th>
                           <th>微信支付订单号</th>
                           <th>价格</th>
                           <th>支付状态</th>
                           <th>创建时间</th>
                           <th>操作</th>
                        </tr>
                  </thead>
                  <tbody>
                        {% for item in model %}
                        <tr>
                           <th scope="row">{{ loop.index }}</th>
                           <td>{{ item.order_num }}</td>
                           <td>{{ item.trans_id }}</td>
                           <td>{{ item.fee }}</td>
                           <td>{{ item.status }}</td>
                           <td>{{ item.created_at }}</td>
                           <td><button @click="validate_pay({{item.id}})" type="button" class="btn btn-primary btn-sm">检查状态</button></td>
                        </tr>
                        {% endfor %}
                  </tbody>
               </table>
               </div>
            </div>
      </div>
      <!-- Modal -->
      <div class="modal fade" id="create_order" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
            aria-hidden="true">
            <div class="modal-dialog" role="document">
               <div class="modal-content">
                  <div class="modal-header">
                        <h5 class="modal-title" id="exampleModalLabel">下单页</h5>
                        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                           <span aria-hidden="true">&times;</span>
                        </button>
                  </div>
                  <div class="modal-body" v-if="order_info">
                        <div class="alert alert-danger" role="alert">
                           订单号: ${order_info.order_num} <br>
                           备注号: ${order_info.id} <br>
                           扫描二维码支付,在备注处输入你的编号。
                        </div>
                        <img src="https://lsol-house-upload.oss-cn-hangzhou.aliyuncs.com/2019-12-27/a9797efa-82ec-4c3c-9875-c2f86815e647.png" class="rounded mx-auto d-block">
                  </div>
                  <div class="modal-footer">
                        <button type="button" class="btn btn-secondary" data-dismiss="modal">关闭</button>
                        <button type="button" @click="validate_pay(order_info.id)" class="btn btn-primary">已完成支付</button>
                  </div>
               </div>
            </div>
      </div>
   </div>

   <script src="https://report.wxls.pro/static/assets/plugins/jquery/jquery.min.js"></script>
   <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
   <script type="text/javascript">
      var app = new Vue({
         el: '#app',
         delimiters: ['${', '}'],
         data: {
            order_info: null
         },
         created: function () {
         },
         methods: {
            create_order () {
               that = this
               $.get({
                  url: "/create",
                  success: function (res) {
                     if (res.success) {
                        $('#create_order').modal('show')
                        that.order_info = res.data
                     }
                  }
               })
            },
            validate_pay (num) {
               $.get({
                  url: `/api/${num}`,
                  success: function (res) {
                     console.log(res)
                     if (res.success === false) {
                        alert(res.message)
                        return false
                     }
                     alert('获取成功')
                     window.location.reload()
                  }
               })
            }
         }
      })
      $('#create_order').on('hidden.bs.modal', function (e) {
         window.location.reload()
      })
   </script>
</body>

</html>

页面的二维码,放的是微信个人支付那的二维码收款。注意此二维码是可以设置金额的,比如1块,5块。你只要给具体的商品放置相应的固定二维码就可以了。 我上面用的是可以自定义的金额二维码。可以的话,程序在结算验证时,是可以拿到订单的金额与实际收到的金额做下判断就能验证是否收款正确。

2.3. 创建flask工厂应用

app/__init__.py:

from flask import Flask, Blueprint, jsonify, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from datetime import datetime
import requests
import time
import redis
import json
import uuid


db = SQLAlchemy()
migrate = Migrate()
r = redis.Redis(host='localhost', port=6379, db=3)


# 创建应用工厂
def create_app():
   app = Flask(__name__)
   app.config.from_object(Config)

   db.init_app(app)
   migrate.init_app(app, db)

   register_buleprints(app)

   return app


# 订单蓝本
def orders_blueprint():
   bp = Blueprint('orders', 'orders')

   @bp.route('/')
   def index():
      model = Orders.query.order_by(Orders.id.desc()).limit(50).all()
      return render_template('index.html', model=model)

   @bp.route('/create')
   def create():
      model = Orders()
      model.order_num = ''.join(str(uuid.uuid4()).split('-')).upper()
      db.session.add(model)
      db.session.commit()

      return jsonify(success=True, data=model.to_dict())

   return bp


# API蓝本
def api_blueprint():
   bp = Blueprint('api', 'api')

   @bp.route('/<no>')
   def index(no):
      lists_url = 'https://wx.tenpay.com/userroll/userrolllist?classify_type=0&count=20'
      detail_url = 'https://wx.tenpay.com/userroll/userrolldetail?trans_id={}'

      cookies = json.loads(r.get('wechat_cookies'))

      res = requests.get(lists_url, cookies=cookies)

      # cookies 失效 发送信号让代理重新抓取cookies
      if res.json().get('ret_code') == 268511753:
            return jsonify(success=False, message='cookies失效了,重新获取')

      for item in res.json().get('record'):
            if item['timestamp'] > int(time.time()) - 77300:
               detail_res = requests.get(detail_url.format(item['trans_id']), cookies=cookies)
               # 条件满足,就修改订单参数
               if detail_res.json()['preview'][3]['value'][0]['name'] == no:
                  order = Orders.query.get(no)
                  order.trans_id = item['trans_id']
                  order.fee = item['fee']
                  order.status = 8
                  db.session.commit()
                  return jsonify(data=order.to_dict())

      return jsonify(success=False, message='没有查到此订单记录')

   return bp


# 注册蓝本
def register_buleprints(app):
   # frontend
   app.register_blueprint(orders_blueprint())
   app.register_blueprint(api_blueprint(), url_prefix='/api')


# 配置类
class Config(object):
   SQLALCHEMY_DATABASE_URI = "mysql+pymysql://用户名:密码@地址/数据库"
   SQLALCHEMY_TRACK_MODIFICATIONS = False


# 订单模型
class Orders(db.Model):
   id = db.Column(db.Integer, primary_key=True)
   order_num = db.Column(db.String(255))
   trans_id = db.Column(db.String(255))
   fee = db.Column(db.Integer)
   status = db.Column(db.Integer, default=9)

   created_at = db.Column(db.DateTime, default=datetime.now, index=True)

   # 序列化
   def to_dict(self, content=False):
      res_data = {
            'id': self.id,
            'order_num': self.order_num,
            'trans_id': self.trans_id,
            'status': self.status,
            'created_at': self.created_at
      }
      return res_data

注意:mysql自行搭建,分配账号密码。 因为这文章并不是专门介绍flask的,所以就不展开讲了,要不然讲几天都讲不完。有不懂的,可以自行百度。 有必要说一下的,就是上面的一些方法,要是以项目来开发的,是都分配到包里的,也就是需要做项目的目录结构合理编排。

2.4. 创建订单数据库

代码编写好后,就是初始化数据库了:

$ pipenv run flask db init
$ pipenv run flask db migrate
$ pipenv run flask db upgrade

一切准备就绪,现在启动应用:

$ pipenv shell
$ export FLASK_ENV=development
$ flask run
* Environment: development
* Debug mode: on
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 961-316-096

演示图:

https://lsol-house-upload.oss-cn-hangzhou.aliyuncs.com/2019-12-30/8166120a-c129-4a86-b59f-4e336f781292.png

此时,关键的流程就走完了。其它需要做的就是自已业务的改善。