day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作

目录

1.种植园商城页面初始化

2.规划商品种类并且构建关于商品的模型类

3.解决APP打包编译之后的跨域限制

4.商品列表后端接口实现

5.前端获取商品列表并显示

6.种植园点击充值允许用户选择充值金额

7.将AlipayPlus模块加载到APP上

8.集成Alipayplus模块完成支付

9.增加用户充值订单模型类

1.种植园商城页面初始化

1.种植园点击商店会去到种植园商城页面

orchard.html,代码:

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作
<!DOCTYPE html>
<html>
<head>
    <title>用户中心</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta charset="utf-8">
    <link rel="stylesheet" href="../static/css/main.css">
    <script src="../static/js/vue.js"></script>
    <script src="../static/js/axios.js"></script>
    <script src="../static/js/main.js"></script>
    <script src="../static/js/uuid.js"></script>
    <script src="../static/js/settings.js"></script>
    <script src="../static/js/socket.io.js"></script>
</head>
<body>
    <div class="app orchard" id="app">
    <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
    <div class="orchard-bg">
            <img src="../static/images/bg2.png">
            <img class="board_bg2" src="../static/images/board_bg2.png">
        </div>
    <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
    <div class="header">
            <div class="info" @click="go_home">
                <div class="avatar">
                    <img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
                    <img class="user_avatar" src="../static/images/avatar.png" alt="">
                    <img class="avatar_border" src="../static/images/avatar_border.png" alt="">
                </div>
                <p class="user_name">好听的昵称</p>
            </div>
            <div class="wallet">
                <div class="balance">
                    <p class="title"><img src="../static/images/money.png" alt="">钱包</p>
                    <p class="num">99,999.00</p>
                </div>
                <div class="balance">
                    <p class="title"><img src="../static/images/integral.png" alt="">果子</p>
                    <p class="num">99,999.00</p>
                </div>
            </div>
      <div class="menu-list">
        <div class="menu">
          <img src="../static/images/menu1.png" alt="">
          排行榜
        </div>
        <div class="menu">
          <img src="../static/images/menu2.png" alt="">
          签到有礼
        </div>
        <!-- ***种植园点击商城去到商城界面*** -->
        <div class="menu" @click="go_orchard_shop">
          <img src="../static/images/menu3.png" alt="">
          道具商城
        </div>
        <div class="menu">
          <img src="../static/images/menu4.png" alt="">
          邮件中心
        </div>
      </div>
        </div>
    <div class="footer" >
      <ul class="menu-list">
        <li class="menu">新手</li>
        <li class="menu">背包</li>
        <li class="menu-center" @click="go_orchard_shop">商店</li>
        <li class="menu">消息</li>
        <li class="menu">好友</li>
      </ul>
    </div>
    </div>
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
          music_play:true,
          namespace: '/mofang_orchard',
          token:"",
          socket: null,
          timeout: 0,
                    prev:{name:"",url:"",params:{}},
                    current:{name:"orchard",url:"orchard.html",params:{}},
                }
            },
      created(){
        this.game.goFrame("orchard","my_orchard.html", this.current,{
            x: 0,
            y: 180,
            w: 'auto',
            h: 'auto',
        },null);
        this.checkout();
      },
            methods:{
        checkout(){
          var token = this.game.get("access_token") || this.game.fget("access_token");
          this.game.checkout(this,token,(new_access_token)=>{
            this.connect();
          });
        },
        connect(){
          // socket连接
          this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
          this.socket.on('connect', ()=>{
              this.game.print("开始连接服务端");
          });
        },
        go_index(){
          this.game.outWin("orchard");
        },
        go_friends(){
          this.game.goFrame("friends","friends.html",this.current);
          this.game.goFrame("friend_list","friend_list.html",this.current,{
              x: 0,
              y: 190,
              w: 'auto',
              h: 'auto',
          },null,true);
        },
        go_home(){
          this.game.goWin("user","user.html", this.current);
        },
                 // ***种植园点击商城去到商城界面***
                go_orchard_shop(){
                    // 种植园商店
                    this.game.goFrame("orchard_shop","shop.html", this.current,null,{
              type:"push",
              subType:"from_top",
              duration:300
          });
                }
            }
        });
    }
    </script>
