day114:MoFang:基于支付宝沙箱测试环境完成创建充值订单接口&服务端处理支付结果的同步通知和异步通知

时间:2024-02-01 13:57:36

目录

1.基于支付宝提供的沙箱测试环境开发支付接口

  1.后端提供创建充值订单接口

  2.前端调用AlipayPlus发起支付

  3.注意:自定义APPLoader完成接下来的开发

  4.下载支付宝沙箱钱包APP

  5.充值成功/失败的提示

2.服务端处理支付结果的同步通知和异步通知

  1.异步通知结果处理

  2.同步通知结果处理

BUG:修复页面底部菜单无法被点击的BUG

MoFang充值流程图

1.基于支付宝提供的沙箱测试环境开发支付接口

沙箱环境: https://openhome.alipay.com/platform/appDaily.htm?tab=info

1.后端提供创建充值订单接口

服务端提供充值api接口,user/views.py代码:

from application import jsonrpc
from .models import Recharge
from datetime import datetime
from alipay import AliPay
from alipay.utils import AliPayConfig
import os, json, random
from flask import current_app

@jsonrpc.method("Recharge.create")
@jwt_required # 验证jwt
def create_recharge(money=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,
        }
    order_number = datetime.now().strftime("%y%m%d%H%M%S") + "%08d" % user.id + "%04d" % random.randint(0, 9999)


    recharge = Recharge(
        status=False,
        out_trade_number=order_number,
        name="账号充值-%s元" % money,
        user_id=user.id,
        money=money
    )
    db.session.add(recharge)
    db.session.commit()
    
    # 创建支付宝sdk对象
    app_private_key_string = open(os.path.join(current_app.BASE_DIR, "application/apps/users/keys/app_private_key.pem")).read()
    alipay_public_key_string = open(os.path.join(current_app.BASE_DIR, "application/apps/users/keys/app_public_key.pem")).read()

    alipay = AliPay(
        appid= current_app.config.get("ALIPAY_APP_ID"),
        app_notify_url=None,  # 默认回调url
        app_private_key_string=app_private_key_string,
        # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        alipay_public_key_string=alipay_public_key_string,
        sign_type=current_app.config.get("ALIPAY_SIGN_TYPE"),
        debug = False,  # 默认False
        config = AliPayConfig(timeout=15)  # 可选, 请求超时时间
    )

    order_string = alipay.api_alipay_trade_app_pay(
        out_trade_no=recharge.out_trade_number,  # 订单号
        total_amount=float(recharge.money),  # 订单金额
        subject=recharge.name,  # 订单标题
        notify_url=current_app.config.get("ALIPAY_NOTIFY_URL")  # 服务端的地址,自定义一个视图函数给alipay
    )

    return {
        "errno": status.CODE_OK,
        "errmsg": message.ok,
        "sandbox": current_app.config.get("ALIPAY_SANDBOX"),
        "order_string": order_string,
        "order_number": recharge.out_trade_number,
    }
后端提供创建充值订单接口

配置文件dev.py,代码:

    # 支付宝配置信息
    ALIPAY_APP_ID = "2016091600523592"
    ALIPAY_SIGN_TYPE = "RSA2"
    ALIPAY_NOTIFY_URL = "https://example.com/notify"
    ALIPAY_SANDBOX = True

2.前端调用AlipayPlus发起支付

客户端发起充值请求,并调用alipayplus模块发起支付.orchard.html代码

<!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" @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
                    }, (ret, err)=>{
                        if( ret ){
                                     if(ret.buttonIndex <= this.recharge_list.length){
                                             // 充值金额
                                             money = this.recharge_list[ret.buttonIndex-1];
                                             // 调用支付宝充值
                                             this.create_recharge(money);
                                     }
                        }else{

                        }
                    });

                },
                create_recharge(money){
                    // 获取历史信息记录
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Recharge.create",
                            "params": {
                                "money": money,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            this.game.print(response.data);
                            if(parseInt(response.data.result.errno)==1000){
                                // ***前往支付宝***
                                orderInfo = response.data.result.order_string;
                                var aliPayPlus = api.require('aliPayPlus');
                                aliPayPlus.payOrder({
                                     orderInfo: orderInfo,
                                     sandbox: true, // 将来APP上线需要修改成false
                                 }, (ret, err)=>{
                                         this.game.print(ret,true);
                                    api.alert({
                                        title: '支付结果',
                                        msg: ret.code,
                                        buttons: ['确定']
                                    });
                                });
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 网络等异常
                            this.game.print(error);
                        });
                    })
                },
        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>
前端调用AlipayPlus发起支付

3.注意:自定义APPLoader完成接下来的开发

基于支付宝模块唤醒支付宝APP,实现支付.

