day114:MoFang:基於支付寶沙箱測試環境完成建立充值訂單介面&服務端處理支付結果的同步通知和非同步通知

Poke發表於2020-12-29

目錄

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程式碼:

day114:MoFang:基於支付寶沙箱測試環境完成建立充值訂單介面&服務端處理支付結果的同步通知和非同步通知
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程式碼

day114:MoFang:基於支付寶沙箱測試環境完成建立充值訂單介面&服務端處理支付結果的同步通知和非同步通知
<!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程式碼

day114:MoFang:基於支付寶沙箱測試環境完成建立充值訂單介面&服務端處理支付結果的同步通知和非同步通知
<!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, 程式碼:

day114:MoFang:基於支付寶沙箱測試環境完成建立充值訂單介面&服務端處理支付結果的同步通知和非同步通知
<!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, 程式碼:

day114:MoFang:基於支付寶沙箱測試環境完成建立充值訂單介面&服務端處理支付結果的同步通知和非同步通知
@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,程式碼:

day114:MoFang:基於支付寶沙箱測試環境完成建立充值訂單介面&服務端處理支付結果的同步通知和非同步通知
<!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;
}

 

相關文章