</body>
</html>
种植园点击商城跳转到商城界面

2.种植园商城页面初始化

新建种植园商城页面shop.html,代码:

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作
<!DOCTYPE html>
<html>
<head>
    <title>商店</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta charset="utf-8">
    <link rel="stylesheet" href="../static/css/main.css">
    <script src="../static/js/vue.js"></script>
    <script src="../static/js/axios.js"></script>
    <script src="../static/js/main.js"></script>
    <script src="../static/js/uuid.js"></script>
    <script src="../static/js/settings.js"></script>
</head>
<body>
    <div class="app frame avatar update_nickname add_friend shop" id="app">
    <div class="box">
      <p class="title">商店</p>
      <img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
      <div class="friends_list shop_list">
        <div class="item">
          <div class="avatar shop_item">
            <img src="../static/images/fruit_tree.png" alt="">
          </div>
          <div class="info">
            <p class="username">果树</p>
            <p class="time">200</p>
          </div>
          <div class="status">200</div>
        </div>
      </div>
    </div>
    </div>
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
                    user_id: "", // 当前登陆用户Id
                    prev:{name:"",url:"",params:{}},
                    current:{name:"orchard",url:"shop.html",params:{}},
                }
            },

            created(){
                this.user_id = this.game.get("id") || this.game.fget("id");
            },
            methods:{
        close_frame(){
          this.game.outFrame("orchard_shop");
        },
            }
        });
    }
    </script>
</body>
</html>
种植园商城页面初始化:shop.html

种植园商城页面CSS样式main.css,代码:

.shop .shop_list{
  margin-left: 1rem;
  margin-top: -5rem;
}

2.规划商品种类并且构建关于商品的模型类

1.先规划好商品的种类以及相关参数

商店的商品:
    1. 种子
       果树
          标题
          价格
          描述
          图片
          使用流程相关
              状态: 种子期, 成长期, 成熟期, 树桩
              种子期: 3  x 60 x 60
              成长期: 6  x 60 x 60
              成熟期: 12 x 60 x 60
              树桩 :  -1
    2. 宠物
       小狗1,小狗2,小狗3,小狗4
          标题
          价格
          描述
          图片
          使用流程相关:
              饱食度: <20%(饥饿) <50%(正常) <100%(饱腹)
              生命期: -1(永久)
              保护命中率  : 10%   0-1   <10%
    3. 狗粮
       狗粮1,狗粮2,狗粮3
          标题
          价格
          描述
          图片
          使用流程相关:
              饱食度: 20%
              有效期: 
    4. 道具
       化肥,....
          标题
          价格
          描述
          图片
          使用流程相关:
          缩短时间: 1小时
    

2.商品基本信息的模型类

apps/orchard/models.py,模型,代码:

from application.utils.models import BaseModel,db
class Goods(BaseModel):
    """商品基本信息"""
    __tablename__ = "mf_goods"
    remark = db.Column(db.String(255), comment="商品描述")
    price = db.Column(db.Numeric(7,2), comment="商品价格")
    image = db.Column(db.String(255),  comment="商品图片")

3.为用户添加果子积分字段

apps/users/models.py,模型代码:

from werkzeug.security import generate_password_hash, check_password_hash
from application.utils.models import BaseModel,db
class User(BaseModel):
    """用户基本信息"""
    __tablename__ = "mf_user"
     
    # 为用户表添加果子积分字段
    credit = db.Column(db.Numeric(7,2),default=0, comment="果子积分")
    

4.将商品基本信息模型注册到admin中

在admin站点中进行模型管理注册,apps/orchard/admin.py,代码:

# 根据模型自动生成页面
from .models import Goods
from flask_admin.contrib.sqla import ModelView
from application import admin,db
class GoodsAdminModel(ModelView):
    # 列表页显示字段列表
    column_list = ["id","name","price"]
    # 列表页可以直接编辑的字段列表
    column_editable_list = ["price"]
    # 是否允许查看详情
    can_view_details = True
    # 列表页显示直接可以搜索数据的字典
    column_searchable_list = ['name', 'price']
    # 过滤器
    column_filters = ['name']
    # 单页显示数据量
    page_size = 10