因为我们使用的支付宝模块是第三方模块,所以在前面下载安装AppLoader里面是没有对应模块代码的,所以如果继续使用AppLoader进行模块功能使用,则会报错如下:

 

所以我们需要进行自定义AppLoader,可以在本地编辑器中项目目录菜单中选择云编译自定义AppLoader,,也可以到官网网站用户后台中心进行编译生成自定义的APPLoader.

方式1: 在本地编辑器中选择云编译自定义AppLoader

 

方式2: 在官网的用户后台中心生成自定义AppLoader

得到自定义AppLoader以后,在测试手机或者安卓模拟器中, 安装自定义AppLoader然后进行功能测试!

4.下载支付宝沙箱钱包APP

同时, 测试之前,因为本次开发的功能是支付宝支付功能,所以我们还需要到支付宝沙箱应用后台下载一个支付宝沙箱钱包App到当前手机或者模拟器中.否则无法完成测试支付过程.

下载: https://sandbox.alipaydev.com/user/downloadApp.htm

客户端发起支付代码:

注意:

在实际过程中, 我们使用的正式的支付宝APP,所以在客户端中sandbox参数的值必须作为配置,引入.

5.充值成功/失败的提示

orchard.html代码

<!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" @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
                    }, (ret, err)=>{
                        if( ret ){
                                     if(ret.buttonIndex <= this.recharge_list.length){
                                             // 充值金额
                                             money = this.recharge_list[ret.buttonIndex-1];
                                             // 调用支付宝充值
                                             this.create_recharge(money);
                                     }
                        }else{

                        }
                    });

                },
                create_recharge(money){
                    // 获取历史信息记录
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Recharge.create",
                            "params": {
                                "money": money,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            this.game.print(response.data);
                            if(parseInt(response.data.result.errno)==1000){
                                // 前往支付宝
                                var aliPayPlus = api.require('aliPayPlus');
                                aliPayPlus.payOrder({
                                     orderInfo: response.data.result.order_string,
                                     sandbox: response.data.result.sandbox, // 将来APP上线需要修改成false
                                 }, (ret, err)=>{
                                         // ***增加充值成功失败的提醒***
                                      pay_result = {
                                            9000:"支付成功",
                          8000:"正在处理中",
                          4000:"订单支付失败",
                          5000:"重复请求",
                          6001:"取消支付",
                          6002:"网络连接出错",
                                            6004:"支付结果未知",
                                        }
                                    api.alert({
                                        title: '支付结果',
                                        msg: pay_result[ret.code],
                                        buttons: ['确定']
                                    });
                                        // 通知服务端, 修改充值结果
                                });
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 网络等异常
                            this.game.print(error);
                        });
                    })
                },
        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.服务端处理支付结果的同步通知和异步通知

1.异步通知结果处理

users/views.py,代码:

from flask import request
def notify_response():
    """支付宝支付结果的异步通知处理"""
    data = request.form.to_dict()
    # sign 不能参与签名验证
    signature = data.pop("sign")

    app_private_key_string = open(os.path.join(current_app.BASE_DIR, "application/apps/users/keys/app_private_key.pem")).read()
    alipay_public_key_string = open(os.path.join(current_app.BASE_DIR, "application/apps/users/keys/app_public_key.pem")).read()

    alipay = AliPay(
        appid= current_app.config.get("ALIPAY_APP_ID"),
        app_notify_url=None,  # 默认回调url
        app_private_key_string=app_private_key_string,
        # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
        alipay_public_key_string=alipay_public_key_string,
        sign_type=current_app.config.get("ALIPAY_SIGN_TYPE"),
        debug = False,  # 默认False
        config = AliPayConfig(timeout=15)  # 可选, 请求超时时间
    )

    # verify
    success = alipay.verify(data, signature)
    if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED" ):
        """充值成功"""
        out_trade_number = data["out_trade_no"]
        recharge = Recharge.query.filter(Recharge.out_trade_number==out_trade_number).first()
        if recharge is None:
            return "fail"
        recharge.status=True
        user = User.query.get(recharge.user_id)
        if user is None:
            return "fail"
        user.money+=recharge.money
        db.session.commit()
    return "success" # 必须只能是success

绑定路由, users/urls.py,代码:

from . import views
from application.utils import path
urlpatterns = [
    ....
    path("/alipay/notify", views.notify_response),
]

tip:改写路由绑定的path方法

上面代码,我们已经完成了异步处理,但是我们要在前期的项目代码处理中, 没有实现视图方法的绑定操作,所以上面的代码视图方法只能被GET请求.

所以需要改在utils/__init__.py中路由绑定的path方法,代码:

def path(rule,func_view,**kwargs):
    # 把蓝图下视图和路由之间的映射关系处理成字典结构,方便后面注册蓝图的时候,直接传参
    return {"rule":rule,"view_func":func_view,**kwargs}

