路飞学城项目-支付相关-结算接口

  ###############    结算接口分析   ################

"""
结算
一,基于购物车的结算
现在购物车里面有什么内容了,
每一个用户的课程信息,包括标题,图片,所有的价格策略,选中的价格策略,
注意:
1,在结算页面不允许再修改价格策略,
2,结算信息只需要选中的价格策略,不需要其他的价格策略了。
3,点击购物车页面的去结算,是做了两个事情,1是加入结算信息,2是跳转结算页面,,
4,这个地方的难点在于优惠券,可以选择优惠券,

二,优惠券的分析
优惠券有三种
1,满减优惠券
2,通用优惠券,
3,折扣优惠券,
优惠券需要两张表,
1,一个优惠券生成规则表,通用类型需要一个字段等值货币,折扣类型需要折扣比例字段,满减需要最低消费字段,
contenttype可以绑定专题课和学位课,
2,一个发放和消费记录表,需要有拥有者和状态,优惠券需要和订单关联起来,就是哪一个订单用的,

三,存储结算信息的数据结构

第一种数据结构:
account_userid_courseid{
    "course_id":{
         "title":
          "img":
          "policy_id":default_policy,
          period : 60
          period_display: 2个月
          price : 99
          coupon: {
            1:{
                'coupon_type':
                'coupon_display':
                'money_equivalent_value'
                'minimum_consume'
                'off_percent'
                }
          }
           default_coupon":0
       }
  payment_global_coupon_用户ID: {
  'coupon': {   # 这个字典存储用户所有可用的通用优惠券
      2: {'money_equivalent_value': 200},  # 优惠券 ID :{优惠券信息}
  },
  'default_coupon': 0
 }

}

第二种数据结构,
account_userid_courseid:{
    course_info: {  # 这个字典存储用户所有课程信息
      id:
      title:
      img:
      price:{  # 实际这个价格策略不需要这么多,只需要选中的价格策略就可以了,因为结算页面不允许修改价格策略,全都放进来是好放,直接redis获取到然后直接放进来
          1:
          2:
      }
      default_price:
        }
    coupon:{  # 这个字典存储用户所有可用的课程优惠券
        1:{}
        2:{}
           default_coupon_id :1  # 初始进来这个默认是空的,选择了一个前端发put请求,然后后端直接改掉,这就是修改优惠券信息,
    }
  payment_global_coupon_用户ID: {
      'coupon': {   # 这个字典存储用户所有可用的通用优惠券
          2: {'money_equivalent_value': 200},  # 优惠券 ID :{优惠券信息}
      },
      'default_coupon': 0
 }
}

结算接口的业务逻辑
################################################
post请求:
前端:
1,操作的地方:在购物车页面,选中购物车中的商品,点击去结算,
2,点击需要传的参数:只课程id,这是一个列表的形式,
请求体:{
    courseids :[1,2]
}
不需要再传策略id了,因为购物车里面有一个default_policy_id,这样后端也剩下了一步校验,
不需要发价格,价格购物车也有,

后端:
1,获取前端传过来的课程id,
  每次进来之后,都要清空这个客户的所有的结算信息,
2,获取所有的课程信息放到结算key中,可能会有多个课程
  2.1 拼接shopping_car_key,
  2.2 判断这个 shopping_car_key 是否存在,要学习怎么写异常,
  2.3 获取到redis中的课程信息,
  2.4 放入account_key中,
3,获取到这个客户这个课程的所有的优惠券信息,然后放入account_key中
  3.1 根据课程id,userid,状态,开始时间,结束时间,object_id,content_type_id,查询符合条件的优惠券信息
  3.2 查到之后这是一个集合,需要遍历然后放入account_key中的coupon中,可能会有多个,
4,获取通用的优惠券信息,
  4.1 然后放入一个新的通用券key中,
5,返回数据

##################################################
get请求
需要传给前端,
1,课程信息
2,课程优惠券信息
3,全局优惠券信息,

#####################################################
put请求,选择优惠券
前端请求参数:
{
course_id : 1 # 如果是通用优惠券这个值就是空,
coupon_id : 1
}
后端,
1,对于传过来的数据,需要课程校验是否存在,优惠券校验是否存在,
2,然后修改redis数据,本质就是redis的操作问题了,

注意:
1,优惠券的状态
2,优惠券的起始时间,



"""

  

  ###############    结算接口相关的表   ################

