路飞学城项目-支付相关-结算接口
############### 结算接口分析 ################
""" 结算 一,基于购物车的结算 现在购物车里面有什么内容了, 每一个用户的课程信息,包括标题,图片,所有的价格策略,选中的价格策略, 注意: 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
############### 结算接口 ################
############### 结算接口 ################
技术改变命运