users/urls.py代码:

from . import views
from application.utils import path
urlpatterns = [
    ....   
    path("/alipay/notify", views.notify_response,methods=["POST","GET"]),
]

完成上面的操作以后, 异步处理的视图方法就可以被外界使用POST请求访问了.

2.同步通知结果处理

1.同步通知结果处理后端接口

users/views.py,代码:

@jsonrpc.method("Recharge.return")
@jwt_required # 验证jwt
def return_recharge(out_trade_number):
    """同步通知处理"""
    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,
        }

    recharge = Recharge.query.filter(Recharge.out_trade_number==out_trade_number).first()
    if recharge is None:
        return {
            "errno": status.CODE_RECHARGE_ERROR,
            "errmsg": message.recharge_not_exists,
        }

    recharge.status=True
    user.money+=recharge.money
    db.session.commit()
    return {
        "errno": status.CODE_OK,
        "errmsg": message.ok,
        "money": user.money,
    }

2.前端支付完成后要通知后端修改充值结果(同步结果通知)

客户端, orchard.html, 代码:

<!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" @click="user_recharge">
                    <p class="title"><img src="../static/images/money.png" alt="">钱包</p>
                    <p class="num">{{money}}</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:"",
                    money:"",
          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();
                this.money = this.game.fget("money")
      },
            methods:{
                user_recharge(){
                    // 发起充值请求
                    api.actionSheet({
                        title: '余额充值',
                        cancelTitle: '取消',
                        buttons: this.recharge_list
                    }, (ret, err)=>{
                        if( ret ){
                                     if(ret.buttonIndex <= this.recharge_list.length){
                                             // 充值金额
                                             money = this.recharge_list[ret.buttonIndex-1];
                                             // 调用支付宝充值
                                             this.create_recharge(money);
                                     }
                        }else{

                        }
                    });

                },
                create_recharge(money){
                    // 获取历史信息记录
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Recharge.create",
                            "params": {
                                "money": money,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            if(parseInt(response.data.result.errno)==1000){
                                // 前往支付宝
                                var aliPayPlus = api.require('aliPayPlus');
                                aliPayPlus.payOrder({
                                     orderInfo: response.data.result.order_string,
                                     sandbox: response.data.result.sandbox, // 将来APP上线需要修改成false
                                 }, (ret, err)=>{
                                      pay_result = {
                                            9000:"支付成功",
                          8000:"正在处理中",
                          4000:"订单支付失败",
                          5000:"重复请求",
                          6001:"取消支付",
                          6002:"网络连接出错",
                                            6004:"支付结果未知",
                                        }
                                    api.alert({
                                        title: '支付结果',
                                        msg: pay_result[ret.code],
                                        buttons: ['确定']
                                    });
                                        // ***通知服务端, 修改充值结果***
                                        this.return_recharge(response.data.result.order_number,token);
                                });
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 网络等异常
                            this.game.print(error);
                        });
                    })
                },
                return_recharge(out_trade_number,token){
                    this.axios.post("",{
                        "jsonrpc": "2.0",
                        "id": this.uuid(),
                        "method": "Recharge.return",
                        "params": {
                            "out_trade_number": out_trade_number,
                        }
                    },{
                        headers:{
                            Authorization: "jwt " + token,
                        }
                    }).then(response=>{
                        if(parseInt(response.data.result.errno)==1000){
                            this.money = response.data.result.money.toFixed(2);
                        }
                    })
                },
        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>
前端支付完成后要通知后端修改充值结果(同步结果通知)

tip:登录成功后将用户的积分和余额也返回给前端

完善下前面登陆代码中的信息补充,users/views.py, 代码:

