• 商城支付功能


    一、支付接口

    1、支付接口路由

      创建LuffyCity/shopping/payment_view.py文件,在 shopping/urls.py 中添加支付接口路由:

    from django.urls import path
    from .views import ShoppingCarView
    from .settlement_view import SettlementView
    from .payment_view import PaymentView
    
    urlpatterns = [
        path('shopping_car', ShoppingCarView.as_view()),   # 购物车
        path('settlement', SettlementView.as_view()),      # 结算中心
        path('payment', PaymentView.as_view())             # 支付接口
    ]

    2、支付接口视图——获取和校验贝里

      前端提交给接口的数据,分析可知:不需要提供课程,只需要提供价格、抵扣贝里(积分)信息即可。

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from utils.my_auth import LoginAuth
    from utils.base_response import BaseResponse
    
    
    class PaymentView(APIView):
        authentication_classes = [LoginAuth, ]     # 登录认证
        def post(self, request):
            res = BaseResponse()
            # 1.获取数据
            balance = request.data.get("balance", 0)     # 贝里(积分),不传默认为0
            price = request.data.get("price", "")        # 价格,不传默认为空字符串
            user_id = request.user.pk
            # 2.校验数据的合法性
            # 2.1 校验贝里数是否合法
            if int(balance) > request.user.balance:      # 如果传来的贝里大于用户数据库中贝里
                res.code = 1070
                res.error = "抵扣的贝里错误"
                return Response(res.dict)

    3、支付接口——redis与数据库中数据校验

      获取前端传递来的数据后,需要校验数据的合法性。除了校验抵扣积分和价格之外。还要将redis中的数据和数据库中的数据进行比对。因为在加入结算中心的时候和进行支付具有一定的时间差。这个时间差会导致课程下架、优惠券过期等情况,为防止这种情况,必须让redis和数据库数据进行比对。

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from utils.my_auth import LoginAuth
    from utils.base_response import BaseResponse
    from .settlement_view import SETTLEMENT_KEY
    from utils.redis_pool import POOL   # redis连接池
    import redis
    from Course.models import Course
    from .models import Coupon
    from django.utils.timezone import now    # 拿时区当前时间
    
    
    CONN = redis.Redis(connection_pool=POOL)    # 创建连接,指定redis连接池
    
    
    class PaymentView(APIView):
        authentication_classes = [LoginAuth, ]     # 登录认证
        def post(self, request):
            res = BaseResponse()
            # 1.获取数据
            balance = request.data.get("balance", 0)     # 贝里(积分),不传默认为0
            price = request.data.get("price", "")        # 价格,不传默认为空字符串
            user_id = request.user.pk
            # 2.校验数据的合法性
            # 2.1 校验贝里数是否合法
            if int(balance) > request.user.balance:      # 如果传来的贝里大于用户数据库中贝里
                res.code = 1070
                res.error = "抵扣的贝里错误"
                return Response(res.dict)
            # 2.2 从用户的结算中心拿数据,与数据库比对是否合法
            settlement_key = SETTLEMENT_KEY % (user_id, "*")   # 课程id未知,用*代替
            all_keys = CONN.scan_iter(settlement_key)    # 得到所有的键
            for key in all_keys:
                settlement_info = CONN.hgetall(key)     # 结算中心信息
                # 2.2.1 比对课程id是否合法
                course_id = settlement_info["id"]
                course_obj = Course.objects.filter(id=course_id).first()   # 拿到课程对象
                if not course_obj or course_obj.status ==1:    # status为1,课程下架
                    res.code = 1071
                    res.error = "课程id不合法"
                    return Response(res.dict)
                # 2.2.2 校验优惠券是否过期
                course_coupon_id = settlement_info.get("default_coupon_id", 0)   # 用get取值,避免为空报错
                if course_coupon_id:
                    coupon_dict = Coupon.objects.filter(
                        id=course_coupon_id,               # 拿到优惠券对象
                        couponrecord__status=0,            # 必须是未使用的
                        couponrecord__account_id=user_id,  # 必须是当前用户的
                        object_id=course_id,               # 必须是当前课程的
                        valid_begin_date__lte=now(),       # 符合使用有效期
                        valid_end_date__gte=now()
                    ).values(         # 不拿对象,取如下数据
                        "coupon_type",               # 优惠券类型
                        "money_equivalent_value",    # 等值货币
                        "off_percent",               # 折扣比
                        "minimum_consume"            # 最小货币
                    )
                if not coupon_dict:   # 没有说明优惠券不合法
                    res.code = 1072
                    res.error = "优惠券不合法"
                    return Response(res.dict)

    4、价格计算

      校验价格前,先定义好价格计算方法。

    class PaymentView(APIView):
        authentication_classes = [LoginAuth, ]     # 登录认证
    
        def post(self, request):...
    
        def account_price(self, coupon_dict, price):
            """
            价格计算
            :param coupon_dict: 优惠券信息
            :param price: 课程价格
            :return:
            """
            coupon_type = coupon_dict["coupon_type"]
            if coupon_type == 0:    # 通用优惠券
                money_equivalent_value = coupon_dict["money_equivalent_value"]    # 等值货币
                if price - money_equivalent_value >= 0:
                    rebate_price = price - money_equivalent_value
                else:
                    rebate_price = 0      # 支付金额不能为负数
            elif coupon_type == 1:  # 满减优惠券
                money_equivalent_value = coupon_dict["money_equivalent_value"]    # 等值货币
                minimum_consume = coupon_dict["minimum_consume"]            # 最小限额
                if price >= minimum_consume:   # 满足满减要求
                    rebate_price = price - money_equivalent_value
                else:
                    return -1        # 不满足,抛出异常
            elif coupon_type == 2:  # 折扣优惠券
                minimum_consume = coupon_dict["minimum_consume"]  # 最小限额
                off_percent = coupon_dict["off_percent"]          # 折扣
                if price >= minimum_consume:   # 满足折扣要求
                    rebate_price = price * (off_percent / 100)    # 乘以折扣
                else:
                    return -1
            return rebate_price      # 返回待支付价格

    5、校验价格

    class PaymentView(APIView):
        authentication_classes = [LoginAuth, ]     # 登录认证
        def post(self, request):
            res = BaseResponse()
            # 1.获取数据
            balance = request.data.get("balance", 0)     # 贝里(积分),不传默认为0
            price = request.data.get("price", "")        # 价格,不传默认为空字符串
            user_id = request.user.pk
            # 2.校验数据的合法性
            # 2.1 校验贝里数是否合法
            if int(balance) > request.user.balance:      # 如果传来的贝里大于用户数据库中贝里
                res.code = 1070
                res.error = "抵扣的贝里错误"
                return Response(res.dict)
            # 2.2 从用户的结算中心拿数据,与数据库比对是否合法
            settlement_key = SETTLEMENT_KEY % (user_id, "*")   # 课程id未知,用*代替
            all_keys = CONN.scan_iter(settlement_key)    # 得到所有的键
            course_rebate_total_price = 0          # 初始化总价为0
            for key in all_keys:
                settlement_info = CONN.hgetall(key)     # 结算中心信息
                # 2.2.1 比对课程id是否合法
                course_id = settlement_info["id"]
                course_obj = Course.objects.filter(id=course_id).first()   # 拿到课程对象
                if not course_obj or course_obj.status ==1:    # status为1,课程下架
                    res.code = 1071
                    res.error = "课程id不合法"
                    return Response(res.dict)
                # 2.2.2 校验优惠券是否过期
                course_coupon_id = settlement_info.get("default_coupon_id", 0)   # 用get取值,避免为空报错
                if course_coupon_id:
                    coupon_dict = Coupon.objects.filter(
                        id=course_coupon_id,               # 拿到优惠券对象
                        couponrecord__status=0,            # 必须是未使用的
                        couponrecord__account_id=user_id,  # 必须是当前用户的
                        object_id=course_id,               # 必须是当前课程的
                        valid_begin_date__lte=now(),       # 符合使用有效期
                        valid_end_date__gte=now()
                    ).values(         # 不拿对象,取如下数据
                        "coupon_type",               # 优惠券类型
                        "money_equivalent_value",    # 等值货币
                        "off_percent",               # 折扣比
                        "minimum_consume"            # 最小限额
                    )
                if not coupon_dict:   # 没有说明优惠券不合法
                    res.code = 1072
                    res.error = "优惠券不合法"
                    return Response(res.dict)
                # 2.3 校验价格price
                # 2.3.1 得到所有的课程折后价格之和
                course_price = settlement_info["price"]
                course_rebate_price = self.account_price(coupon_dict, course_price)
                if course_rebate_price == -1:    # coupon_type有问题
                    res.code = 1074
                    res.error = "课程优惠券不符合要求"
                    return Response(res.dict)
                course_rebate_total_price += course_rebate_price     # 得到课程所有折后价格之后
            # 2.3.2 校验全局优惠券是否合法
            global_coupon_key = GLOBAL_COUPON_KEY % user_id
            global_coupon_id = int(CONN.hget(global_coupon_key, "default_global_coupon_id"))   # 转整数
            if global_coupon_id:
                global_coupon_dict = Coupon.objects.filter(
                    id=global_coupon_id,
                    couponrecord__status=0,
                    couponrecord__account_id=user_id,
                    valid_begin_date__lte=now(),
                    valid_end_date__gte=now()
                ).values(
                    "coupon_type",  # 优惠券类型
                    "money_equivalent_value",  # 等值货币
                    "off_percent",  # 折扣比
                    "minimum_consume"  # 最小限额
                )
            if not global_coupon_dict:
                res.code = 1073
                res.error = "全局优惠券id不合法"
                return Response(res.dict)
            # 2.3.3 与全局优惠券做折扣
            global_rebate_price = self.account_price(global_coupon_dict, course_rebate_total_price)
            if global_rebate_price == -1:
                res.code = 1076
                res.error = "全局优惠券不符合要求"
                return Response(res.dict)
            # 2.3.4 折扣贝里
            balance_money = balance / 100
            balance_rebate_price = global_rebate_price - balance
            if balance_rebate_price < 0:
                balance_rebate_price = 0    # 抵扣完贝里不会为负
            # 2.3.5 终极校验price
            if balance_rebate_price != price:
                res.code = 1078
                res.error = "价格不合法"
                return Response(res.dict)

      拿到价格后,就可以创建订单了,此时的订单状态是未支付状态。

      然后调用支付宝接口来支付了,如果支付成功支付宝接口将返回回调。收到回调将改变订单状态。

      注意:如果支付多个课程,订单详情表需要有多个记录,更改优惠券的使用状态、更改用户表的贝里、贝里记录表需添加交易记录。

    二、支付宝支付

      之前曾用过支付宝沙箱环境,博客地址:支付宝支付

      查看沙箱环境支付宝API:https://docs.open.alipay.com/api

      选择沙箱环境统一收单下单并支付页面接口:https://docs.open.alipay.com/api_1/alipay.trade.page.pay

      之前支付宝没有Python的sdk,都是使用的github开源项目,现在有蚂蚁金服开放平台的官方SDK:https://pypi.org/project/alipay-sdk-python/

  • 相关阅读:
    31天重构学习笔记23. 引入参数对象
    31天重构学习笔记31. 使用多态代替条件判断
    31天重构学习笔记25. 引入契约式设计
    新加坡面试经历
    Release a AutoUpdater tool
    31天重构学习笔记24. 分解复杂判断
    31天重构学习笔记29. 去除中间人对象
    自动更新组件分享
    WPF基础到企业应用系列7——深入剖析依赖属性(WPF/Silverlight核心)
    (收藏)2010年度十大杰出IT博客
  • 原文地址:https://www.cnblogs.com/xiugeng/p/12240194.html
Copyright © 2020-2023  润新知