admin.add_view(GoodsAdminModel(Goods,db.session,name="商品", category="种植园"))

5.在商城里添加一些道具(植物 狗粮 化肥等)

INSERT INTO mofang.mf_goods 
(id, name, is_deleted, orders, status, created_time, updated_time, remark, price, image) 
VALUES 
(1, '果树', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '果树', 10.00, null),
(2, '小狗1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗1号', 100.00, null),
(3, '小狗2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗2号', 200.00, null),
(4, '小狗3号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗3号', 300.00, null),
(5, '小狗4号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '小狗4号', 100.00, null),
(6, '狗粮1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '狗粮1号', 10.00, null),
(7, '狗粮2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '狗粮2号', 5.00, null),
(8, '化肥1号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥1号', 3.00, null),
(9, '化肥2号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥2号', 6.00, null),
(10, '化肥3号', 0, 0, 1, '2020-12-28 16:09:39', '2020-12-28 16:09:39', '化肥3号', 9.00, null);

3.解决APP打包编译之后的跨域限制

解决app打包生成以后,页面无法请求服务端数据的跨域问题。

无法获取数据的原因是,当前APP中获取数据是通过ajax来发送请求的,因为我们当前的APP是混合APP,所以实际来说,这种混合APP就是一个浏览器内核构建的。因此也会存在同源策略的访问限制,因此我们需要在服务端实现跨域资源共享。

服务端终端运行:

pip install -U flask-cors

CORS初始化:

application/__init__.py,代码:

from flask_cors import CORS

# flask_cors
cors = CORS()

def init_app(config_path):
    """全局初始化"""
  
    # cors
    cors.init_app(app,resources={r"/api/*": {"origins": "*"}})

4.商品列表后端接口实现

1.视图部分

提供商品列表给客户端,apps/orchard/views.py, 代码:

from application import jsonrpc
from status import APIStatus as status
from message import ErrorMessage as message
from application import redis
from .models import Goods
from application.apps.users.models import User
from flask_jwt_extended import jwt_required,get_jwt_identity
from .marshmallow import GoodsInfoSchema
@jsonrpc.method(name="Orchard.goods.list")
@jwt_required  # 验证jwt
def goods_list(page=1,limit=10):
    """商品列表"""
    current_user_id = get_jwt_identity()
    user = User.query.get(current_user_id)
    if user is None:
        return {
            "errno": status.CODE_NO_USER,
            "errmsg": message.user_not_exists,
        }

    pagination = Goods.query.filter(
        Goods.is_deleted==False,
        Goods.status==True
    ).paginate(page, per_page=limit)
    
    # 转换数据格式
    gis = GoodsInfoSchema()
    goods_list = gis.dump(pagination.items,many=True)

    return {
        "errno": status.CODE_OK,
        "errmsg": message.ok,
        "goods_list": goods_list,
        "pages": pagination.pages
    }

2.序列化器部分

marshmallow.py,代码:

from message import ErrorMessage as Message
from .models import Goods,db
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema,auto_field
from marshmallow import post_dump
class GoodsInfoSchema(SQLAlchemyAutoSchema):
    id = auto_field()
    name = auto_field()
    price = auto_field()
    image = auto_field()
    remark = auto_field()

    class Meta:
        model = Goods
        fields = ["id","name","price","image","remark"]
        sql_session = db.session

    @post_dump()
    def mobile_format(self, data, **kwargs):
        data["price"] = "%.2f" % data["price"]
        if data["image"] == None:
            data["image"] = ""
        return data

5.前端获取商品列表并显示

客户端就可以在打开商品的时候, 获取商品列表,shop.html代码:

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作
<!DOCTYPE html>
<html>
<head>
    <title>商店</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta charset="utf-8">
    <link rel="stylesheet" href="../static/css/main.css">
    <script src="../static/js/vue.js"></script>
    <script src="../static/js/axios.js"></script>
    <script src="../static/js/main.js"></script>
    <script src="../static/js/uuid.js"></script>
    <script src="../static/js/settings.js"></script>
</head>
<body>
    <div class="app frame avatar update_nickname add_friend shop" id="app">
    <div class="box">
      <p class="title">商店</p>
      <img class="close" @click="close_frame" src="../static/images/close_btn1.png" alt="">
      <div class="friends_list shop_list">
        <div class="item" v-for="goods in goods_list">
          <div class="avatar shop_item">
            <img :src="settings.static_url+goods.image" alt="">
          </div>
          <div class="info">
            <p class="username">{{goods.name}}</p>
            <p class="time">{{goods.remark}}</p>
          </div>
          <div class="status">{{goods.price}}</div>
        </div>
      </div>
    </div>
    </div>
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
                    user_id: "", // 当前登陆用户Id
          goods_list:[], // 商品列表
          page: 1,
          limit: 10,
          is_send_ajax:false,
                    prev:{name:"",url:"",params:{}},
                    current:{name:"orchard",url:"shop.html",params:{}},
                }
            },
            created(){
                this.user_id = this.game.get("id") || this.game.fget("id");
        this.get_goods_list();
            },
            methods:{
        close_frame(){
          this.game.outFrame("orchard_shop");
        },
        // ***获取商品列表***
        get_goods_list(){
                    if(this.is_send_ajax){
                        return ;
                    }
                    // 通过请求获取当前用户的好友列表
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.is_send_ajax = true;
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Orchard.goods.list",
                            "params": {
                                "page": this.page,
                "limit": this.limit,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            if(parseInt(response.data.result.errno)==1000){
                                if(this.page+1 == response.data.result.pages){
                                    this.is_send_ajax = true;
                                }else{
                                    this.is_send_ajax = false;
                                    this.page+=1;
                                }
                                if(this.page>1){
                                    api.refreshHeaderLoadDone();
                                }
                                this.goods_list = response.data.result.goods_list.concat(this.goods_list);
                            }else if(parseInt(response.data.result.errno) == 1008){
                                this.friends = [];
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 网络等异常
                            this.game.print(error);
                        });
                    })
                },
            }
        });
    }
    </script>