@jsonrpc.method("User.login")
def login(ticket,randstr,account,password):
    """根据用户登录信息生成token"""
    # 校验防水墙验证码
    params = {
        "aid": current_app.config.get("CAPTCHA_APP_ID"),
        "AppSecretKey": current_app.config.get("CAPTCHA_APP_SECRET_KEY"),
        "Ticket": ticket,
        "Randstr": randstr,
        "UserIP": request.remote_addr
    }
    # 把字典数据转换成地址栏的查询字符串格式
    # aid=xxx&AppSecretKey=xxx&xxxxx
    params = urlencode(params)
    url = current_app.config.get("CAPTCHA_GATEWAY")
    # 发送http的get请求
    f = urlopen("%s?%s" % (url, params))
    # https://ssl.captcha.qq.com/ticket/verify?aid=xxx&AppSecretKey=xxx&xxxxx

    content = f.read()
    res = json.loads(content)
    print(res)

    if int(res.get("response")) != 1:
        # 验证失败
        return {"errno": status.CODE_CAPTCHA_ERROR, "errmsg": message.captcaht_no_match}

    # 1. 根据账户信息和密码获取用户
    if len(account) < 1:
        return {"errno":status.CODE_NO_ACCOUNT,"errmsg":message.account_no_data}
    user = User.query.filter(or_(
        User.mobile==account,
        User.email==account,
        User.name==account
    )).first()

    if user is None:
        return {"errno": status.CODE_NO_USER,"errmsg":message.user_not_exists}

    # 验证密码
    if not user.check_password(password):
        return {"errno": status.CODE_PASSWORD_ERROR, "errmsg":message.password_error}

    # 2. 生成jwt token
    access_token = create_access_token(identity=user.id)
    refresh_token = create_refresh_token(identity=user.id)
    print(access_token)
    print(refresh_token)
    return {
        "errno": status.CODE_OK,
        "errmsg": message.ok,
        "id": user.id,
        "nickname": user.nickname if user.nickname else account,
        "avatar": user.avatar if user.avatar else current_app.config["DEFAULT_AVATAR"],
        "money": float(user.money),
        "credit": float(user.credit),
        "access_token": access_token,
        "refresh_token":refresh_token
    }
登录成功后将用户的余额和果子积分返回给前端

BUG:修复页面底部菜单无法被点击的BUG

出现bug的原因: my_orchard页面帧的高度挡住了主页面的中的底部菜单.

客户端页面代码, orchard.html,代码:

<!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" @click="user_recharge">
                    <p class="title"><img src="../static/images/money.png" alt="">钱包</p>
                    <p class="num">{{money}}</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:"",
                    money:"",
          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: 410,
        },null);
        this.checkout();
                this.money = this.game.fget("money")
      },
            methods:{
                user_recharge(){
                    // 发起充值请求
                    api.actionSheet({
                        title: '余额充值',
                        cancelTitle: '取消',
                        buttons: this.recharge_list
                    }, (ret, err)=>{
                        if( ret ){
                                     if(ret.buttonIndex <= this.recharge_list.length){
                                             // 充值金额
                                             money = this.recharge_list[ret.buttonIndex-1];
                                             // 调用支付宝充值
                                             this.create_recharge(money);
                                     }
                        }else{

                        }
                    });

                },
                create_recharge(money){
                    // 获取历史信息记录
                    var token = this.game.get("access_token") || this.game.fget("access_token");
                    this.game.checkout(this, token, (new_access_token)=>{
                        this.axios.post("",{
                            "jsonrpc": "2.0",
                            "id": this.uuid(),
                            "method": "Recharge.create",
                            "params": {
                                "money": money,
                            }
                        },{
                            headers:{
                                Authorization: "jwt " + token,
                            }
                        }).then(response=>{
                            if(parseInt(response.data.result.errno)==1000){
                                // 前往支付宝
                                var aliPayPlus = api.require('aliPayPlus');
                                aliPayPlus.payOrder({
                                     orderInfo: response.data.result.order_string,
                                     sandbox: response.data.result.sandbox, // 将来APP上线需要修改成false
                                 }, (ret, err)=>{
                                      pay_result = {
                                            9000:"支付成功",
                          8000:"正在处理中",
                          4000:"订单支付失败",
                          5000:"重复请求",
                          6001:"取消支付",
                          6002:"网络连接出错",
                                            6004:"支付结果未知",
                                        }
                                    api.alert({
                                        title: '支付结果',
                                        msg: pay_result[ret.code],
                                        buttons: ['确定']
                                    });
                                        // 通知服务端, 修改充值结果
                                        this.return_recharge(response.data.result.order_number,token);
                                });
                            }else{
                                    this.game.print(response.data);
                            }
                        }).catch(error=>{
                            // 网络等异常
                            this.game.print(error);
                        });
                    })
                },
                return_recharge(out_trade_number,token){
                    this.axios.post("",{
                        "jsonrpc": "2.0",
                        "id": this.uuid(),
                        "method": "Recharge.return",
                        "params": {
                            "out_trade_number": out_trade_number,
                        }
                    },{
                        headers:{
                            Authorization: "jwt " + token,
                        }
                    }).then(response=>{
                        if(parseInt(response.data.result.errno)==1000){
                            this.money = response.data.result.money.toFixed(2);
                        }
                    })
                },
        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>
修复页面底部菜单无法被点击的BUG

main.css中修改样式选择符对应的css样式

.orchard-frame .prop-list{
  position: absolute;
  bottom: 1rem; /*bottom: 6rem 修改成 bottom: 1rem*/
  width: 100%;
}
.orchard-frame .pet-hp-list{
  position: absolute;
  right: 0;
  bottom: 3rem;/*bottom: 8rem 修改成 bottom: 3rem*/
  width: 11rem;
  height: 4rem;
}