商城支付功能

一、支付接口

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/

 

posted @ 2020-01-30 20:58  休耕  阅读(583)  评论(0编辑  收藏  举报