</body>
</html>
前端获取商品列表并显示

6.种植园点击充值允许用户选择充值金额

orchard.html页面中, 当用户点击充值功能时允许用户设置充值金额, 代码:

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作
<!DOCTYPE html>
<html>
<head>
    <title>用户中心</title>
    <meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
    <meta charset="utf-8">
    <link rel="stylesheet" href="../static/css/main.css">
    <script src="../static/js/vue.js"></script>
    <script src="../static/js/axios.js"></script>
    <script src="../static/js/main.js"></script>
    <script src="../static/js/uuid.js"></script>
    <script src="../static/js/settings.js"></script>
    <script src="../static/js/socket.io.js"></script>
</head>
<body>
    <div class="app orchard" id="app">
    <img class="music" :class="music_play?'music2':''" @click="music_play=!music_play" src="../static/images/player.png">
    <div class="orchard-bg">
            <img src="../static/images/bg2.png">
            <img class="board_bg2" src="../static/images/board_bg2.png">
        </div>
    <img class="back" @click="go_index" src="../static/images/user_back.png" alt="">
    <div class="header">
            <div class="info" @click="go_home">
                <div class="avatar">
                    <img class="avatar_bf" src="../static/images/avatar_bf.png" alt="">
                    <img class="user_avatar" src="../static/images/avatar.png" alt="">
                    <img class="avatar_border" src="../static/images/avatar_border.png" alt="">
                </div>
                <p class="user_name">好听的昵称</p>
            </div>
            <div class="wallet">
                  <!-- ***为充值绑定一个user_recharge事件*** -->
                <div class="balance" @click="user_recharge">
                    <p class="title"><img src="../static/images/money.png" alt="">钱包</p>
                    <p class="num">99,999.00</p>
                </div>
                <div class="balance">
                    <p class="title"><img src="../static/images/integral.png" alt="">果子</p>
                    <p class="num">99,999.00</p>
                </div>
            </div>
      <div class="menu-list">
        <div class="menu">
          <img src="../static/images/menu1.png" alt="">
          排行榜
        </div>
        <div class="menu">
          <img src="../static/images/menu2.png" alt="">
          签到有礼
        </div>
        <div class="menu" @click="go_orchard_shop">
          <img src="../static/images/menu3.png" alt="">
          道具商城
        </div>
        <div class="menu">
          <img src="../static/images/menu4.png" alt="">
          邮件中心
        </div>
      </div>
        </div>
    <div class="footer" >
      <ul class="menu-list">
        <li class="menu">新手</li>
        <li class="menu">背包</li>
        <li class="menu-center" @click="go_orchard_shop">商店</li>
        <li class="menu">消息</li>
        <li class="menu">好友</li>
      </ul>
    </div>
    </div>
    <script>
    apiready = function(){
        init();
        new Vue({
            el:"#app",
            data(){
                return {
          music_play:true,
          namespace: '/mofang_orchard',
          token:"",
          socket: null,
                    recharge_list: ['10','20','50','100','200','500','1000']
          timeout: 0,
                    prev:{name:"",url:"",params:{}},
                    current:{name:"orchard",url:"orchard.html",params:{}},
                }
            },
      created(){
        this.game.goFrame("orchard","my_orchard.html", this.current,{
            x: 0,
            y: 180,
            w: 'auto',
            h: 'auto',
        },null);
        this.checkout();
      },
            methods:{
                 // ***充值***
                user_recharge(){
                    // 充值
                    api.actionSheet({
                        title: '余额充值',
                        cancelTitle: '取消',
                        buttons: this.recharge_list
                    }, function(ret, err){
                        if( ret ){
                             // 充值金额
                                     money = this.recharge_list[ret.buttonIndex-1];
                                     // 调用支付宝重置
                                     
                        }else{

                        }
                    });

                },
        checkout(){
          var token = this.game.get("access_token") || this.game.fget("access_token");
          this.game.checkout(this,token,(new_access_token)=>{
            this.connect();
          });
        },
        connect(){
          // socket连接
          this.socket = io.connect(this.settings.socket_server + this.namespace, {transports: ['websocket']});
          this.socket.on('connect', ()=>{
              this.game.print("开始连接服务端");
          });
        },
        go_index(){
          this.game.outWin("orchard");
        },
        go_friends(){
          this.game.goFrame("friends","friends.html",this.current);
          this.game.goFrame("friend_list","friend_list.html",this.current,{
              x: 0,
              y: 190,
              w: 'auto',
              h: 'auto',
          },null,true);
        },
        go_home(){
          this.game.goWin("user","user.html", this.current);
        },
                go_orchard_shop(){
                    // 种植园商店
                    this.game.goFrame("orchard_shop","shop.html", this.current,null,{
              type:"push",
              subType:"from_top",
              duration:300
          });
                }
            }
        });
    }
    </script>
