[day30] Line购物机器人 小总结与感想

今天的文章分为两部分,一个是今天做完的部分修改,一部份是参赛感想

今日的品质更新 & 逻辑修正

我目前规划做到的部分大概先暂时到从Line进行完整的购物流程,亦即

  1. 显示商品列表
  2. 使用者进行购物车内品项的加减控制
  3. 建立订单
  4. 付款结果通知等

这部分在今天继续更新一些功能上的修改

购物车品项检查

现在会拒绝库存不足的品项加入购物车

def Control_Shopping_Cart_ViaMessageText(uid, user_type_text):
    split_text = user_type_text.split(' ')
    if(len(split_text) > 3):return "错误的购物车指令\ncart \{要加入或变更的产品ID\} \{该产品的数量\}\n输入数字不需要大括弧\{\}"
    if(not split_text[1].isnumeric()):return "请输入罗马数字的产品ID"

    scid = dbpm.INS_QUY_SC(uid)

    if((split_text[2][0] == '+' or split_text[2][0] == '-') and split_text[2][1:].isnumeric()):
        num = int(split_text[2])
        new_qt = dbpm.QUY_Shopping_Cart_item_Quantity(split_text[1], scid) + num
    elif(split_text[2].isnumeric()):
        new_qt = int(split_text[2])
    else:
        return "请直接输入订购数量的罗马数字,或是+/-符号加数字"

    if(new_qt < 0):
        new_qt = 0
    # app.logger.debug(f"{split_text[1]}, {new_qt}")
    p_name, p_price = dbpm.QUY_Prod_Name_and_Price_by_pid(split_text[1])
    current_quantity = dbpm.QUY_Prod_Quantity_by_pid(split_text[1])
    if(current_quantity >= new_qt):
        dbpm.INS_UPD_Prod_to_Cart(scid, split_text[1], new_qt)
        if(new_qt == 0):
            dbpm.DEL_Shopping_Cart_items(scid)
            return f"已将{p_name}自购物车中删除"
        else:
            return f"已将{p_name}(单价:{p_price})的购买数量设定为{new_qt}"
    else:
        return f"{p_name}目前的库存不足,无法加入购物车"

重作订单产生流程

现在付款ID不会再绑定订单,在付款成功前,使用者可以切换用什麽付款,以及加入库存更新 & 购物车内物品库存检查,建立订单流程:

  1. 使用者於显示购物车列表,按下"点我下订单"按钮
  2. 产生PostBack Event,data = action=buy
  3. 接收data = action=buy的PostBack Event
  4. 如果没有附带参数,则进入建立订单流程,检查购物车品项与当前的系统库存(MakeOrder_1_Check_Cart)
  5. 建立订单资料纪录(MakeOrder_2_Create_Order),将订单与使用者和购物车绑定()
  6. 提供按钮以让使用者选择付款方式(银行帐户或信用卡)
  7. 产生PostBack Event,data = action=buy?oid=oid&scid=scid&paytype=paytype
  8. 接收PostBack Event,判定资料完整後进入,串接永丰银行金流系统(MakeOrder_3_Request_Pay),依照付款方式给予参数申请服务
  9. 接收自银行的参数,传送给使用者
# Server.py

elif(datapath == "action=buy"):
    if(not datavalue):
        isSucc, scidormsg = Handler.MakeOrder_1_Check_Cart(event.source.user_id)
        app.logger.debug(f"建立订单-检查购物车, {isSucc}, {scidormsg}")
        if(isSucc):
            isSucc, oidormsg = Handler.MakeOrder_2_Create_Order(scidormsg, event.source.user_id)
            if(isSucc):
                template_msg = APIModel.OrderPaySelTemp(scidormsg, oidormsg)
                line_bot_api.reply_message(
                    event.reply_token,
                    template_msg
                )
            else:
                line_bot_api.reply_message(
                    event.reply_token,
                    TextSendMessage(text=oidormsg)
                ) 
        else:
            line_bot_api.reply_message(
                event.reply_token,
                TextSendMessage(text=scidormsg)
            )
    else:
        oid = datavalue.get('oid')[0]
        scid = datavalue.get('scid')[0]
        paytype = datavalue.get('paytype')[0]
        app.logger.debug(f"订单:{oid}(scid:{scid}), 选择付款方式:{paytype}")
        if(oid and scid and paytype):
            isSucc, msg = Handler.MakeOrder_3_Request_Pay(oid, scid, paytype)
            if(isSucc):
                if(paytype == "1"):
                    app.logger.debug(f"银行转帐(ATM)付款资讯:{msg}")
                elif(paytype == "2"):
                    app.logger.debug(f"信用卡付款资讯:{msg}")
                    template_msg = APIModel.OrderPayURLTemp(msg)
                    line_bot_api.reply_message(
                        event.reply_token,
                        template_msg
                    )
            else:
                line_bot_api.reply_message(
                    event.reply_token,
                    TextSendMessage(text=msg)
                )
        else:
            app.logger.debug("建立订单时参数错误", datavalue)