class Coupon(models.Model):
    """优惠券生成规则"""
    name = models.CharField(max_length=64, verbose_name="活动名称")
    brief = models.TextField(blank=True, null=True, verbose_name="优惠券介绍")
    coupon_type_choices = ((0, '通用券'), (1, '满减券'), (2, '折扣券'))
    coupon_type = models.SmallIntegerField(choices=coupon_type_choices, default=0, verbose_name="券类型")

    """
    通用:
        money_equivalent_value=100
        off_percent=null
        minimum_consume=0
    满减:
        money_equivalent_value=100
        off_percent=null
        minimum_consume=1000
    折扣:
        money_equivalent_value=0
        off_percent=79
        minimum_consume=0
    """
    money_equivalent_value = models.IntegerField(verbose_name="等值货币")
    off_percent = models.PositiveSmallIntegerField(verbose_name="折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True,
                                                   null=True)
    minimum_consume = models.PositiveIntegerField(verbose_name="最低消费", default=0, help_text="仅在满减券时填写此字段")

    content_type = models.ForeignKey(ContentType, blank=True, null=True)
    object_id = models.PositiveIntegerField("绑定课程", blank=True, null=True, help_text="可以把优惠券跟课程绑定")
    content_object = GenericForeignKey('content_type', 'object_id')

    quantity = models.PositiveIntegerField("数量(张)", default=1)
    open_date = models.DateField("优惠券领取开始时间")
    close_date = models.DateField("优惠券领取结束时间")
    valid_begin_date = models.DateField(verbose_name="有效期开始时间", blank=True, null=True)
    valid_end_date = models.DateField(verbose_name="有效结束时间", blank=True, null=True)
    coupon_valid_days = models.PositiveIntegerField(verbose_name="优惠券有效期(天)", blank=True, null=True,
                                                    help_text="自券被领时开始算起")
    date = models.DateTimeField(auto_now_add=True, verbose_name="创建优惠券的日期")

    class Meta:
        verbose_name_plural = "31. 优惠券生成记录"

    def __str__(self):
        return "%s(%s)" % (self.get_coupon_type_display(), self.name)

    def save(self, *args, **kwargs):
        if not self.coupon_valid_days or (self.valid_begin_date and self.valid_end_date):
            if self.valid_begin_date and self.valid_end_date:
                if self.valid_end_date <= self.valid_begin_date:
                    raise ValueError("valid_end_date 有效期结束日期必须晚于 valid_begin_date ")
            if self.coupon_valid_days == 0:
                raise ValueError("coupon_valid_days 有效期不能为0")
        if self.close_date < self.open_date:
            raise ValueError("close_date 优惠券领取结束时间必须晚于 open_date优惠券领取开始时间 ")

        super(Coupon, self).save(*args, **kwargs)



class CouponRecord(models.Model):
    """优惠券发放、消费纪录"""
    coupon = models.ForeignKey("Coupon")
    number = models.CharField(max_length=64, unique=True, verbose_name="优惠券的编号")
    account = models.ForeignKey("UserInfo", verbose_name="拥有者")
    status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'))
    status = models.SmallIntegerField(choices=status_choices, default=0)
    get_time = models.DateTimeField(verbose_name="领取时间", help_text="用户领取时间")
    used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间")

    # order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单")  # 一个订单可以有多个优惠券

    class Meta:
        verbose_name_plural = "32. 用户优惠券"

    def __str__(self):
        return '%s-%s-%s' % (self.account, self.number, self.status)

   ###############    结算接口第一种结构   ################

