现在有不少测试朋友做的项目中,可能也会涉及到支付相关的功能。比如:做商城的,做游戏的以及其他在线交易的网站、APP等。如果支付出了问题,或者用户拿少的钱通过篡改请求数据购买大金额的商品,如果是实物的话,发货前还有可能被发现。如果是虚拟商品话费、游戏币等就有可能造成损失。 所以,不管是实物也好,虚拟商品也好,涉及到支付功能时,大家在测试的过程中一定要重视,否则,会造成很大损失。 那我们今天就来说说支付流程的测试点,废话不多说,来进入我们今天的正题吧。 第一步基本测试 1、安全权限检测 登录或不登录 2、 选择的支付方式 ①网上银行(借记卡和信用卡)直接支付,网上账号支付(通过充值后再支付),第三方平台支付(支付宝,云网,快钱等); ②借记卡未开通网上银行有无提醒,每家银行的接口测试(国有四大银行、招行等其他及国外银行); ③信用卡是否开通网上银行,有无当天支付限额;、 ④借记卡与信用卡是直接输入卡号、密码、验证码、卡上专用码,还是直接使用用户名和密码加动态密码支付; ⑤ 直接支付考虑充值费用与所支付费用是否平衡,是否包含一定手续费; ⑥系统帐号支付:将银行卡或第三方钱转到系统帐号进行支付,检测账户余额不足时是否提示;是否有当天限额; ⑦第三方平台支付,接口的测试; ⑧是否支持批量支付; ⑨是否需要身份验证、手机短信提示,找他人代付功能; 3、收款功能 提现(转到银行卡或公司账户)功能 4、 账户管理 5、账单查询 6、打印,传真,邮件提醒功能 第二步异常情况 1、网络带宽问题 2、无法正常充值,网上银行充值有问题,如银行服务器忙等 3、 并发用户多 4、充值、支付成功,但数据未更新 第三步测试方法 1、流程图 画支付流程图,依据业务流程进行功能全覆盖测试 2、接口测试 对支付接口进行重点测试(因支付方式多样,所以选择性广) 3、功能测试 采用黑盒测试策略应采用黑盒测试策略,使用等价类划分、边界值分析、因果图法、判定表法等测试用例设计方法的原理与实现,分别对支付系统的功能、账户和交易风险监控、系统性能及安全性等测试指标项进行测试。黑盒测试法应制订覆盖全部功能模块的测试用例,通过执行测试用例以实现系统功能、业务流程和其它质量特性的测试。 4、安全性测试 ①URL有参数的手动修改参数,看是否得到其他用户的信息和相关页面; ② 在登录输入框的地方输入“or 1=1--”看是否有SQL注入 ③在注重SQL注入的同时,一般在有输入框的地方输入 ④输入的数据没有进行有效的控制和验证 ⑤ 直接输入需要权限的页面地址可用访问 5、性能测试 带负载情况下的响应时间和吞吐率,在某个时间段内同时访问系统的用户数量,或是在线数据处理的数量。 软件的安全性测试 用户购买100元游戏币时,前往第三方支付跳转进行金额的篡改由100元改成0.01元,结果就拿了0.01元充值了100元的游戏币。对订单金额没有做校验导致这样的后果,损失比较大。 测试的过程中一定要注意对服务端进行校验,支付时数据的篡改一定要有校验。当同步、异步通知都存在的情况的,异步通知(第三方支付成功后台通知),没有到账,导致部分用户充值不到账,引起客户投诉。当同步、异步并存的时候,一定要分别对同步和异步进行检验,确保都能正常到账。我们所做的绝大多少的互联网产品都会涉及到第三方支付,所以支付功能必然是重要的,作为测试互联网产品的一员,我们必须要做好支付的安全性。
一、开发背景:
二、团队
团队
- 开发
- 前端 1
- 后端 4-5
- 运维 1
- UI 1
- 测试 1
- 产品经理 1
- 运营 2
- 销售 2
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
from django.db.models import Q
class CourseCategory(models.Model):
"""课程大类, e.g 前端 后端..."""
name = models.CharField(max_length=64, unique=True)
def __str__(self):
return "%s" % self.name
class Meta:
verbose_name = "课程大类"
verbose_name_plural = "课程大类"
class CourseSubCategory(models.Model):
"""课程子类, e.g python linux """
category = models.ForeignKey("CourseCategory")
name = models.CharField(max_length=64, unique=True)
def __str__(self):
return "%s" % self.name
class Meta:
verbose_name = "课程子类"
verbose_name_plural = "课程子类"
class DegreeCourse(models.Model):
"""学位课程"""
name = models.CharField(max_length=128, unique=True)
course_img = models.CharField(max_length=255, verbose_name="缩略图")
brief = models.TextField(verbose_name="学位课程简介", )
total_scholarship = models.PositiveIntegerField(verbose_name="总奖学金(贝里)", default=40000)
mentor_compensation_bonus = models.PositiveIntegerField(verbose_name="本课程的导师辅导费用(贝里)", default=15000)
# 忽略,用于GenericForeignKey反向查询, 不会生成表字段,切勿删除
coupon = GenericRelation("Coupon")
# 为了计算学位奖学金
period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=150)
prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师")
# 忽略,用于GenericForeignKey反向查询,不会生成表字段,切勿删除
degreecourse_price_policy = GenericRelation("PricePolicy")
def __str__(self):
return self.name
class Scholarship(models.Model):
"""学位课程奖学金"""
degree_course = models.ForeignKey("DegreeCourse")
time_percent = models.PositiveSmallIntegerField(verbose_name="奖励档位(时间百分比)", help_text="只填百分值,如80,代表80%")
value = models.PositiveIntegerField(verbose_name="奖学金数额")
def __str__(self):
return "%s:%s" % (self.degree_course, self.value)
class Course(models.Model):
"""课程"""
name = models.CharField(max_length=128, unique=True)
course_img = models.CharField(max_length=255)
sub_category = models.ForeignKey("CourseSubCategory")
course_type_choices = ((0, \'付费\'), (1, \'VIP专享\'), (2, \'学位课程\'))
course_type = models.SmallIntegerField(choices=course_type_choices)
degree_course = models.ForeignKey("DegreeCourse", blank=True, null=True, help_text="若是学位课程,此处关联学位表")
brief = models.TextField(verbose_name="课程概述", max_length=2048)
level_choices = ((0, \'初级\'), (1, \'中级\'), (2, \'高级\'))
level = models.SmallIntegerField(choices=level_choices, default=1)
pub_date = models.DateField(verbose_name="发布日期", blank=True, null=True)
period = models.PositiveIntegerField(verbose_name="建议学习周期(days)", default=7)
order = models.IntegerField("课程顺序", help_text="从上一个课程数字往后排")
attachment_path = models.CharField(max_length=128, verbose_name="课件路径", blank=True, null=True)
status_choices = ((0, \'上线\'), (1, \'下线\'), (2, \'预上线\'))
status = models.SmallIntegerField(choices=status_choices, default=0)
template_id = models.SmallIntegerField("前端模板id", default=1)
coupon = GenericRelation("Coupon")
# 用于GenericForeignKey反向查询,不会生成表字段,切勿删除
price_policy = GenericRelation("PricePolicy")
def __str__(self):
return "%s(%s)" % (self.name, self.get_course_type_display())
def save(self, *args, **kwargs):
if self.course_type == 2:
if not self.degree_course:
raise ValueError("学位课程必须关联对应的学位表")
super(Course, self).save(*args, **kwargs)
class PricePolicy(models.Model):
"""价格与有课程效期表"""
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(\'content_type\', \'object_id\')
valid_period_choices = ((1, \'1天\'), (3, \'3天\'),
(7, \'1周\'), (14, \'2周\'),
(30, \'1个月\'),
(60, \'2个月\'),
(90, \'3个月\'),
(180, \'6个月\'), (210, \'12个月\'),
(540, \'18个月\'), (720, \'24个月\'),
)
valid_period = models.SmallIntegerField(choices=valid_period_choices)
price = models.FloatField()
class Meta:
unique_together = ("content_type", \'object_id\', "valid_period")
def __str__(self):
return "%s(%s)%s" % (self.content_object, self.get_valid_period_display(), self.price)
class CourseDetail(models.Model):
"""课程详情页内容"""
course = models.OneToOneField("Course")
hours = models.IntegerField("课时")
course_slogan = models.CharField(max_length=125, blank=True, null=True)
video_brief_link = models.CharField(verbose_name=\'课程介绍\', max_length=255, blank=True, null=True)
why_study = models.TextField(verbose_name="为什么学习这门课程")
what_to_study_brief = models.TextField(verbose_name="我将学到哪些内容")
career_improvement = models.TextField(verbose_name="此项目如何有助于我的职业生涯")
prerequisite = models.TextField(verbose_name="课程先修要求", max_length=1024)
recommend_courses = models.ManyToManyField("Course", related_name="recommend_by", blank=True)
teachers = models.ManyToManyField("Teacher", verbose_name="课程讲师")
def __str__(self):
return "%s" % self.course
class OftenAskedQuestion(models.Model):
"""常见问题"""
content_type = models.ForeignKey(ContentType,limit_choices_to={\'model__contains\': \'course\'}) # 关联course or degree_course
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(\'content_type\', \'object_id\')
question = models.CharField(max_length=255)
answer = models.TextField(max_length=1024)
def __str__(self):
return "%s-%s" % (self.content_object, self.question)
class Meta:
unique_together = (\'content_type\', \'object_id\', \'question\')
class CourseOutline(models.Model):
"""课程大纲"""
course_detail = models.ForeignKey("CourseDetail")
title = models.CharField(max_length=128)
# 前端显示顺序
order = models.PositiveSmallIntegerField(default=1)
content = models.TextField("内容", max_length=2048)
def __str__(self):
return "%s" % self.title
class Meta:
unique_together = (\'course_detail\', \'title\')
class CourseChapter(models.Model):
"""课程章节"""
course = models.ForeignKey("Course", related_name=\'coursechapters\')
chapter = models.SmallIntegerField(verbose_name="第几章", default=1)
name = models.CharField(max_length=128)
summary = models.TextField(verbose_name="章节介绍", blank=True, null=True)
pub_date = models.DateField(verbose_name="发布日期", auto_now_add=True)
class Meta:
unique_together = ("course", \'chapter\')
def __str__(self):
return "%s:(第%s章)%s" % (self.course, self.chapter, self.name)
class CourseSection(models.Model):
"""课时目录"""
chapter = models.ForeignKey("CourseChapter", related_name=\'coursesections\')
name = models.CharField(max_length=128)
order = models.PositiveSmallIntegerField(verbose_name="课时排序", help_text="建议每个课时之间空1至2个值,以备后续插入课时")
section_type_choices = ((0, \'文档\'), (1, \'练习\'), (2, \'视频\'))
section_type = models.SmallIntegerField(default=2, choices=section_type_choices)
# vid: cc视频,唯一标识:Dfadljfosdfjlsdfjs
section_link = models.CharField(max_length=255, blank=True, null=True, help_text="若是video,填vid,若是文档,填link")
video_time = models.CharField(verbose_name="视频时长", blank=True, null=True, max_length=32) # 仅在前端展示使用
pub_date = models.DateTimeField(verbose_name="发布时间", auto_now_add=True)
free_trail = models.BooleanField("是否可试看", default=False)
class Meta:
unique_together = (\'chapter\', \'section_link\')
def __str__(self):
return "%s-%s" % (self.chapter, self.name)
class Homework(models.Model):
chapter = models.ForeignKey("CourseChapter")
title = models.CharField(max_length=128, verbose_name="作业题目")
order = models.PositiveSmallIntegerField("作业顺序", help_text="同一课程的每个作业之前的order值间隔1-2个数")
homework_type_choices = ((0, \'作业\'), (1, \'模块通关考核\'))
homework_type = models.SmallIntegerField(choices=homework_type_choices, default=0)
requirement = models.TextField(max_length=1024, verbose_name="作业需求")
threshold = models.TextField(max_length=1024, verbose_name="踩分点")
recommend_period = models.PositiveSmallIntegerField("推荐完成周期(天)", default=7)
scholarship_value = models.PositiveSmallIntegerField("为该作业分配的奖学金(贝里)")
note = models.TextField(blank=True, null=True)
enabled = models.BooleanField(default=True, help_text="本作业如果后期不需要了,不想让学员看到,可以设置为False")
class Meta:
unique_together = ("chapter", "title")
def __str__(self):
return "%s - %s" % (self.chapter, self.title)
class Teacher(models.Model):
"""讲师、导师表"""
name = models.CharField(max_length=32)
role_choices = ((0, \'讲师\'), (1, \'导师\'))
role = models.SmallIntegerField(choices=role_choices, default=0)
title = models.CharField(max_length=64, verbose_name="职位、职称")
signature = models.CharField(max_length=255, help_text="导师签名", blank=True, null=True)
image = models.CharField(max_length=128)
brief = models.TextField(max_length=1024)
def __str__(self):
return self.name
# 普通课购买课程后,我的订单中去评价。
class CourseReview(models.Model):
"""课程评价"""
enrolled_course = models.OneToOneField("EnrolledCourse")
about_teacher = models.FloatField(default=0, verbose_name="讲师讲解是否清晰")
about_video = models.FloatField(default=0, verbose_name="内容实用")
about_course = models.FloatField(default=0, verbose_name="课程内容通俗易懂")
review = models.TextField(max_length=1024, verbose_name="评价")
disagree_number = models.IntegerField(default=0, verbose_name="踩")
agree_number = models.IntegerField(default=0, verbose_name="赞同数")
date = models.DateTimeField(auto_now_add=True, verbose_name="评价日期")
is_recommend = models.BooleanField("热评推荐", default=False)
hide = models.BooleanField("不在前端页面显示此条评价", default=False)
def __str__(self):
return "%s-%s" % (self.enrolled_course.course, self.review)
# 学位课购买课程后,我的订单中去天报名表,然后就编程去学习(以后去评价)。
class DegreeCourseReview(models.Model):
"""学位课程评价
为了以后可以定制单独的评价内容,所以不与普通课程的评价混在一起,单独建表
"""
enrolled_course = models.ForeignKey("EnrolledDegreeCourse")
course = models.ForeignKey("Course", verbose_name="评价学位模块", blank=True, null=True,
help_text="不填写即代表评价整个学位课程", limit_choices_to={\'course_type\': 2})
about_teacher = models.FloatField(default=0, verbose_name="讲师讲解是否清晰")
about_video = models.FloatField(default=0, verbose_name="视频质量")
about_course = models.FloatField(default=0, verbose_name="课程")
review = models.TextField(max_length=1024, verbose_name="评价")
disagree_number = models.IntegerField(default=0, verbose_name="踩")
agree_number = models.IntegerField(default=0, verbose_name="赞同数")
date = models.DateTimeField(auto_now_add=True, verbose_name="评价日期")
is_recommend = models.BooleanField("热评推荐", default=False)
hide = models.BooleanField("不在前端页面显示此条评价", default=False)
def __str__(self):
return "%s-%s" % (self.enrolled_course, self.review)
# 购买学位课后,为每个学生每个模块会立刻生成一条学习纪录(未开通)
class StudyRecord(models.Model):
"""学位课程的模块学习进度
报名学位课程后,每个模块会立刻生成一条学习纪录
"""
enrolled_degree_course = models.ForeignKey("EnrolledDegreeCourse")
course_module = models.ForeignKey("Course", verbose_name="学位模块", limit_choices_to={\'course_type\': 2})
open_date = models.DateField(blank=True, null=True, verbose_name="开通日期")
end_date = models.DateField(blank=True, null=True, verbose_name="完成日期")
status_choices = ((2, \'在学\'), (1, \'未开通\'), (0, \'已完成\'))
status = models.SmallIntegerField(choices=status_choices, default=1)
class Meta:
unique_together = (\'enrolled_degree_course\', \'course_module\')
def __str__(self):
return \'%s-%s\' % (self.enrolled_degree_course, self.course_module)
def save(self, *args, **kwargs):
if self.course_module.degree_course_id != self.enrolled_degree_course.degree_course_id:
raise ValueError("学员要开通的模块必须与其报名的学位课程一致!")
super(StudyRecord, self).save(*args, **kwargs)
# 为学生开通一个模块
class CourseSchedule(models.Model):
"""课程进度计划表,针对学位课程,每开通一个模块,就为这个学员生成这个模块的推荐学习计划表,后面的奖惩均按此表进行"""
study_record = models.ForeignKey("StudyRecord")
homework = models.ForeignKey("Homework")
recommend_date = models.DateField("推荐交作业日期")
def __str__(self):
return "%s - %s - %s " % (self.study_record, self.homework, self.recommend_date)
class Meta:
unique_together = (\'study_record\', \'homework\')
# 学生交作业
class HomeworkRecord(models.Model):
"""学员作业记录及成绩"""
homework = models.ForeignKey("Homework")
student = models.ForeignKey("EnrolledDegreeCourse", verbose_name="学生")
score_choices = (
(100, \'A+\'),
(90, \'A\'),
(85, \'B+\'),
(80, \'B\'),
(70, \'B-\'),
(60, \'C+\'),
(50, \'C\'),
(40, \'C-\'),
(-1, \'D\'),
(0, \'N/A\'),
(-100, \'COPY\'),
)
score = models.SmallIntegerField(verbose_name="分数", choices=score_choices, null=True, blank=True)
mentor = models.ForeignKey("Account", related_name="my_stu_homework_record", limit_choices_to={\'role\': 1},
verbose_name="导师")
mentor_comment = models.TextField(verbose_name="导师批注", blank=True, null=True) # 导师
status_choice = (
(0, \'待批改\'),
(1, \'已通过\'),
(2, \'不合格\'),
)
status = models.SmallIntegerField(verbose_name=\'作业状态\', choices=status_choice, default=0)
submit_num = models.SmallIntegerField(verbose_name=\'提交次数\', default=0)
correct_date = models.DateTimeField(\'备注日期\', blank=True, null=True)
note = models.TextField(blank=True, null=True)
date = models.DateTimeField("作业提交日期", auto_now_add=True)
check_date = models.DateTimeField("批改日期", null=True, blank=True)
update_time = models.DateTimeField(auto_now=True, verbose_name="提交日期")
# homework_path = models.CharField(verbose_name=\'作业路径\', max_length=256,blank=True,null=True) 作业路径可以动态拿到,没必要存
reward_choice = (
(0, \'新提交\'),
(1, \'按时提交\'),
(2, \'未按时提交\'),
(3, \'成绩已奖励\'),
(4, \'成绩已处罚\'),
(5, \'未作按时检测\'),
)
reward_status = models.SmallIntegerField(verbose_name=\'作业记录奖惩状态\', default=0)
def __str__(self):
return "%s %s" % (self.homework, self.student)
class Meta:
unique_together = ("homework", "student")
# 导师跟进记录
class StuFollowUpRecord(models.Model):
"""学员跟进记录"""
enrolled_degree_course = models.ForeignKey("EnrolledDegreeCourse", verbose_name="学生")
mentor = models.ForeignKey("Account", related_name=\'mentor\', limit_choices_to={\'role\': 1}, verbose_name="导师")
followup_tool_choices = ((0, \'QQ\'), (1, \'微信\'), (2, \'电话\'), (3, \'系统通知\'))
followup_tool = models.SmallIntegerField(choices=followup_tool_choices, default=1)
record = models.TextField(verbose_name="跟进记录")
attachment_path = models.CharField(max_length=128, blank=True, null=True, verbose_name="附件路径",
help_text="跟进记录的截图等")
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return "%s --%s --%s" % (self.enrolled_degree_course, self.record, self.date)
# ######################## 深科技 ########################
class ArticleSource(models.Model):
"""文章来源"""
name = models.CharField(max_length=64, unique=True)
def __str__(self):
return self.name
class Article(models.Model):
"""文章资讯"""
title = models.CharField(max_length=255, unique=True, db_index=True, verbose_name="标题")
source = models.ForeignKey("ArticleSource", verbose_name="来源")
article_type_choices = ((0, \'资讯\'), (1, \'视频\'))
article_type = models.SmallIntegerField(choices=article_type_choices, default=0)
brief = models.TextField(max_length=512, verbose_name="摘要")
head_img = models.CharField(max_length=255)
content = models.TextField(verbose_name="文章正文")
pub_date = models.DateTimeField(verbose_name="上架日期")
offline_date = models.DateTimeField(verbose_name="下架日期")
status_choices = ((0, \'在线\'), (1, \'下线\'))
status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="状态")
order = models.SmallIntegerField(default=0, verbose_name="权重", help_text="文章想置顶,可以把数字调大,不要超过1000")
vid = models.CharField(max_length=128, verbose_name="视频VID", help_text="文章类型是视频, 则需要添加视频VID", blank=True, null=True)
comment_num = models.SmallIntegerField(default=0, verbose_name="评论数")
agree_num = models.SmallIntegerField(default=0, verbose_name="点赞数")
view_num = models.SmallIntegerField(default=0, verbose_name="观看数")
collect_num = models.SmallIntegerField(default=0, verbose_name="收藏数")
# tags = models.ManyToManyField("Tags", blank=True, verbose_name="标签")
date = models.DateTimeField(auto_now_add=True, verbose_name="创建日期")
position_choices = ((0, \'信息流\'), (1, \'banner大图\'), (2, \'banner小图\'))
position = models.SmallIntegerField(choices=position_choices, default=0, verbose_name="位置")
# comment = GenericRelation("Comment") # 用于GenericForeignKey反向查询, 不会生成表字段,切勿删除,如有疑问请联系老村长
def __str__(self):
return "%s-%s" % (self.source, self.title)
class Collection(models.Model):
"""收藏"""
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(\'content_type\', \'object_id\')
account = models.ForeignKey("Account")
date = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = (\'content_type\', \'object_id\', \'account\')
class Comment(models.Model):
"""通用的评论表"""
# content_type = models.ForeignKey(ContentType, blank=True, null=True, verbose_name="类型")
# object_id = models.PositiveIntegerField(blank=True, null=True)
# content_object = GenericForeignKey(\'content_type\', \'object_id\')
# FK(Article)
article = models.ForeignKey(\'Article\')
p_node = models.ForeignKey("self", blank=True, null=True, verbose_name="父级评论")
content = models.TextField(max_length=1024)
account = models.ForeignKey("Account", verbose_name="会员名")
disagree_number = models.IntegerField(default=0, verbose_name="踩")
agree_number = models.IntegerField(default=0, verbose_name="赞同数")
date = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.content
# ######################## 购买课程相关 ########################
class EnrolledCourse(models.Model):
"""已报名课程,不包括学位课程"""
account = models.ForeignKey("Account")
course = models.ForeignKey("Course", limit_choices_to=~Q(course_type=2))
enrolled_date = models.DateTimeField(auto_now_add=True)
valid_begin_date = models.DateField(verbose_name="有效期开始自")
valid_end_date = models.DateField(verbose_name="有效期结束至")
status_choices = ((0, \'已开通\'), (1, \'已过期\'))
status = models.SmallIntegerField(choices=status_choices, default=0)
order_detail = models.OneToOneField("OrderDetail") # 使订单购买后支持 课程评价
# order = models.ForeignKey("Order",blank=True,null=True)
def __str__(self):
return "%s:%s" % (self.account, self.course)
class DegreeRegistrationForm(models.Model):
"""学位课程报名表"""
enrolled_degree = models.OneToOneField("EnrolledDegreeCourse")
current_company = models.CharField(max_length=64, )
current_position = models.CharField(max_length=64, )
current_salary = models.IntegerField()
work_experience_choices = (
(0, "应届生"),
(1, "1年"),
(2, "2年"),
(3, "3年"),
(4, "4年"),
(5, "5年"),
(6, "6年"),
(7, "7年"),
(8, "8年"),
(9, "9年"),
(10, "10年"),
(11, "超过10年"),
)
work_experience = models.IntegerField()
open_module = models.BooleanField("是否开通第1模块", default=True)
stu_specified_mentor = models.CharField("学员自行指定的导师名", max_length=32, blank=True, null=True)
study_plan_choices = ((0, "1-2小时/天"),
(1, "2-3小时/天"),
(2, "3-5小时/天"),
(3, "5小时+/天"),
)
study_plan = models.SmallIntegerField(choices=study_plan_choices, default=1)
why_take_this_course = models.TextField("报此课程原因", max_length=1024)
why_choose_us = models.TextField("为何选路飞", max_length=1024)
your_expectation = models.TextField("你的期待", max_length=1024)
memo = models.CharField(max_length=255, blank=True, null=True)
def __str__(self):
return "%s" % self.enrolled_degree
class EnrolledDegreeCourse(models.Model):
"""已报名的学位课程"""
account = models.ForeignKey("Account")
degree_course = models.ForeignKey("DegreeCourse")
enrolled_date = models.DateTimeField(auto_now_add=True)
valid_begin_date = models.DateField(verbose_name="有效期开始自", blank=True, null=True) # 开通第一个模块时,再添加课程有效期,2年
valid_end_date = models.DateField(verbose_name="有效期结束至", blank=True, null=True)
status_choices = (
(0, \'在学中\'),
(1, \'休学中\'),
(2, \'已毕业\'),
(3, \'超时结业\'),
(4, \'未开始\'),
# (3, \'其它\'),
)
study_status = models.SmallIntegerField(choices=status_choices, default=0)
mentor = models.ForeignKey("Account", verbose_name="导师", related_name=\'my_students\',
blank=True, null=True, limit_choices_to={\'role\': 1})
mentor_fee_balance = models.PositiveIntegerField("导师费用余额", help_text="这个学员的导师费用,每有惩罚,需在此字段同时扣除")
order_detail = models.OneToOneField("OrderDetail") # 使订单购买后支持填写报名表
def __str__(self):
return "%s:%s" % (self.account, self.degree_course)
class Meta:
unique_together = (\'account\', \'degree_course\')
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 = models.IntegerField(verbose_name="等值货币")
off_percent = models.PositiveSmallIntegerField("折扣百分比", help_text="只针对折扣券,例7.9折,写79", blank=True, null=True)
minimum_consume = models.PositiveIntegerField("最低消费", 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)
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)
account = models.ForeignKey("Account", 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 Order(models.Model):
"""订单"""
payment_type_choices = ((0, \'微信\'), (1, \'支付宝\'), (2, \'优惠码\'), (3, \'贝里\'))
payment_type = models.SmallIntegerField(choices=payment_type_choices)
payment_number = models.CharField(max_length=128, verbose_name="支付第3方订单号", null=True, blank=True)
order_number = models.CharField(max_length=128, verbose_name="订单号", unique=True) # 考虑到订单合并支付的问题
account = models.ForeignKey("Account")
actual_amount = models.FloatField(verbose_name="实付金额")
status_choices = ((0, \'交易成功\'), (1, \'待支付\'), (2, \'退费申请中\'), (3, \'已退费\'), (4, \'主动取消\'), (5, \'超时取消\'))
status = models.SmallIntegerField(choices=status_choices, verbose_name="状态")
date = models.DateTimeField(auto_now_add=True, verbose_name="订单生成时间")
pay_time = models.DateTimeField(blank=True, null=True, verbose_name="付款时间")
cancel_time = models.DateTimeField(blank=True, null=True, verbose_name="订单取消时间")
def __str__(self):
return "%s" % self.order_number
class OrderDetail(models.Model):
"""订单详情"""
order = models.ForeignKey("Order")
content_type = models.ForeignKey(ContentType) # 可关联普通课程或学位
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey(\'content_type\', \'object_id\')
original_price = models.FloatField("课程原价")
price = models.FloatField("折后价格")
content = models.CharField(max_length=255, blank=True, null=True) # ?
valid_period_display = models.CharField("有效期显示", max_length=32) # 在订单页显示
valid_period = models.PositiveIntegerField("有效期(days)") # 课程有效期
memo = models.CharField(max_length=255, blank=True, null=True)
# def __str__(self):
# return "%s - %s - %s" % (self.order, self.content_type, self.price)
class Meta:
# unique_together = ("order", \'course\')
unique_together = ("order", \'content_type\', \'object_id\')
class TransactionRecord(models.Model):
"""贝里交易纪录"""
account = models.ForeignKey("Account")
amount = models.IntegerField("金额")
balance = models.IntegerField("账户余额")
transaction_type_choices = ((0, \'收入\'), (1, \'支出\'), (2, \'退款\'), (3, "提现")) # 2 为了处理 订单过期未支付时,锁定期贝里的回退
transaction_type = models.SmallIntegerField(choices=transaction_type_choices)
content_type = models.ForeignKey(ContentType, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True, verbose_name="关联对象")
content_object = GenericForeignKey(\'content_type\', \'object_id\')
transaction_number = models.CharField(unique=True, verbose_name="流水号", max_length=128)
date = models.DateTimeField(auto_now_add=True)
memo = models.CharField(max_length=128, blank=True, null=True)
def __str__(self):
return "%s" % self.transaction_number
# ######################## 用户 ########################
class Account(models.Model):
username = models.CharField("用户名", max_length=64, unique=True)
password = models.CharField(\'password\', max_length=128)
class UserAuthToken(models.Model):
"""
用户Token表
"""
user = models.OneToOneField(to="Account")
token = models.CharField(max_length=40)
created = models.DateTimeField(auto_now_add=True)
整个支付流程
一、添加到购物车
当用户选择需要购买的课程和价格策略后,有两种方式购买课程:
一:直接购买,将课程id和选择的价格策略放到redis中,跳到去支付页面,从redis中获取购买课程的id和价格策略id,如果该用户要使用优惠券和贝利,则选择当前用户所拥有并且未使用和没过期的优惠券,得到折后价格,点击去支付完成支付,
二、添加到购物车中完成支付(post)
获取到所选课程的id和所选择的价格策略id传到后端,在后端获取到所选课程的id和所选择的价格策略id,根据课程id获取到当前课程,然后根据当前课程获取到当前课程所有的价格策略,判断在后端获取到的价格策略id在不在当前课程的价格策略中,不在的话则抛出异常价格策略不存在,在的话继续执行,将该课程所有价格策略的id,时间周期和价格存放到price_policy_list中
price_policy_objs = course_obj.price_policy.all()
for item in price_policy_objs:
if item.id == price_policy_id:
flag = True
price_policy_list.append({\'id\':item.id,
\'valid_period\':item.get_valid_period_display(),
\'price\':item.price})
继续将该课程的所有信息主要包括:课程id,课程图片地址,课程标题,所有价格策略,默认价格策略封装在course_dict中
course_dict = {
\'id\':course_obj.id,
\'img\':course_obj.course_img,
\'title\':course_obj.name,
\'price_policy_list\':price_policy_list,
\'default_policy_id\':price_policy_id
}
判断当前用户的购物车中是否有东西,没有的话则把当前课程放入到购物车中,如果购物车中已经有课程课程,则将该课程添加到购物车中,最后封装好放到redis中:
nothing = CONN.hget(settings.LUFFY_SHOPPING_CAR,request.user.id)
if not nothing:
data = {course_obj.id: course_dict}
else:
data = json.loads(nothing.decode(\'utf-8\'))
data[course_obj.id] = course_dict
CONN.hset(settings.LUFFY_SHOPPING_CAR,request.user.id, json.dumps(data))
封装在redis中的数据结构如下:
chopping_car = {
request.user.id:{
course.id:{
title:\'xx\',
img:\'xx\',
choice_policy_id:1,
price_policy_list:[
{id:1,price:\'9.9\', period:\'1个月\'},
{id:2,price:\'19.9\',period:\'3个月\'},
{id:3,price:\'59.9\',period:\'8个月\'},
],
}
},
course.id:[
title:\'xx\',
img:\'xx\',
choice_policy_id:1,
price_policy_list:[
{id:1,price:\'9.9\', period:\'1个月\'},
{id:2,price:\'19.9\',period:\'3个月\'},
{id:3,price:\'59.9\',period:\'8个月\'},
],
]
}
}
}
from rest_framework.views import APIView
from rest_framework.response import Response
from django.core.exceptions import ObjectDoesNotExist
from api import models
from api.utils.auth.api_view import AuthAPIView
from api.utils.exception import PricePolicyDoesNotExist
from pool import POOL
import json
from django.conf import settings
import redis
CONN = redis.Redis(connection_pool=POOL)
class ShoppingCarView(AuthAPIView,APIView):
def get(self,request,*args,**kwargs):
"""
查看购物车
:param request:
:param args:
:param kwargs:
:return:
"""
course = CONN.hget(settings.LUFFY_SHOPPING_CAR,request.user.id)
course_dict = json.loads(course.decode(\'utf-8\'))
return Response(course_dict)
def post(self,request,*args,**kwargs):
"""
获取课程ID和价格策略ID,放入redis
:param request:
:param args:
:param kwargs:
:return:
"""
ret = {\'code\':1000,\'msg\':None}
try:
course_id = request.data.get(\'course_id\')
price_policy_id = request.data.get(\'price_policy_id\')
# 1. 获取课程
course_obj = models.Course.objects.get(id=course_id)
# 2. 获取当前课程的所有价格策略: id, 有效期,价格
price_policy_list = []
flag = False
price_policy_objs = course_obj.price_policy.all()
for item in price_policy_objs:
if item.id == price_policy_id:
flag = True
price_policy_list.append({\'id\':item.id,
\'valid_period\':item.get_valid_period_display(),
\'price\':item.price})
if not flag:
raise PricePolicyDoesNotExist()
# 3. 课程和价格策略均没有问题,将课程和价格策略放到redis中
# 课程id,课程图片地址,课程标题,所有价格策略,默认价格策略
course_dict = {
\'id\':course_obj.id,
\'img\':course_obj.course_img,
\'title\':course_obj.name,
\'price_policy_list\':price_policy_list,
\'default_policy_id\':price_policy_id
}
# a. 获取当前用户购物车中的课程 car = {1: {,,,}, 2:{....}}
# b. car[course_obj.id] = course_dict
# c. conn.hset(\'luffy_shopping_car\',request.user.id,car)
nothing = CONN.hget(settings.LUFFY_SHOPPING_CAR,request.user.id)
if not nothing:
data = {course_obj.id: course_dict}
else:
data = json.loads(nothing.decode(\'utf-8\'))
data[course_obj.id] = course_dict
CONN.hset(settings.LUFFY_SHOPPING_CAR,request.user.id, json.dumps(data))
except ObjectDoesNotExist as e:
ret[\'code\'] = 1001
ret[\'msg\'] = "课程不存在"
except PricePolicyDoesNotExist as e:
ret[\'code\'] = 1002
ret[\'msg\'] = "价格策略不存在"
except Exception as e:
ret[\'code\'] = 1003
ret[\'msg\'] = "添加购物车异常"
print()
return Response(ret)
def patch(self,request,*args,**kwargs):
\'\'\'
修改价格策略,课程id不变,价格策略id变化
:param reques:
:param args:
:param kwargs:
:return:
\'\'\'
ret = {\'code\': 1000, \'msg\': None}
try:
#课程id
course_id = request.data.get(\'course_id\')
course_id=str(course_id)
#修改后的价格策略id
price_policy_id = request.data.get(\'price_policy_id\')
#当前用户的购物车
cart=CONN.hget(settings.LUFFY_SHOPPING_CAR,request.user.id)
print(cart)
if not cart:
raise Exception(\'访问的购物车不存在\')
cart=json.loads(cart.decode(\'utf-8\'))
#判断课程师傅在当前用户的购物车中
if course_id not in cart:
raise Exception(\'该课程在购物车中不存在\')
#当前课程所有的价格策略
currentCourse_price_police= cart[course_id][\'price_policy_list\']
policy_exit=False
#如果当前选择的价格策略在当前的课程策略中
for price_policy in currentCourse_price_police:
if price_policy[\'id\']==price_policy_id:
policy_exit=True
break
if not policy_exit:
raise PricePolicyDoesNotExist()
#将购物车中的当前课程的价格策略设置为当前的价格策略
cart[course_id][\'default_policy_id\']=price_policy_id
CONN.hset(settings.LUFFY_SHOPPING_CAR,request.user.id,json.dumps(cart))
print(cart)
except PricePolicyDoesNotExist as e:
ret[\'code\'] = 1001
ret[\'msg\'] = \'价格策略不存在\'
except Exception as e:
ret[\'code\']=1002
ret[\'msg\']=str(e)
return Response(ret)
def delete(self,request,*args,**kwargs):
ret = {\'code\': 1000, \'msg\': None}
try:
course_id=request.data.get(\'course_id\')
course_id=str(course_id)
#获取当前用户的购物车
cart=CONN.hget(settings.LUFFY_SHOPPING_CAR,request.user.id)
print(cart)
#如果没有购物车
if not cart:
raise Exception(\'购物车里面没有东西\')
cart=json.loads(cart.decode(\'utf-8\'))
#当前用户购物车中没有该课程
if course_id not in cart:
raise Exception(\'所删除的课程没在购物车中\')
#删除该课程
del cart[course_id]
#写入到redis中
CONN.hset(settings.LUFFY_SHOPPING_CAR,request.user.id,json.dumps(cart))
except Exception as e:
ret[\'code\']=1001
ret[\'msg\']=str(e)
return Response(ret)
修改购物车总的价格策略(patch):
向后台发课程id和要修改为的价格策略id,判断课程是否在购物车中,判断传递过来的价格策略是否在当前课程的价格策略中,在的话将 redis中的当前课程的默认价格策略修改为当前的价格策略具体将上面代码
二、去结算
前端向后端传递要去结算的课程的id列表如:[1,2]
1.获取用户提交的课程id, [1,2],
判断是否选择要结算的课程,没选择则抛出异常
course_id_list = request.data.get(\'course_list\')
if not course_id_list or not isinstance(course_id_list, list):
raise Exception(\'请选择要结算的课程\')
2.检测购物车中检查是否已经有课程(应该有课程的)
product_dict = redis_pool.conn.hget(settings.LUFFY_SHOPPING_CAR, request.user.id)
if not product_dict:
raise Exception(\'购物车无课程\')
3.检测购物车中是否有用户要购买的课程
product_dict = json.loads(product_dict.decode(\'utf-8\'))
# ###### 课程、价格和优惠券 #######
policy_course_dict = {}
# 循环用户传递过来的要结算的课程ID列表
for course_id in course_id_list:
course_id = str(course_id)
product = product_dict.get(course_id)
if not product:
raise Exception(\'购买的课程必须先加入购物车\')
如果所结算的课程在购物车中,4.获取选中价格策略的价格详细,
选择购物车中当前课程下的所有价格策略和当前课程的所选择的价格策略相等的价格策略,获取其信息,
# c. 购物车中是否有用户要购买的课程
product_dict = json.loads(product_dict.decode(\'utf-8\'))
# ###### 课程、价格和优惠券 #######
policy_course_dict = {}
# 循环用户传递过来的要结算的课程ID列表
for course_id in course_id_list:
course_id = str(course_id)
product = product_dict.get(course_id)
if not product:
raise Exception(\'购买的课程必须先加入购物车\')
# 获取选中价格策略的价格详细
policy_exist = False
for policy in product[\'price_policy_list\']:
if policy[\'id\'] == product[\'choice_policy_id\']:
policy_price = policy[\'price\']
policy_period = policy[\'period\']
policy_valid_period = policy[\'valid_period\']
policy_exist = True
break
if not policy_exist:
raise Exception(\'购物车中的课程无此价格\')
将上面我们获取的课程信息和价格策略信息封装在policy_course中
policy_course = {
\'course_id\': course_id,
\'course_name\': product[\'name\'],
\'course_img\': product[\'course_img\'],
\'policy_id\': product[\'choice_policy_id\'],
\'policy_price\': policy_price,
\'policy_period\': policy_period,
\'policy_valid_period\': policy_valid_period,
\'default_coupon_id\': 0,
\'coupon_record_list\': {
0:{\'id\': 0, \'text\': \'请选择优惠券\'},
},
}
5.获取当前用户所有的优惠券
user_coupon_list = models.CouponRecord.objects.filter(account=request.user,status=0)
6.区分用户的优惠券种类,课程优惠券添加到课程中;全局优惠券添加到全局
# ###### 全局优惠券 #######
global_coupon_record_dict = {}
循环遍历当前用户的所有优惠券,判断他们是否过期
begin_date = record.coupon.valid_begin_date
end_date = record.coupon.valid_end_date
if begin_date:
if current_date < begin_date:
continue
if end_date:
if current_date > end_date:
continue
,如果没过期,判断他们是全局优惠券还是针对某个课程的优惠券,区分好是什么优惠券以后还的区分该优惠券是什么类型,
如果是通用券
if record.coupon.coupon_type == 0:
temp = {\'type\': 0, \'text\': "通用优惠券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'money_equivalent_value\': record.coupon.money_equivalent_value}
如果是满减券:
elif record.coupon.coupon_type == 1:
temp = {\'type\': 1, \'text\': "满减券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'minimum_consume\': record.coupon.minimum_consume,
\'money_equivalent_value\': record.coupon.money_equivalent_value}
如果是折扣券:
elif record.coupon.coupon_type == 2:
temp = {\'type\': 2, \'text\': "折扣券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'off_percent\': record.coupon.off_percent}
如果是全局优惠券,则
global_coupon_record_dict[record.id] = temp
如果但是针对课程的优惠券:
policy_course_dict[cid][\'coupon_record_list\'][record.id] = temp
最后将所有数据封装在user_pay中放到redis上
user_pay = {
\'policy_course_dict\': policy_course_dict,
\'global_coupon_record_dict\': global_coupon_record_dict,
\'default_global_coupon_id\': 0,
}
redis_pool.conn.hset(settings.LUFFY_PAYMENT, request.user.id, json.dumps(user_pay))
user_pay数据结构
结算中心 = {
用户ID: {
policy_course_dict:{
1:{
\'course_id\': course_id,
\'course_name\': product[\'name\'],
\'course_img\': product[\'course_img\'],
\'policy_id\': product[\'choice_policy_id\'],
\'policy_price\': policy_price,
\'policy_\': policy_period, # 30/
\'default_coupon_id\': 1,
\'coupon_record_list\': {
0:{\'id\': 0, \'text\': \'请选择优惠券\'},
1:{\'id\': 1, \'type\':1, \'text\': \'优惠券1\', ..},
2:{\'id\': 2, \'type\':2, \'text\': \'优惠券1\', ..},
3: {\'id\': 3, \'type\':3, \'text\': \'优惠券1\', ..},
},
},
2:{
\'course_id\': course_id,
\'course_name\': product[\'name\'],
\'course_img\': product[\'course_img\'],
\'policy_id\': product[\'choice_policy_id\'],
\'policy_price\': policy_price,
\'policy_\': policy_period,
\'default_coupon_id\': 0,
\'coupon_record_list\': {
0:{\'id\': 0, \'text\': \'请选择优惠券\'},
1:{\'id\': 1, \'type\':1, \'text\': \'优惠券1\', ..},
2:{\'id\': 2, \'type\':2, \'text\': \'优惠券1\', ..},
3: {\'id\': 3, \'type\':3, \'text\': \'优惠券1\', ..},
},
}
},
global_coupon_dict:{
1:{\'type\': 0, \'text\': "通用优惠券", \'id\': 1, ..},
2:{\'type\': 0, \'text\': "通用优惠券", \'id\': 2, ..},
3:{\'type\': 0, \'text\': "通用优惠券", \'id\': 3, ...},
4:{\'type\': 0, \'text\': "通用优惠券", \'id\': 4, ...},
},
choice_global_coupon:3
}
}
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import json
import datetime
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from api.utils import redis_pool
from api.utils.auth.api_view import AuthAPIView
from api import models
class PaymentView(AuthAPIView,APIView):
"""
去结算接口
"""
def get(self, request, *args, **kwargs):
"""
获取结算列表
:param request:
:param args:
:param kwargs:
:return:
"""
response = {\'code\': 1000}
try:
# 结算商品列表
payment_list = redis_pool.conn.hget(settings.LUFFY_PAYMENT, request.user.id)
if not payment_list:
raise Exception()
response[\'data\'] = {
\'payment_list\': json.loads(payment_list.decode(\'utf-8\')), # 结算信息(课程、价格和优惠券)
"balance": request.user.balance # 个人贝里账户,可使用贝里金额
}
except Exception as e:
response[\'code\'] = 1001
response[\'msg\'] = "结算列表为空"
return Response(response)
def post(self, request, *args, **kwargs):
"""
去结算
方案一(示例):用户提交课程id,去redis购物车中获取其选好的价格策略,再次检测课程和价格策略的合法性。
PS: 直接购买时,需要先加入购物车,再立即去结算
方案二:用户提交课程id和价格策略id,去数据库验证其合法性。
PS: 直接购买时,直接去结算
user.id: {
policy_course_dict:{
课程ID:{
\'course_id\': course_id,
\'course_name\': product[\'name\'],
\'course_img\': product[\'course_img\'],
\'policy_id\': product[\'choice_policy_id\'],
\'policy_price\': policy_price,
\'policy_\': policy_period,
\'coupon_record_list\': {
0:{\'id\': 0, \'text\': \'请选择优惠券\'},
1:{\'id\': 1, \'type\':1, \'text\': \'优惠券1\', ..},
2:{\'id\': 2, \'type\':2, \'text\': \'优惠券1\', ..},
3: {\'id\': 3, \'type\':3, \'text\': \'优惠券1\', ..},
},
},
课程ID:{
\'course_id\': course_id,
\'course_name\': product[\'name\'],
\'course_img\': product[\'course_img\'],
\'policy_id\': product[\'choice_policy_id\'],
\'policy_price\': policy_price,
\'policy_\': policy_period,
\'coupon_record_list\': {
0:{\'id\': 0, \'text\': \'请选择优惠券\'},
1:{\'id\': 1, \'type\':1, \'text\': \'优惠券1\', ..},
2:{\'id\': 2, \'type\':2, \'text\': \'优惠券1\', ..},
3: {\'id\': 3, \'type\':3, \'text\': \'优惠券1\', ..},
},
}
},
global_coupon_dict:{
1:{\'type\': 0, \'text\': "通用优惠券", \'id\': 1, ..},
2:{\'type\': 0, \'text\': "通用优惠券", \'id\': 2, ..},
3:{\'type\': 0, \'text\': "通用优惠券", \'id\': 3, ...},
4:{\'type\': 0, \'text\': "通用优惠券", \'id\': 4, ...},
},
choice_global_coupon:0
}
:param request:
:param args:
:param kwargs:
:return:
"""
response = {\'code\': 1001}
try:
# a. 获取用户提交的课程id, [1,2]
course_id_list = request.data.get(\'course_list\')
if not course_id_list or not isinstance(course_id_list, list):
raise Exception(\'请选择要结算的课程\')
# b. 购物车中检查是否已经有课程(应该有课程的)
product_dict = redis_pool.conn.hget(settings.LUFFY_SHOPPING_CAR, request.user.id)
if not product_dict:
raise Exception(\'购物车无课程\')
# c. 购物车中是否有用户要购买的课程
product_dict = json.loads(product_dict.decode(\'utf-8\'))
# ###### 课程、价格和优惠券 #######
policy_course_dict = {}
# 循环用户传递过来的要结算的课程ID列表
for course_id in course_id_list:
course_id = str(course_id)
product = product_dict.get(course_id)
if not product:
raise Exception(\'购买的课程必须先加入购物车\')
# 获取选中价格策略的价格详细
policy_exist = False
for policy in product[\'price_policy_list\']:
if policy[\'id\'] == product[\'choice_policy_id\']:
policy_price = policy[\'price\']
policy_period = policy[\'period\']
policy_valid_period = policy[\'valid_period\']
policy_exist = True
break
if not policy_exist:
raise Exception(\'购物车中的课程无此价格\')
policy_course = {
\'course_id\': course_id,
\'course_name\': product[\'name\'],
\'course_img\': product[\'course_img\'],
\'policy_id\': product[\'choice_policy_id\'],
\'policy_price\': policy_price,
\'policy_period\': policy_period,
\'policy_valid_period\': policy_valid_period,
\'default_coupon_id\': 0,
\'coupon_record_list\': {
0:{\'id\': 0, \'text\': \'请选择优惠券\'},
},
}
policy_course_dict[course_id] = policy_course
# 获取当前所有优惠券
user_coupon_list = models.CouponRecord.objects.filter(account=request.user,status=0)
# ###### 全局优惠券 #######
global_coupon_record_dict = {}
# 课程优惠券添加到课程中;全局优惠券添加到全局
current_date = datetime.datetime.now().date()
for record in user_coupon_list:
# 检查优惠券是否已经过期
begin_date = record.coupon.valid_begin_date
end_date = record.coupon.valid_end_date
if begin_date:
if current_date < begin_date:
continue
if end_date:
if current_date > end_date:
continue
# 全局优惠券
if not record.coupon.content_type:
if record.coupon.coupon_type == 0:
temp = {\'type\': 0, \'text\': "通用优惠券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'money_equivalent_value\': record.coupon.money_equivalent_value}
elif record.coupon.coupon_type == 1:
temp = {\'type\': 1, \'text\': "满减券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'minimum_consume\': record.coupon.minimum_consume,
\'money_equivalent_value\': record.coupon.money_equivalent_value}
elif record.coupon.coupon_type == 2:
temp = {\'type\': 2, \'text\': "折扣券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'off_percent\': record.coupon.off_percent}
else:
continue
global_coupon_record_dict[record.id] = temp
# 课程优惠券
else:
cid = record.coupon.object_id
if record.coupon.content_type.model == \'course\' and cid in policy_course_dict:
# 课程价格:满减,打折,通用
if record.coupon.coupon_type == 0:
temp = {\'type\': 0, \'text\': "通用优惠券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'money_equivalent_value\': record.coupon.money_equivalent_value}
elif record.coupon.coupon_type == 1 and policy_course_dict[cid][\'policy_price\'] >= record.coupon.minimum_consume:
temp = {\'type\': 1, \'text\': "满减券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'minimum_consume\': record.coupon.minimum_consume,
\'money_equivalent_value\': record.coupon.money_equivalent_value}
elif record.coupon.coupon_type == 2:
temp = {\'type\': 2, \'text\': "折扣券", \'id\': record.id,
\'begin_date\': begin_date, \'end_date\': end_date,
\'off_percent\': record.coupon.off_percent}
else:
continue
policy_course_dict[cid][\'coupon_record_list\'][record.id] = temp
user_pay = {
\'policy_course_dict\': policy_course_dict,
\'global_coupon_record_dict\': global_coupon_record_dict,
\'default_global_coupon_id\': 0,
}
redis_pool.conn.hset(settings.LUFFY_PAYMENT, request.user.id, json.dumps(user_pay))
except Exception as e:
response[\'code\'] = 1002
response[\'msg\'] = str(e)
return Response(response)
def patch(self,request, *args, **kwargs):
"""
选择优惠券
1. 获取用户提交数据
{
course_id: \'课程id\',
coupon_id: \'优惠券ID\'
}
{
course_id: 0\',
coupon_id: \'全局优惠券ID\'
}
:param request:
:param args:
:param kwargs:
:return:
"""
return Response(\'...\')
三.去支付
1.去结算中心获取要结算的所有课程和优惠券
2.循环遍历每一个课程
开始总价格totalprice和折扣价totaldiscount都为0,
总价 = 0
总折扣 = 0
2.1.如果该课程没有使用优惠券,则总价格=totalprice+课程原价,totaldiscount=0,
b. 循环购买的所有课程
当前时间 = datetime.datetime.now()
当前日期 = datetime.datetime.now().date
****课程信息 = [] *****
****使用的优惠券ID列表 = [] *****
for course_id,values in policy_course_dict.items():
课程原价 = values[\'policy_price\']
使用的优惠券ID = values[\'default_coupon_id\']
discount = 0
# 未使用优惠券
temp = {
课程ID: 1,
原价: 10,
折扣价:10,
有效期:30
}
课程信息.append(temp)
总价 += 课程原价
折扣 += discount
2.3.如果使用了优惠券,则需要去判断所使用的优惠券是否已经过期,或是否已经被使用,如果过期了或者被使用了就抛出异常,否则继续往下执行,判断该优惠券的类型
如果使用了优惠券:
去数据库查询:指定优惠券是否已经使用、是否已经过期
如果优惠券可不用:
raise Exception(\'优惠券不可用\')
如果是通用优惠券:
discount = 通用优惠券(如果大于课程原价,课程原价)
elif 如果是满减优惠券:
if 价格是否大于最小满减要求:
discount = 通用优惠券(如果大于课程原价,课程原价)
elif 如果是折扣优惠券:
discount = 课程原价 * (1-折扣)
使用的优惠券ID列表.append(绑定可以的优惠券ID)
temp = {
课程ID: 1,
原价: 10,
折扣价:9,
有效期:30
}
课程信息.append(temp)
总价 += 课程原价
折扣 += discount
到此为止
pay = 总价 - 总折扣
3.继续计算看是否使用全局优惠券
全站优惠券ID = choice_global_coupon
数据库获取并检查是否可用(优惠券是否已经使用、是否已经过期)
如果优惠券可不用:
raise(\'全站优惠券不可用\')
g_discount = 0
如果是通用优惠券:
g_discount = 通用优惠券(如果大于pay,pay)
elif 如果是满减优惠券:
if 价格是否大于最小满减要求:
g_discount = 通用优惠券(如果大于pay,pay)
elif 如果是折扣优惠券:
g_discount = pay * (1- 折扣)
总折扣 += g_discount
使用的优惠券ID列表.append(全站优惠券ID)
4.贝利支付
if balance <= request.user.balance:
总折扣 += balance
5.总结算
总价 - 总折扣 = alipay
if alipay ==0:
贝里&优惠券
pay_type = 0
else:
支付宝支付
pay_type = 1
如果最后支付=0,就直接修改支付状态为已支付,否则改为待支付
6.点击立即支付以后 进行数据库操作
事务:
# 1. 创建订单表
order_obj = models.Order.objects.create(....status=0) # pay_type = 0
或
order_obj = models.Order.objects.create(....status=1) # pay_type = 1
# 2. 生成订单详细
for item in 课程信息:
detail_obj = models.OrderDetail.objects.create(order_obj,课程ID,原价和折扣价)
models.EnrolledCourse.objects.create(...当前时间,当前时间+30,status=1)
# 3. 处理优惠券
models.CouponRecord.objects.filter(account=request.user,status=0,id__in=使用的优惠券ID列表]).update(status=1,order=order_obj)
# 4. 处理贝里交易
models.Account.objects.filter(id=reuqest.user.id).update(balance=F(balance)-balance)
models.TransactionRecord.objects.create(amount=balance,balance=request.user.balance,transaction_type=1,content_object=order_obj)
点击立即支付要生成订单,订单根据前面支付是否等于0来判断支付状态是否要修改为已支付或者待支付;生成订单详情,循环课程信息,写入课程的原价,折后价,视频的有效期,即订单的有效期,对优惠券进行处理,把该用户已使用的优惠券状态改为已使用;:修改贝里,该账户的贝里减去使用的贝里,并更新贝里消费记录表,显示账户的消费金额,账户的余额等。
最后
if pay_type==1:
生成支付宝链接(自动生成自己的订单号),并返回给前端Vue
# ##################################### 支付宝的回调 ######################################
def callback(request,*args,**kwargs):
models.Order.objects.filter(订单号).update(status=0)