# OrderHandler.py

def MakeOrder_1_Check_Cart(uid):
    scid = dbpm.INS_QUY_SC(uid)
    isSucc, msg = CheckQuantity(scid)
    if(isSucc):
        dbpm.UPD_Shopping_Cart_lock_bY_scid(True, scid)
        return True, scid
    else:
        return False, msg

def CheckQuantity(scid):
    shopping_list = dbpm.QUY_Shopping_Cart_by_scid(scid)
    if(not shopping_list):
        return False, "购物车内没有商品喔"
    for prod in shopping_list:
        current_quantity = dbpm.QUY_Prod_Quantity_by_pid(prod[0])
        if(current_quantity - prod[1] < 0):
            dbpm.INS_UPD_Prod_to_Cart(scid, prod[0], current_quantity)
            dbpm.DEL_Shopping_Cart_items(scid)
            app.logger.warn(f"商品{prod[0]},库存不足无法满足订单需求数量({prod[1]})")
            return False, "部分商品库存不足,请稍後重试"
    return True, None

def MakeOrder_2_Create_Order(scid, uid):
    try:
        oid = dbpm.INS_Order(uid, scid, ostatus="初始化订单")
        return True, oid
    except ExecError as Err:
        app.logger.error(scid, uid, Err)
        return False, f"建立订单时发生错误\n{Err}"

def MakeOrder_3_Request_Pay(oid, scid, paytype):
    if(Check_order_ispaid(oid)):return False, "该笔订单已完成付款"
    shopping_list = dbpm.QUY_Shopping_Cart_by_scid(scid)
    amount = 0
    msg = None
    for cart_item in shopping_list:
        product_name, product_price = dbpm.QUY_Prod_Name_and_Price_by_pid(cart_item[0])
        amount = amount + product_price * cart_item[1]
    if(paytype == "1"):
        # ATM
        expiredate = (datetime.now() + timedelta(days = 1)).strftime("%Y%m%d")
        paid = dbpm.INS_payment_req('ATM', amount)
        neworder = APIModel.ReqOrderCreate(ShopNo=os.environ['ShopNo'], OrderNo=paid, Amount=amount*100, \
        PrdtName='IT铁人赛虚拟商店', Param1=oid, ReturnURL=os.environ['ReturnURL'], BackendURL=os.environ['BackendURL'], PayType="A", ExpireDate=expiredate)
        msg = GenApi.OrderCreate(neworder)
        app.logger.debug(f"MakeOrder:{msg}")
    elif(paytype == "2"):
        # 信用卡一次付清
        paid = dbpm.INS_payment_req('C-1', amount)
        neworder = APIModel.ReqOrderCreate(ShopNo=os.environ['ShopNo'], OrderNo=paid, Amount=amount*100, \
        PrdtName='IT铁人赛虚拟商店', Param1=oid, ReturnURL=os.environ['ReturnURL'], BackendURL=os.environ['BackendURL'], PayType="C")
        msg = GenApi.OrderCreate(neworder)
        app.logger.debug(f"MakeOrder:{msg}")

    if(msg):
        if(msg.Status == 'S'):
            dbpm.UPD_payment_bypaid(paid=paid, tsno=msg.TSNo, ts_decp=msg.Description, ts_status=True, cardpayurl=msg.CardParam.CardPayURL)
            dbpm.UPD_Order_by_oid(paid=paid, ostatus="已产生付款请求", oid=oid)
            isSucc, errmsg = UpdateQuantity(shopping_list)
            if(isSucc):
                return True, msg
            else:
                return False, errmsg
        else:
            dbpm.UPD_payment_bypaid(paid=paid, tsno=msg.TSNo, ts_decp=msg.Description, ts_status=False)
            dbpm.UPD_Order_by_oid(paid=paid, ostatus="产生付款请求失败", oid=oid)
            errmsg = f"与金流系统通讯时发生错误,{msg.Description}"
    dbpm.UPD_Shopping_Cart_lock_bY_scid(False, scid)
    return False, errmsg or "建立付款请求时发生错误"

其他的修改

付款通知的外层的Status只是讯息传送的Status,与付款相关的参数都在TSResultContent,所以会出现外层Status是成功,内部却是失败的情况,修改API解析参数调用