from rest_framework.views import APIView
from rest_framework.response import Response
from utils.auth import LuffyAuth
from django.conf import settings
from django_redis import get_redis_connection
import json
from utils.response import BaseResponse
from api import models
import datetime

class PaymentViewSet(APIView):

    authentication_classes =  [LuffyAuth,]
    conn = get_redis_connection("default")

    def post(self,request,*args,**kwargs):
        ret = BaseResponse()
        try:
            # 清空当前用户结算中心的数据
            # luffy_payment_1_*
            # luffy_payment_coupon_1
            key_list = self.conn.keys( settings.PAYMENT_KEY %(request.auth.user_id,"*",) )
            key_list.append(settings.PAYMENT_COUPON_KEY %(request.auth.user_id,))
            self.conn.delete(*key_list)

            payment_dict = {}  # 这里保存课程信息,和课程优惠券,

            global_coupon_dict = {
                "coupon":{},
                "default_coupon":0
            }

            # 1. 获取用户要结算课程ID
            course_id_list = request.data.get('courseids')
            for course_id in course_id_list:
                car_key = settings.SHOPPING_CAR_KEY %(request.auth.user_id,course_id,)

                # 1.1 检测用户要结算的课程是否已经加入购物车
                if not self.conn.exists(car_key):
                    ret.code = 1001
                    ret.error = "课程需要先加入购物车才能结算"
                # 1.2 从购物车中获取信息,放入到结算中心。
                # 获取标题和图片
                policy = json.loads(self.conn.hget(car_key, 'policy').decode('utf-8'))
                default_policy = self.conn.hget(car_key, 'default_policy').decode('utf-8')
                policy_info = policy[default_policy]  # 这也是一个字典,里面有期限,期限display,价格,

                payment_course_dict = {
                    "course_id":str(course_id),
                    "title":self.conn.hget(car_key, 'title').decode('utf-8'),
                    "img":self.conn.hget(car_key, 'img').decode('utf-8'),
                    "policy_id":default_policy,
                    "coupon":{},
                    "default_coupon":0
                }
                payment_course_dict.update(policy_info)  # 把字典policy_info中的字段更新到payment_course_dict这个字典中,
                payment_dict[str(course_id)] = payment_course_dict


            # 2. 获取优惠券
            ctime = datetime.date.today()

            coupon_list = models.CouponRecord.objects.filter(
                account=request.auth.user,
                status=0,
                coupon__valid_begin_date__lte=ctime,
                coupon__valid_end_date__gte=ctime,
            )


            for item in coupon_list:  # item是获取到的每一个优惠券,


                # 只处理绑定课程的优惠券
                if not item.coupon.object_id:  # 处理通用优惠券,
                    # 优惠券ID
                    coupon_id = item.id

                    # 优惠券类型:满减、折扣、立减
                    coupon_type = item.coupon.coupon_type

                    info = {}
                    info['coupon_type'] = coupon_type
                    info['coupon_display'] = item.coupon.get_coupon_type_display()
                    if coupon_type == 0:  # 立减
                        info['money_equivalent_value'] = item.coupon.money_equivalent_value
                    elif coupon_type == 1:  # 满减券
                        info['money_equivalent_value'] = item.coupon.money_equivalent_value
                        info['minimum_consume'] = item.coupon.minimum_consume
                    else:  # 折扣
                        info['off_percent'] = item.coupon.off_percent

                    global_coupon_dict['coupon'][coupon_id] = info

                    continue
                # 优惠券绑定课程的ID
                coupon_course_id = str(item.coupon.object_id)

                # 优惠券ID
                coupon_id = item.id

                # 优惠券类型:满减、折扣、立减
                coupon_type = item.coupon.coupon_type

                info = {}
                info['coupon_type'] = coupon_type
                info['coupon_display'] = item.coupon.get_coupon_type_display()
                if coupon_type == 0: # 立减
                    info['money_equivalent_value'] = item.coupon.money_equivalent_value
                elif coupon_type == 1: # 满减券
                    info['money_equivalent_value'] = item.coupon.money_equivalent_value
                    info['minimum_consume'] = item.coupon.minimum_consume
                else: # 折扣
                    info['off_percent'] = item.coupon.off_percent

                if coupon_course_id not in payment_dict:
                    # 获取到优惠券,但是没有购买此课程
                    continue
                # 将优惠券设置到指定的课程字典中
                payment_dict[coupon_course_id]['coupon'][coupon_id] = info

            # 可以获取绑定的优惠券

            # 3. 将绑定优惠券课程+全站优惠券 写入到redis中(结算中心)。
            # 3.1 绑定优惠券课程放入redis
            for cid,cinfo in payment_dict.items():
                pay_key = settings.PAYMENT_KEY %(request.auth.user_id,cid,)
                cinfo['coupon'] = json.dumps(cinfo['coupon'])
                self.conn.hmset(pay_key,cinfo)
            # 3.2 将全站优惠券写入redis
            gcoupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,)
            global_coupon_dict['coupon'] = json.dumps(global_coupon_dict['coupon'])
            self.conn.hmset(gcoupon_key,global_coupon_dict)

        except Exception as e:
            pass

        return Response(ret.dict)

    def patch(self,request,*args,**kwargs):

        ret = BaseResponse()
        try:
            # 1. 用户提交要修改的优惠券
            course = request.data.get('courseid')
            course_id = str(course) if course else course

            coupon_id = str(request.data.get('couponid'))

            # payment_global_coupon_1
            redis_global_coupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,)

            # 修改全站优惠券
            if not course_id:
                if coupon_id == "0":
                    # 不使用优惠券,请求数据:{"couponid":0}
                    self.conn.hset(redis_global_coupon_key,'default_coupon',coupon_id)
                    ret.data = "修改成功"
                    return Response(ret.dict)
                # 使用优惠券,请求数据:{"couponid":2}
                coupon_dict = json.loads(self.conn.hget(redis_global_coupon_key,'coupon').decode('utf-8'))

                # 判断用户选择得优惠券是否合法
                if coupon_id not in coupon_dict:
                    ret.code = 1001
                    ret.error = "全站优惠券不存在"
                    return Response(ret.dict)

                # 选择的优惠券合法
                self.conn.hset(redis_global_coupon_key, 'default_coupon', coupon_id)
                ret.data = "修改成功"
                return Response(ret.dict)

            # 修改课程优惠券
            # luffy_payment_1_1
            redis_payment_key = settings.PAYMENT_KEY % (request.auth.user_id, course_id,)
            # 不使用优惠券
            if coupon_id == "0":
                self.conn.hset(redis_payment_key,'default_coupon',coupon_id)
                ret.data = "修改成功"
                return Response(ret.dict)

            # 使用优惠券
            coupon_dict = json.loads(self.conn.hget(redis_payment_key,'coupon').decode('utf-8'))
            if coupon_id not in coupon_dict:
                ret.code = 1010
                ret.error = "课程优惠券不存在"
                return Response(ret.dict)

            self.conn.hset(redis_payment_key,'default_coupon',coupon_id)

        except Exception as e:
            ret.code = 1111
            ret.error = "修改失败"

        return Response(ret.dict)

    def get(self,request,*args,**kwargs):

        ret = BaseResponse()

        try:
            # luffy_payment_1_*
            redis_payment_key = settings.PAYMENT_KEY %(request.auth.user_id,"*",)

            # luffy_payment_coupon_1
            redis_global_coupon_key = settings.PAYMENT_COUPON_KEY %(request.auth.user_id,)

            # 1. 获取绑定课程信息
            course_list = []
            for key in self.conn.scan_iter(redis_payment_key):
                info = {}
                data = self.conn.hgetall(key)
                for k,v in data.items():
                    kk = k.decode('utf-8')
                    if kk == "coupon":
                        info[kk] = json.loads(v.decode('utf-8'))
                    else:
                        info[kk] = v.decode('utf-8')
                course_list.append(info)

            # 2.全站优惠券
            global_coupon_dict = {
                'coupon':json.loads(self.conn.hget(redis_global_coupon_key,'coupon').decode('utf-8')),
                'default_coupon':self.conn.hget(redis_global_coupon_key,'default_coupon').decode('utf-8')
            }

            ret.data = {
                "course_list":course_list,
                "global_coupon_dict":global_coupon_dict
            }
        except Exception as e:
            ret.code = 1001
            ret.error = "获取失败"

        return Response(ret.dict)

   ###############    结算接口第二种结构  ################