</body>
</html>
点击充值允许用户选择充值金额

7.将AlipayPlus模块加载到APP上

1.首先来到APIcloud开发者web后台,把Alipayplus模块加载到APP。

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作

2.点击Alipayplus进入模块详情。

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作

3.把模块使用到指定APP中。[下图只做参看, 项目已定义叫Mofang]

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作

8.集成Alipayplus模块完成支付

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作

 

1.安装alipay的sdk

接下来,服务端中需要完成的就是生成订单参数和接收支付结果,所以我们先 安装alipay的sdk,集成到flask项目中。

终端运行:

pip install python-alipay-sdk --upgrade

2.配置支付宝的公钥和私钥

配置支付宝的公钥和私钥,保存到application/apps/users/keys目录下。

cd application/apps/users/
mkdir keys
cd keys
openssl
OpenSSL> genrsa -out app_private_key.pem   2048  # 私钥
OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 导出公钥
OpenSSL> exit

保存商户公钥到支付宝开放平台, 并从开放平台中把支付宝公钥保存到项目中

day113:MoFang:种植园商城页面&充值集成Alipay完成支付的准备工作

9.增加用户充值订单模型类

users/models.py,代码:

 

class Recharge(BaseModel):
    __tablename__ = "mf_user_recharge"
    status  = db.Column(db.Boolean, default=True, comment="状态(是否支付)")
    out_trade_number  = db.Column(db.String(64), unique=True, comment="订单号")
    user_id = db.Column(db.Integer, comment="用户")
    money = db.Column(db.Numeric(7,2), comment="账户余额")
    trade_number = db.Column(db.String(64), unique=True, comment="流水号")

 

上一篇:alipay-bolt 1.5.3 - NettyServer


下一篇:Django--支付宝(沙箱)