def OrderPayQueryHandler(resp:APIModel.ResOrderPayQuery, line_bot_api:LineBotApi):
    app.logger.debug(f"ResOrderPayQuery:{resp}")
    payinfo = resp.TSResultContent
    if(payinfo.Status != 'S'):
        dbpm.UPD_payment_bytsno(ispaid=False, paytoken=resp.PayToken, tsno=payinfo.TSNo, aptype=payinfo.APType)
        app.logger.info(f"订单{payinfo.Param1}付款失败, 付款编号:{payinfo.OrderNo} - {payinfo.Description}")
        uid = dbpm.UPD_Order_status_by_paid(ostatus=f"付款失败-{payinfo.Description}", paid = payinfo.OrderNo)
        line_bot_api.push_message(uid, TextSendMessage(text=f"您的订单: {payinfo.Param1} 付款失败,原因可能为:\n{payinfo.Description}"))
    else:
        dbpm.UPD_payment_bytsno(ispaid=True, paytoken=resp.PayToken, tsno=payinfo.TSNo, aptype=payinfo.APType)
        app.logger.info(f"订单{payinfo.Param1}付款成功, 付款编号:{payinfo.OrderNo} - {payinfo.Description}")
        uid = dbpm.UPD_Order_status_by_paid(ostatus=f"付款成功-{payinfo.Description}", paid = payinfo.OrderNo)
        line_bot_api.push_message(uid, TextSendMessage(text=f"您的订单: {payinfo.Param1} 付款成功罗"))

依购物车更新库存从建立订单时变更,改为付款成功後才变更,比较符合常见的库存逻辑

def UpdateQuantity(shopping_list, mode = 1):
    if(shopping_list):
        try:
            if(mode):
                for prod in shopping_list:
                    current_quantity = dbpm.QUY_Prod_Quantity_by_pid(prod[0])
                    new_quantity = current_quantity - prod[1]
                    dbpm.UPD_Prod_Quantity(prod[0], new_quantity)
                    # app.logger.debug(f"pid:{prod[0]}, oldqt:{current_quantity}, newqt:{new_quantity}")
            else:
                for prod in shopping_list:
                    current_quantity = dbpm.QUY_Prod_Quantity_by_pid(prod[0])
                    new_quantity = current_quantity + prod[1]
                    dbpm.UPD_Prod_Quantity(prod[0], new_quantity)
            return True, None
        except Exception as err:
            return False, err
    return False, f"shopping_list:{shopping_list}"

参赛感想 & 想做却没空做的功能

30天,铁腿了0rz

老实说参赛最初的构想是摩斯卡App,谁知道後来变成Line订餐机器人,只能说灵感就是这样,因为中期想了一堆有的没的的新功能,结果做不完哈哈

本次铁人赛,摸了一堆以前完全没玩过的框架与功能需求,主要有

  1. 接入真实银行的金流系统,处理API交互
    1. 永丰FunBiz消费支付API
  2. 部属云端服务Heroku App,进行功能测试与开发部署
    1. Python App
    2. postgresql Database
  3. 架设Line机器人服务器,处理各种讯息与提供服务
    1. 与Line官方通讯
    2. API授权凭证产生处理
    3. 各种子功能
      1. 追踪者优惠券
      2. 文字处理
      3. PostBackEvent
      4. .....

其实最後一天还在想能加什麽功能,但想到BUG很多就还是赶紧改好品质更新,以下为想做却还没做的残念部分:

功能 说明 大概的构想
使用者教学 如何使用系统 拍摄影片或是给使用者一组测试的资料可供输入
查询订单 显示近期订单 分为一般使用者与管理人员两种介面
优化显示 Flex message 现在的介面太丑啦
网页端的後台 控制库存、出货 目前订单系统只写了半套,还有一堆功能如出货,订单管理,之类的.....梦想太大

2021铁人赛暂时先到这边告一段落,如果有问题,欢迎留言给我喔

2021铁人赛 - GitHub

建立资料库SQL

Line机器人官方帐号ID:@171ssmku

https://ithelp.ithome.com.tw/upload/images/20211012/20140853I0Rhs5YyNE.png


<<:  day27_ARM 在 Server 领域的发展 (下)

>>:  Day28:今天来聊一下Hacking Cloud Computing

资安学习路上-picoCTF 解题(Web)1

Web 1. GET aHEAD 打开网页原始码发现目前有两个选项"GET"、&...

【Day2】声音的一些基本介绍

声音这东西实在是太自然了,所以我们很少去思考这东西的本质到底是什麽 简单的来复习一下声音是什麽,你...

etcd 元件浅解

在 Kubernetes 中元件间的通讯都是藉由 API Server 通讯,而 API Serve...

Day 52 (JS_API)

1. API? 应用程序介面 图形库中的一组API定义了绘制指标的方式,可於图形输出装置上显示指标。...

企划实现(1)

常常有人说做好一个企划需要勇气,但绝非这麽简单,创业不只需要勇气还需要运气、人脉、实力 、执着 做好...