from api.exceptions import CommentException
import datetime
class AccountView(APIView):
    authentication_classes = [TokenAuth, ]
    conn = get_redis_connection('default')

    def post(self,request):
        res = BaseException()
        account_dict = {}
        try:
            # 1,获取前端的课程id
            course_list = request.data.get('course_id')
            user_id = request.user.id
            print(course_list,user_id)

            # 清空redis操作,把所有的结算数据全部清除
            del_list = self.conn.keys(settings.PAYMENT_COURSE_KEY.format(user_id, '*'))
            print(del_list)
            for key in del_list:
                self.conn.delete(key)  # 删除该用户的课程、优惠券信息的key
            print('--------')
            for course_id in course_list:
                # 2,判断购物车中是否有这个信息
                shopping_car_id = settings.SHOPPING_CART_KEY.format(user_id,course_id)
                print(shopping_car_id)
                if not self.conn.exists(shopping_car_id):
                    raise CommentException(10033,'购物车不存在这个课程')
                # 2.1 存在这个key,就把内容取出来然后放入 account_dict
                account_dict['course_info'] = str(self.conn.hgetall(shopping_car_id))
                account_dict['course_coupon'] = str(self.get_coupon_dict(request,course_id))

                # 把数据存入redis
                account_key = settings.PAYMENT_COURSE_KEY.format(user_id,course_id)
                self.conn.set(account_key,json.dumps(account_dict))

            # 构建通用券
            global_key = settings.PAYMENT_GLOBAL_COUPON_KEY.format(user_id)
            self.conn.set(global_key,json.dumps(self.get_coupon_dict(request)))
            res.data='加入成功'
        except CommentException as e:
            print(e)
            res.code= e.code
            res.error = e.msg
        except Exception as e:
            print(e)
            res.code = 500
            res.error = '添加结算信息失败'
        return Response(res.dict)

    def get_coupon_dict(self,request,course_id=None):
        now = datetime.datetime.utcnow()
        # 3,把这个课程的优惠券拿出来,放进去account_dict
        coupon_list = models.CouponRecord.objects.filter(
            status=0,
            account=request.user,
            coupon__valid_begin_date__lte=now,
            coupon__valid_end_date__gte=now,
            # coupon__content_type_id= 这个值理论上需要加,因为会有多个,
            coupon__object_id=course_id
        )
        # 3.1 把优惠券的内容怎么放?放什么字段?这个地方就是对业务的理解问题了,
        coupon_dict = {}
        for coupon_item in coupon_list:
            coupon_dict[coupon_item.id] = {
                "name": coupon_item.coupon.name,
                "money_equivalent_value": coupon_item.coupon.money_equivalent_value,
                "off_percent": coupon_item.coupon.off_percent,
                "minimum_consume": coupon_item.coupon.minimum_consume,
                "coupon_type": coupon_item.coupon.get_coupon_type_display(),
                "valid_begin_date": coupon_item.coupon.valid_begin_date.strftime('%Y-%m-%d'),
                "valid_end_date": coupon_item.coupon.valid_end_date.strftime('%Y-%m-%d'),
            }
        return coupon_dict

###############    结算接口   ################

###############    结算接口   ################

原文地址:https://www.cnblogs.com/andy0816/p/12302095.html