1.未封装的支付接口的使用
支付接口demo:
要使用Crypto模块,需要安装pycryptodome库,安装方法如下:
pip3 install pycryptodome
如有site-packages中存在crypto、pycrypto,在pip之前,需要pip3 uninstall crypto、pip3 uninstall pycrypto,否则无法安装成功。
from django.contrib import admin from django.urls import path from app01 import views urlpatterns = [ path('admin/', admin.site.urls), path('page1/', views.page1), path('page2/', views.page2), ]
from django.shortcuts import render, redirect, HttpResponse from utils.pay import AliPay import json import time def ali(): # 沙箱环境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info app_id = "2016091100486897" # POST请求,用于最后的检测 notify_url = "http://47.94.172.250:8804/page2/" # notify_url = "http://www.wupeiqi.com:8804/page2/" # GET请求,用于页面的跳转展示 return_url = "http://47.94.172.250:8804/page2/" # return_url = "http://www.wupeiqi.com:8804/page2/" merchant_private_key_path = "keys/app_private_2048.txt" alipay_public_key_path = "keys/alipay_public_2048.txt" alipay = AliPay( appid=app_id, app_notify_url=notify_url, return_url=return_url, app_private_key_path=merchant_private_key_path, alipay_public_key_path=alipay_public_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥 debug=True, # 默认False, ) return alipay def page1(request): if request.method == "GET": return render(request, 'page1.html') else: money = float(request.POST.get('money')) alipay = ali() # 生成支付的url query_params = alipay.direct_pay( subject="路飞学城", # 商品简单描述 out_trade_no="x345" + str(time.time()), # 商户订单号 total_amount=money, # 交易金额(单位: 元 保留俩位小数) ) pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params) return redirect(pay_url) def page2(request): alipay = ali() if request.method == "POST": # 检测是否支付成功 # 去请求体中获取所有返回的数据:状态/订单号 from urllib.parse import parse_qs body_str = request.body.decode('utf-8') post_data = parse_qs(body_str) post_dict = {} for k, v in post_data.items(): post_dict[k] = v[0] print(post_dict) sign = post_dict.pop('sign', None) status = alipay.verify(post_dict, sign) print('POST验证', status) if status: # 修改订单状态 pass return HttpResponse('POST返回') else: params = request.GET.dict() sign = params.pop('sign', None) status = alipay.verify(params, sign) print('GET验证', status) if status: # 获取订单状态,显示给用户 return HttpResponse('支付成功')
from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from base64 import decodebytes, encodebytes import json class AliPay(object): """ 支付宝支付接口(PC端支付接口) """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.importKey(fp.read()) if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): data.pop("sign", None) # 排序后的字符串 unsigned_items = self.ordered_data(data) unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) sign = self.sign(unsigned_string.encode("utf-8")) # ordered_items = self.ordered_data(data) quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 获得最终的订单信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def ordered_data(self, data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 将字典类型的数据dump出来 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 开始计算签名 key = self.app_private_key signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new(unsigned_string)) # base64 编码,转换为unicode表示并移除回车 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 开始计算签名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序后的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="dist/css/bootstrap.css"> </head> <body> <form method="POST"> {% csrf_token %} <input type="text" name="money"> <input type="submit" value="去支付" /> </form> <script></script> </body> </html>
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCK5urihEF0Yje6KSylvVcdBGANbyPWInuTX7uJX3Uz7nDUEuKROF4W6c6QLSckt0LPeZTA6llB5pW0TrW8UPqVQCtcSkUpbYZ7srTV9uy4y3oDoQwz5AWUL97iYRCubewiqsrATTGxRyDTp3OvBncIKt8+wm++3/IO7fHX/dNttobkXO0FP0tkVc+zaslmpN7HsEMFYvbfHQzmrGQ2gobPONYJOueUEw8jWpQcTnKq3SgfUhjX7kgoKenoZf+2Ao6/rmyYQqnxtqzstgdJzuD/Wb2dFIYjIP/fWplDVZEEcan/VlHcuAfXv3s9iXVLMmTTyvqU77s+2gdyYwNLXhxFAgMBAAECggEAQ0Tf/kGk3XNvn6WvLLlMxg3HYtovVdYvWMklLrtfLH5OgDaBKWlOD/S9iA+GBH8ISSiNhPw5q/O7Dq6Lzx68rKl+Fl0Vr6GOXrvGXlUOgdRxS+6j1UGZ/hFM9P+jL4amtIdYV9dKuJtE55wSJ3KPFRKGOYO60IruVJKh7EPOMDRbSSfWQbU0S6AoChpDyGbYdQn/oWc6sdrZHLZtqXgjgp9AdbWVDEZvY7g7kBxjaIpaAs5CWxVDc1O29E1I4Mg7hssxe401PgFtofIgV19v6siPvSs/wI/sZhJjJddG0ih17CWWdTzkrgtEfh+sVowtkd+KtP7QMMaFrnrkJAlGpQKBgQDfbhVS9IiBlHrWtyvHQsPLJrQ9IfIO+ZjEOGSRiMWxK/+4DoJkQU6Yj6v9O3g0oQg0VnPCoTEtKhYKpgP/tue02hAPyRxdvO4mU545s7YeVNDgrk2L5GPr8QTJnfLjTvYoy2hZdoga+moBW8u/YmO3k0QRLB82jActqK6vC+PQYwKBgQCfJm972sIc168AKWuOft9RyOPqwozFuJww8Etu9EAW1+UFxjZPFyY5JYfUu2VZck9WjIazYQO5pEm3jzMOP4qj9fhf3kBRz2g3EwyzxZLfxAbWnH+OPNZx8BIxoJtqgFOeZWanXyaoiCCZvQ54NmFBXysDeUXJCxLfjg+1nmR9NwKBgQDVFUGU+c1t91Mnj01bHdto1aKzYrpdecEt8bJH8a7Ih3O772p/fqEccnjOa3b6ilEuyPxhtCUYM7kNssLBj4hvPEBxLZW1+EcPmlOeKDwZtT336YPfVJPPIu8z8UUBb/7nbQY5vAeV4xhR71/jSExdeT9DOVcTSHxYGTVvj+FWjQKBgGazE8/127tnB1vwXqLmhk+tdj5A6zyQI+KEvfjMjyruiLDQNq2U/6py6JNDlmRBGqd8KVRJ73B1bsiQFN9F675gdLXQouroD5Uyqsi7X0scoVkORlXQNoXx6Juzy3bPdqZJQxQQl867gWYUFOlIFjxsIEKumHTiu3wdnU+S9b/DAoGARB/tRjp3iyOzKWLXvS3Dv4JZ/ZxuO/hnM8s+oJu5qODuYm9wD/rdw3L2bc+GsDK58xOB8+Avt/cYvakyno9dH9y8bA2wCCe9Ho1y1oBWQ0AJ9UxrFqHVB6iIYzLxxNcVrdoUE7XrEvKJE9Zm3Ss0x+E7VYU2Oz93zHda6oINijI=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB -----END PUBLIC KEY-----
支付接口 示例:
from django.contrib import admin from django.urls import path, re_path from api.views.course import ( CourseView, CourseDetailView ) from api.views.login import LoginView from api.views.shoppingcart import ShoppingCartView from api.views.account import AccountView from api.views.payment import PaymentView urlpatterns = [ path('admin/', admin.site.urls), path('login/', LoginView.as_view()), path('course/', CourseView.as_view({'get': 'list'})), re_path('course/detail/$', CourseDetailView.as_view({'get': 'list'})), re_path('course/detail/(?P<pk>\d+)/$', CourseDetailView.as_view({'get': 'retrieve'})), # 购物车 path('shoppingcart/', ShoppingCartView.as_view()), # 结算 path('account/', AccountView.as_view()), # 支付 path('payment/', PaymentView.as_view()), ]
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.auth.models import AbstractUser from django.utils.safestring import mark_safe # 课程表 ######################################## class Course(models.Model): """ 专题课程 """ name = models.CharField(max_length=128, unique=True, verbose_name="模块") course_img = models.CharField(max_length=255) course_type_choices = ((0, '付费'), (1, 'VIP专享'), (2, '学位课程')) course_type = models.SmallIntegerField(choices=course_type_choices) 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) order_details = GenericRelation("OrderDetail", related_query_name="course") coupon = GenericRelation("Coupon") price_policy = GenericRelation("PricePolicy") # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除,如有疑问请联系老村长 def __str__(self): return "%s(%s)" % (self.name, self.get_course_type_display()) class CourseDetail(models.Model): """ 课程详情页内容 """ course = models.OneToOneField("Course", on_delete=models.CASCADE) hours = models.IntegerField("课时") course_slogan = models.CharField(max_length=125, blank=True, null=True) video_brief_link = models.CharField(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 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 PricePolicy(models.Model): """ 价格与有课程效期表 """ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 关联course or degree_course object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') # course = models.ForeignKey("Course") valid_period_choices = ((1, '1天'), (3, '3天'), (7, '1周'), (14, '2周'), (30, '1个月'), (60, '2个月'), (90, '3个月'), (120, '4个月'), (180, '6个月'), (210, '12个月'), (540, '18个月'), (720, '24个月'), (722, '24个月'), (723, '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 CourseChapter(models.Model): """ 课程章节 """ course = models.ForeignKey("Course", related_name='coursechapters', on_delete=models.CASCADE) chapter = models.SmallIntegerField(verbose_name="第几章", default=1) name = models.CharField(max_length=128) summary = models.TextField(verbose_name="章节介绍", blank=True, null=True) is_create = models.BooleanField(verbose_name="是否创建题库进度", default=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', on_delete=models.CASCADE) 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) 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) is_flash = models.BooleanField(verbose_name="是否使用FLASH播放", default=False) player_choices = ((0, "CC"), (1, "POLYV"), (2, "ALI")) player = models.SmallIntegerField(choices=player_choices, default=1, help_text="视频播放器选择") def course_chapter(self): return self.chapter.chapter def course_name(self): return self.chapter.course.name class Meta: unique_together = ('chapter', 'section_link') def __str__(self): return "%s-%s" % (self.chapter, self.name) class OftenAskedQuestion(models.Model): """常见问题""" content_type = models.ForeignKey(ContentType, limit_choices_to={'model__contains': 'course'}, on_delete=models.CASCADE) # 关联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') verbose_name_plural = "常见问题" # 优惠券 ######################################## 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.FloatField(verbose_name="等值货币", blank=True, null=True) 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, on_delete=models.CASCADE) 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="自券被领时开始算起") status_choices = ((0, "上线"), (1, "下线")) status = models.SmallIntegerField(choices=status_choices, default=0) date = models.DateTimeField(auto_now_add=True) def __str__(self): return "%s(%s)" % (self.get_coupon_type_display(), self.name) class CouponRecord(models.Model): """ 优惠券发放、消费纪录 """ coupon = models.ForeignKey("Coupon", on_delete=models.CASCADE) account = models.ForeignKey("UserInfo", blank=True, null=True, verbose_name="使用者", on_delete=models.CASCADE) status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'), (3, '未领取')) status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(blank=True, null=True, verbose_name="领取时间", help_text="用户领取时间") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间") order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单", on_delete=models.CASCADE) # 一个订单可以有多个优惠券 date = models.DateTimeField(auto_now_add=True, verbose_name="生成时间") # _coupon = GenericRelation("Coupon") def __str__(self): return self.coupon.name + "优惠券记录" # 订单表 ######################################## class Order(models.Model): """ 订单 """ payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里'), (4, '银联')) 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("UserInfo", on_delete=models.CASCADE) actual_amount = models.FloatField(verbose_name="实付金额") # coupon = models.OneToOneField("Coupon", blank=True, null=True, verbose_name="优惠码") #一个订单可以有多个优惠券 status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')) status = models.SmallIntegerField(choices=status_choices, verbose_name="状态") order_type_choices = ((0, '用户下单'), (1, '线下班创建'),) order_type = models.SmallIntegerField(choices=order_type_choices, default=0, 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", on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 可关联普通课程或学位 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 UserInfo(AbstractUser): username = models.CharField("用户名", max_length=64, unique=True) email = models.EmailField( verbose_name='email address', max_length=255, unique=True, blank=True, null=True ) uid = models.CharField(max_length=64, unique=True) # 与第3方交互用户信息时,用这个uid,以避免泄露敏感用户信息 mobile = models.BigIntegerField(verbose_name="手机", unique=True, help_text="用于手机验证码登录", null=True) qq = models.CharField(verbose_name="QQ", max_length=64, blank=True, null=True, db_index=True) weixin = models.CharField(max_length=128, blank=True, null=True, db_index=True, verbose_name="微信") signature = models.CharField('个人签名', blank=True, null=True, max_length=255) brief = models.TextField("个人介绍", blank=True, null=True) openid = models.CharField(max_length=128, blank=True, null=True) alipay_card = models.CharField(max_length=128, blank=True, null=True, verbose_name="支付宝账户") gender_choices = ((0, '保密'), (1, '男'), (2, '女')) gender = models.SmallIntegerField(choices=gender_choices, default=0, verbose_name="性别") id_card = models.CharField(max_length=32, blank=True, null=True, verbose_name="身份证号或护照号") password = models.CharField('password', max_length=128, help_text=mark_safe('''<a class='btn-link' href='password'>重置密码</a>''')) is_active = models.BooleanField(default=True, verbose_name="账户状态") is_staff = models.BooleanField(verbose_name='staff status', default=False, help_text='决定着用户是否可登录管理后台') name = models.CharField(max_length=32, default="", verbose_name="真实姓名") head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png', verbose_name="个人头像") role_choices = ((0, '学员'), (1, '导师'), (2, '讲师'), (3, '管理员'), (4, '班主任'), (5, '线下班主任')) role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="角色") # balance = models.PositiveIntegerField(default=0, verbose_name="可提现余额") # #此处通过transaction_record表就可以查到,所以不用写在这了 memo = models.TextField('备注', blank=True, null=True, default=None, help_text="json格式存储") date_joined = models.DateTimeField(auto_now_add=True, verbose_name="注册时间") beli = models.IntegerField(default=1000) class Meta: verbose_name = '用户信息' verbose_name_plural = "用户信息" def __str__(self): return "%s(%s)" % (self.username, self.get_role_display()) class Token(models.Model): """ The default authorization token model. """ key = models.CharField(max_length=40) user = models.OneToOneField(UserInfo, related_name='auth_token', on_delete=models.CASCADE, verbose_name="关联用户") created = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) def __str__(self): return self.key
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCK5urihEF0Yje6KSylvVcdBGANbyPWInuTX7uJX3Uz7nDUEuKROF4W6c6QLSckt0LPeZTA6llB5pW0TrW8UPqVQCtcSkUpbYZ7srTV9uy4y3oDoQwz5AWUL97iYRCubewiqsrATTGxRyDTp3OvBncIKt8+wm++3/IO7fHX/dNttobkXO0FP0tkVc+zaslmpN7HsEMFYvbfHQzmrGQ2gobPONYJOueUEw8jWpQcTnKq3SgfUhjX7kgoKenoZf+2Ao6/rmyYQqnxtqzstgdJzuD/Wb2dFIYjIP/fWplDVZEEcan/VlHcuAfXv3s9iXVLMmTTyvqU77s+2gdyYwNLXhxFAgMBAAECggEAQ0Tf/kGk3XNvn6WvLLlMxg3HYtovVdYvWMklLrtfLH5OgDaBKWlOD/S9iA+GBH8ISSiNhPw5q/O7Dq6Lzx68rKl+Fl0Vr6GOXrvGXlUOgdRxS+6j1UGZ/hFM9P+jL4amtIdYV9dKuJtE55wSJ3KPFRKGOYO60IruVJKh7EPOMDRbSSfWQbU0S6AoChpDyGbYdQn/oWc6sdrZHLZtqXgjgp9AdbWVDEZvY7g7kBxjaIpaAs5CWxVDc1O29E1I4Mg7hssxe401PgFtofIgV19v6siPvSs/wI/sZhJjJddG0ih17CWWdTzkrgtEfh+sVowtkd+KtP7QMMaFrnrkJAlGpQKBgQDfbhVS9IiBlHrWtyvHQsPLJrQ9IfIO+ZjEOGSRiMWxK/+4DoJkQU6Yj6v9O3g0oQg0VnPCoTEtKhYKpgP/tue02hAPyRxdvO4mU545s7YeVNDgrk2L5GPr8QTJnfLjTvYoy2hZdoga+moBW8u/YmO3k0QRLB82jActqK6vC+PQYwKBgQCfJm972sIc168AKWuOft9RyOPqwozFuJww8Etu9EAW1+UFxjZPFyY5JYfUu2VZck9WjIazYQO5pEm3jzMOP4qj9fhf3kBRz2g3EwyzxZLfxAbWnH+OPNZx8BIxoJtqgFOeZWanXyaoiCCZvQ54NmFBXysDeUXJCxLfjg+1nmR9NwKBgQDVFUGU+c1t91Mnj01bHdto1aKzYrpdecEt8bJH8a7Ih3O772p/fqEccnjOa3b6ilEuyPxhtCUYM7kNssLBj4hvPEBxLZW1+EcPmlOeKDwZtT336YPfVJPPIu8z8UUBb/7nbQY5vAeV4xhR71/jSExdeT9DOVcTSHxYGTVvj+FWjQKBgGazE8/127tnB1vwXqLmhk+tdj5A6zyQI+KEvfjMjyruiLDQNq2U/6py6JNDlmRBGqd8KVRJ73B1bsiQFN9F675gdLXQouroD5Uyqsi7X0scoVkORlXQNoXx6Juzy3bPdqZJQxQQl867gWYUFOlIFjxsIEKumHTiu3wdnU+S9b/DAoGARB/tRjp3iyOzKWLXvS3Dv4JZ/ZxuO/hnM8s+oJu5qODuYm9wD/rdw3L2bc+GsDK58xOB8+Avt/cYvakyno9dH9y8bA2wCCe9Ho1y1oBWQ0AJ9UxrFqHVB6iIYzLxxNcVrdoUE7XrEvKJE9Zm3Ss0x+E7VYU2Oz93zHda6oINijI=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB -----END PUBLIC KEY-----
from rest_framework import serializers from api.models import * class CourseModelSerializer(serializers.ModelSerializer): class Meta: model = Course fields = '__all__' level = serializers.CharField(source='get_level_display') coursedetail_id = serializers.CharField(source='coursedetail.pk') class CourseDetailModelSerializer(serializers.ModelSerializer): class Meta: model = CourseDetail fields = '__all__' name = serializers.CharField(source="course.name") prices = serializers.SerializerMethodField() brief = serializers.CharField(source='course.brief') study_all_time = serializers.StringRelatedField(source='hours') level = serializers.CharField(source='course.get_level_display') teachers = serializers.SerializerMethodField() is_online = serializers.CharField(source="course.get_status_display") recommend_coursesinfo = serializers.SerializerMethodField() def get_prices(self, instance): return [{"price": obj.price, "valid_period": obj.valid_period, "valid_period_text": obj.get_valid_period_display() } for obj in instance.course.price_policy.all() ] def get_teachers(self, instance): return [{"name": obj.name, "image": obj.image } for obj in instance.teachers.all() ] def get_recommend_coursesinfo(self, instance): return [{"name": obj.name, "pk": obj.pk } for obj in instance.recommend_courses.all() ]
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import APIException from django.core.cache import cache import datetime import pytz from api.models import Token class LoginAuth(BaseAuthentication): def authenticate(self, request): token = request.META.get("HTTP_AUTHORIZATION") # 校验是否存在token字符串 # 缓存校验 user = cache.get(token) if user: # print(user, token) return user, token # 数据库校验 token_obj = Token.objects.filter(key=token).first() if not token_obj: raise APIException('认证失败') # 校验是否在有效期内 token_created_time = token_obj.created now = datetime.datetime.now() now = now.replace(tzinfo=pytz.timezone('UTC')) delta = now - token_created_time state = delta < datetime.timedelta(weeks=2) if state: # 校验成功,写入缓存中 delta = datetime.timedelta(weeks=2) - delta delta = delta.total_seconds() # 转化为时间戳格式 cache.set(token_obj.key, token_obj.user, min(delta, 3600*24*7)) # 通过min控制缓存时间不超过7天 return token_obj.user, token_obj.key else: raise APIException('认证超时')
class BaseResponse(object): def __init__(self): self.code = 1000 self.data = None self.msg = "" @property def dict(self): return self.__dict__ if __name__ == '__main__': res = BaseResponse() res.data = 123 print(res.dict)
class CommonException(Exception): def __init__(self, code, msg): self.code = code self.msg = msg
from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from base64 import decodebytes, encodebytes import json class AliPay(object): """ 支付宝支付接口(PC端支付接口) """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.importKey(fp.read()) if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): data.pop("sign", None) # 排序后的字符串 unsigned_items = self.ordered_data(data) unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) sign = self.sign(unsigned_string.encode("utf-8")) # ordered_items = self.ordered_data(data) quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 获得最终的订单信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def ordered_data(self, data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 将字典类型的数据dump出来 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 开始计算签名 key = self.app_private_key signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new(unsigned_string)) # base64 编码,转换为unicode表示并移除回车 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 开始计算签名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序后的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature)
from rest_framework.views import APIView from rest_framework.response import Response from django.contrib import auth import uuid import datetime from api.models import Token class LoginView(APIView): def post(self, request): res = {'user': None, 'msg': None} try: user = request.data.get('user') pwd = request.data.get('pwd') user_obj = auth.authenticate(username=user, password=pwd) if user_obj: random_str = str(uuid.uuid4()) Token.objects.update_or_create(user=user_obj, defaults={"key": random_str, 'created': datetime.datetime.now()}) res['user'] = user_obj.username res['token'] = random_str else: res['msg'] = 'user or pwd error' except Exception as e: res['msg'] = str(e) return Response(res)
from rest_framework.viewsets import ModelViewSet from api.models import * from api.utils.serializer import ( CourseModelSerializer, CourseDetailModelSerializer ) from api.utils.auth import LoginAuth class CourseView(ModelViewSet): authentication_classes = [LoginAuth] queryset = Course.objects.all() serializer_class = CourseModelSerializer class CourseDetailView(ModelViewSet): authentication_classes = [LoginAuth] queryset = CourseDetail.objects.all() serializer_class = CourseDetailModelSerializer
from rest_framework.views import APIView from rest_framework.response import Response from django.conf import settings from django.core.exceptions import ObjectDoesNotExist import json import redis from api.utils.auth import LoginAuth from api.utils.exceptions import CommonException from api.utils.response import BaseResponse from api.models import * cache = redis.Redis(decode_responses=True) class ShoppingCartView(APIView): authentication_classes = [LoginAuth] def post(self, request): """ 状态码: 1000: 成功 1001: 课程不存在 模拟请求数据: { "course_id":1, "price_policy_id":2 } :param request: :return: """ # 1 获取请求数据 course_id = request.data.get('course_id') price_policy_id = request.data.get('price_policy_id') user_id = request.user.pk res = BaseResponse() try: # 2 校验数据 # 2.1 校验课程是否存在 course_obj = Course.objects.get(pk=course_id) # 2.2 校验价格策略是否合法 price_policy_dict = {} for price_policy in course_obj.price_policy.all(): price_policy_dict[price_policy.pk] = { "pk": price_policy.pk, "valid_period": price_policy.valid_period, "valid_period_text": price_policy.get_valid_period_display(), "price": price_policy.price, "default": price_policy_id == price_policy.pk } # print("price_policy_id:", price_policy_id) # print("price_policy_dict:", price_policy_dict) if price_policy_id not in price_policy_dict: raise CommonException(1002, "价格策略错误!") pp = PricePolicy.objects.get(pk=price_policy_id) # 3 写入redis shopping_car_key = settings.SHOPPINGCAR_KEY % (user_id, course_id) shopping_car_val = { "title": course_obj.name, "img": course_obj.course_img, "relate_price_policy": price_policy_dict, "choose_price_policy_id": price_policy_id, "price": pp.price, "valid_period": pp.valid_period, "valid_period_text": pp.get_valid_period_display() } cache.set(shopping_car_key, json.dumps(shopping_car_val)) res.data = "加入购物车成功!" """ REDIS={ # 方案1 user_id:{ shoppingcar:{ "course_id":{ "title":"....", "img":"...." }, "course_id":{ "title":"....", "img":"...." } "course_id":{ "title":"....", "img":"...." } } } } # 方案2 REDIS={ shoppingcar_1_1:{ "title":"....", "img":"...." } shoppingcar_1_2:{ "title":"....", "img":"...." } } """ except CommonException as e: res.code = e.code res.msg = e.msg except ObjectDoesNotExist as e: res.code = 1001 res.msg = "课程不存在" return Response(res.dict) def get(self, request): res = BaseResponse() try: # 1.获取user_id user_id = request.user.pk # 2.获取所有与当前user_id有关的key shopping_car_key = settings.SHOPPINGCAR_KEY % (user_id, "*") shopping_car_key_list = cache.scan_iter(shopping_car_key) # print("shopping_car_key:", shopping_car_key) # print("redis:", cache.keys()) # 循环从redis中获取当前用户加入购物车中的所有课程 shopping_car_list = [] for key in shopping_car_key_list: course_info = json.loads(cache.get(key)) shopping_car_list.append(course_info) res.data = {"shopping_car_list": shopping_car_list, "total": len(shopping_car_list) } except Exception: res.code = 1033 res.error = "获取购物车失败!" return Response(res.dict) """ 1 post接口创建数据结构: { 'title': 'Django课程', 'img': 'https://www.luffycity.com/static/frontend/course/5/21天_1544059695.5584881.jpeg', 'relate_price_policy': { '1': { 'pk': 1, 'valid_period': 30, 'valid_period_text': '1个月', 'price': 1000.0, 'default': False }, '2': { 'pk': 2, 'valid_period': 60, 'valid_period_text': '2个月', 'price': 2000.0, 'default': True }, '3': { 'pk': 3, 'valid_period': 120, 'valid_period_text': '4个月', 'price': 3000.0, 'default': False } }, 'choose_price_policy_id': 2, 'price': 2000.0, 'valid_period': 60, 'valid_period_text': '2个月' } 2 get接口的数据结构: "data": { "total": 2, "shopping_car_list": [ { "id": 2, "default_price_period": 14, "relate_price_policy": { "1": { "valid_period": 7, "valid_period_text": "1周", "default": false, "prcie": 100 }, "2": { "valid_period": 14, "valid_period_text": "2周", "default": true, "prcie": 200 }, "3": { "valid_period": 30, "valid_period_text": "1个月", "default": false, "prcie": 300 } }, "name": "Django框架学习", "course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", "default_price": 200 }, { "id": 4, "default_price_period": 30, "relate_price_policy": { "4": { "valid_period": 30, "valid_period_text": "1个月", "default": true, "prcie": 1000 }, "5": { "valid_period": 60, "valid_period_text": "2个月", "default": false, "prcie": 1500 } }, "name": "Linux系统基础5周入门精讲", "course_img": "https://luffycity.com/static/frontend/course/12/Linux5周入门_15095893.png", "default_price": 1000 } ] }, "code": 1000, "msg": "" """
from rest_framework.views import APIView from rest_framework.response import Response from django.conf import settings from django.core.exceptions import ObjectDoesNotExist import json import redis from api.utils.auth import LoginAuth from api.utils.exceptions import CommonException from api.utils.response import BaseResponse from api.models import * cache = redis.Redis(decode_responses=True) class AccountView(APIView): authentication_classes = [LoginAuth] # 获取优惠券字典 def get_coupons_dict(self, request, course_id=None): import datetime now = datetime.datetime.now() coupon_record_list = CouponRecord.objects.filter( account=request.user, coupon__content_type=8, coupon__object_id=course_id, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now, ) coupons = {} for coupon_record in coupon_record_list: coupons[coupon_record.pk] = { "name": coupon_record.coupon.name, "coupon_type": coupon_record.coupon.coupon_type, "money_equivalent_value": coupon_record.coupon.money_equivalent_value, "off_percent": coupon_record.coupon.off_percent, "minimum_consume": coupon_record.coupon.minimum_consume, } return coupons def cal_coupon_price(self, price, coupon_info): coupon_type = coupon_info["coupon_type"] money_equivalent_value = coupon_info.get("money_equivalent_value") off_percent = coupon_info.get("off_percent") minimum_consume = coupon_info.get("minimum_consume") rebate_price = 0 if coupon_type == "立减券": rebate_price = price - money_equivalent_value if rebate_price <= 0: rebate_price = 0 elif coupon_type == "满减券": if minimum_consume > price: raise CommonException(3000, "优惠券未达到最低消费") else: rebate_price = price - money_equivalent_value elif coupon_type == "折扣券": rebate_price = price * off_percent/100 return rebate_price def post(self, request): """ 状态码: 1000: 成功 1001: 课程不存在 模拟请求数据: { "course_id_list":[1,2] } :param request: :return: """ # 1 获取请求数据 course_id_list = request.data.get('course_id_list') user_id = request.user.pk res = BaseResponse() # 清空操作 del_list = cache.keys(settings.ACCOUNT_KEY % (user_id, "*")) if del_list: cache.delete(*del_list) price_list = [] try: for course_id in course_id_list: # 结算情况: 1 直接购买 2 购物车结算 account_key = settings.ACCOUNT_KEY % (user_id, course_id) account_val = {} # 获取课程基本信息 shopping_car_key = settings.SHOPPINGCAR_KEY % (user_id, course_id) # course_obj = Course.objects.get(pk=course_id) course_info = json.loads(cache.get(shopping_car_key)) if not cache.exists(shopping_car_key): raise CommonException(1040, "购物车不存在该课程") # 添加到结算字典中 account_val["course_info"] = course_info # 课程价格加入到价格列表中 price_list.append(float(course_info["price"])) # 获取优惠券信息:查询当前用户的当前课程的有效的未使用的优惠券 coupons = self.get_coupons_dict(request, course_id) # 将优惠券字典添加到结算字典中 account_val["coupons"] = coupons cache.set(account_key, json.dumps(account_val)) # 获取通用优惠券 global_coupons = self.get_coupons_dict(request) cache.set("global_coupons_%s" % user_id, json.dumps(global_coupons)) cache.set("total_price", sum(price_list)) except CommonException as e: res.code = e.code res.msg = e.msg except ObjectDoesNotExist: res.code = 1001 res.msg = "课程不存在" return Response(res.dict) def get(self, request, *args, **kwargs): res = BaseResponse() try: user_id = request.user.pk account_key = settings.ACCOUNT_KEY % (user_id, "*") account_key_list = cache.scan_iter(account_key) account_course_list = [] for key in account_key_list: course = json.loads(cache.get(key)) temp = {} for key, val in course["course_info"].items(): temp[key] = val coupon_list = [] for key, val in course["coupons"].items(): val["pk"] = key coupon_list.append(val) temp["coupon_list"] = coupon_list account_course_list.append(temp) global_coupons_dict = json.loads(cache.get("global_coupons_%s" % user_id)) total_price = cache.get("total_price") global_coupons = [] for key, val in global_coupons_dict.items(): global_coupons.append(val) res.data = { "account_course_list": account_course_list, "total": len(account_course_list), "global_coupons": global_coupons, "total_price": total_price } except Exception: res.code = 1033 res.error = "获取购物车失败!" return Response(res.dict) def put(self, request, *args, **kwargs): """ choose_coupons:{ choose_coupons: {"1": "2", "2": "3", "global_coupon_id": "5"}, is_beli: true } :param request: :param args: :param kwargs: :return: """ res = BaseResponse() try: choose_coupons = request.data.get("choose_coupons") is_beli = request.data.get("is_beli") user_id = request.user.pk # 获取结算课程表 cal_price = {} data = self.get(request).data.get("data") account_course_list = data.get("account_course_list") account_course_info = {} for account_course in account_course_list: temp = { "coupons":{}, "default_price": account_course["price"] } account_course_info[account_course["id"]] = temp for item in account_course["coupon_list"]: temp["coupons"][item["pk"]] = item price_list = [] total_price = 0 for key, val in account_course_info.items(): if str(key) not in choose_coupons: price_list.append(val["default_price"]) cal_price[key] = val["default_price"] else: coupon_info = val.get("coupons").get(str(choose_coupons[str(key)])) rebate_price = self.cal_coupon_price(val["default_price"], coupon_info) price_list.append(rebate_price) cal_price[key] = rebate_price total_price = sum(price_list) # 计算通用优惠券的价格 global_coupon_id = choose_coupons.get("global_coupon_id") if global_coupon_id: global_coupons = data.get("global_coupons") global_coupon_dict = {} for item in global_coupons: global_coupon_dict[item["pk"]] = item total_price = self.cal_coupon_price(total_price, global_coupon_dict[global_coupon_id]) # 计算贝里 if json.loads(is_beli): total_price = total_price - request.user.beli/10 if total_price < 0: total_price = 0 cal_price["total_price"] = total_price res.data = cal_price except Exception as e: res.code = 500 res.msg = "结算错误!" + str(e) return Response(res.dict) """ account post接口: account_1_1:{ 'course_info': { 'title': 'Django课程', 'img': 'https://www.luffycity.com/static/frontend/course/5/21天_1544059695.5584881.jpeg', 'relate_price_policy': { '1': { 'pk': 1, 'valid_period': 30, 'valid_period_text': '1个月', 'price': 1000.0, 'default': False }, '2': { 'pk': 2, 'valid_period': 60, 'valid_period_text': '2个月', 'price': 2000.0, 'default': True }, '3': { 'pk': 3, 'valid_period': 120, 'valid_period_text': '4个月', 'price': 3000.0, 'default': False } }, 'choose_price_policy_id': 2, 'price': 2000.0, 'valid_period': 60, 'valid_period_text': '2个月' }, 'coupons': { '1': { 'name': '双11百元立减券', 'coupon_type': 0, 'money_equivalent_value': 100.0, 'off_percent': None, 'minimum_consume': 0 }, '2': { 'name': '双十二五折优惠券', 'coupon_type': 2, 'money_equivalent_value': 0.0, 'off_percent': 50, 'minimum_consume': 0 } } } global_coupons_1:{ '3': { 'name': 'alex大婚满减券', 'coupon_type': 1, 'money_equivalent_value': 50.0, 'off_percent': None, 'minimum_consume': 1000 } } """
import json from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response from api.utils.auth import LoginAuth from api.utils.pay import AliPay from api.utils.response import BaseResponse from api.utils.exceptions import CommonException from api.models import * class PaymentView(APIView): authentication_classes = [LoginAuth] def get_alipay(self): # 沙箱环境地址:https://openhome.alipay.com/platform/appDaily.htm?tab=info app_id = "2016091100486897" # POST请求,用于最后的检测 notify_url = "http://47.94.172.250:8804/page2/" # notify_url = "http://www.wupeiqi.com:8804/page2/" # GET请求,用于页面的跳转展示 return_url = "http://47.94.172.250:8804/page2/" # return_url = "http://www.wupeiqi.com:8804/page2/" merchant_private_key_path = "keys/app_private_2048.txt" alipay_public_key_path = "keys/alipay_public_2048.txt" alipay = AliPay( appid=app_id, app_notify_url=notify_url, return_url=return_url, app_private_key_path=merchant_private_key_path, alipay_public_key_path=alipay_public_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥 debug=True, # 默认False, ) return alipay def cal_coupon_price(self, price, coupon_record_obj): coupon_type = coupon_record_obj.coupon.coupon_type money_equivalent_value = coupon_record_obj.coupon.money_equivalent_value off_percent = coupon_record_obj.coupon.off_percent minimum_consume = coupon_record_obj.coupon.minimum_consume rebate_price = 0 if coupon_type == 0: # 立减券 rebate_price = price - money_equivalent_value if rebate_price <= 0: rebate_price = 0 elif coupon_type == 1: # 满减券 if minimum_consume > price: raise CommonException(1007, "优惠券未达到最低消费") else: rebate_price = price - money_equivalent_value elif coupon_type == 2: rebate_price = price * off_percent / 100 print("原价格", price) print("计算优惠券价格", coupon_record_obj.coupon.get_coupon_type_display(), rebate_price) return rebate_price def post(self, request): """ 模拟前端数据: { "is_beli":"true", "course_list"=[ { "course_id":1 "default_price_policy_id":1, "coupon_record_id":2 }, { "course_id":2 "default_price_policy_id":4, "coupon_record_id":6 } ], "global_coupon_id":3, "pay_money":298 } :param request: :return: """ # 1 获取数据 course_list = request.data.get("course_list") global_coupon_id = request.data.get("global_coupon_id") is_beli = request.data.get("is_beli") pay_money = request.data.get("pay_money") user_id = request.user.pk res = BaseResponse() # 2 校验数据 # 2.1 校验每一个课程 try: price_list = [] for course in course_list: course_id = course.get("course_id") default_price_policy_id = course.get("default_price_policy_id") coupon_record_id = course.get("coupon_record_id") # 2.1.1 校验课程是否存在 course_obj = Course.objects.filter(pk=course_id).first() if not course_obj: raise CommonException(1001, "课程不存在") # 2.1.2 校验价格策略 if default_price_policy_id not in [obj.pk for obj in course_obj.price_policy.all()]: raise CommonException(1002, "价格策略错误!") pp = PricePolicy.objects.get(pk=default_price_policy_id) # 2.1.3 校验课程优惠券 if coupon_record_id: import datetime now = datetime.datetime.now() coupon_record_obj = CouponRecord.objects.filter( pk=coupon_record_id, account=request.user, coupon__content_type=8, coupon__object_id=course_id, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now ).first() if not coupon_record_obj: raise CommonException(1003, "课程优惠券异常!") # 计算折后价格 rebate_money = self.cal_coupon_price(pp.price, coupon_record_obj) price_list.append(rebate_money) else: price_list.append(pp.price) print("price_list", price_list) final_price = sum(price_list) # 2.2 校验通用优惠券 if global_coupon_id: # 2.2.1 校验通用优惠券合法 import datetime now = datetime.datetime.now() g_coupon_record_obj = CouponRecord.objects.filter( pk=global_coupon_id, account=request.user, coupon__content_type=8, coupon__object_id=None, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now ).first() if not g_coupon_record_obj: raise CommonException(1004, "通用优惠券异常!") # 2.2.2 计算折后价格 final_price = self.cal_coupon_price(final_price, g_coupon_record_obj) # 2.3 校验贝里 if json.loads(is_beli): final_price = final_price - request.user.beli / 10 if final_price < 0: final_price = 0 # 2.4 校验最终价格 print("final_price", final_price) if final_price != pay_money: raise CommonException(1005, "支付价格异常!") # 3 生成订单 # 生成订单记录 # 3 生成订单记录 # Order记录 # orderDetail # orderDetail # orderDetail import uuid order_obj = Order.objects.create( payment_type=1, order_number=uuid.uuid4(), account=request.user, status=1, order_type=1, actual_amount=pay_money, ) print("course_list", course_list) for course_item in course_list: OrderDetail.objects.create( order=order_obj, content_type_id=14, object_id=course_item.get("course_id"), # original_price=course_item.get("original_price"), # price=course_item.get("rebate_price") or course_item.get("original_price"), # valid_period=course_item.get("valid_period"), # valid_period_display=course_item.get("valid_period_display"), ) # 4 调用alipay接口 import time alipay = self.get_alipay() # 生成支付的url query_params = alipay.direct_pay( subject="路飞学城", # 商品简单描述 out_trade_no="x345" + str(time.time()), # 商户订单号 total_amount=pay_money, # 交易金额(单位: 元 保留俩位小数) ) pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params) res.data = { "url": pay_url } except ObjectDoesNotExist: res.code = 1001 res.msg = "课程不存在!" except CommonException as e: res.code = e.code res.msg = e.msg return Response(res.dict)
1.支付接口在views/payment.py中,引入了utils/pay.py,还有keys中的应用私钥及支付宝公钥 2.注意在settings中加上如下代码, # 因为使用了AbstractUser AUTH_USER_MODEL = 'api.UserInfo' # redis中存储购物车的键 SHOPPINGCAR_KEY = "shoppingcar_%s_%s" # redis中存储结算信息的键 ACCOUNT_KEY = "account_%s_%s"
2.封装的支付接口的使用
使用方式:
(1)准备好封装好的支付接口 ali包,放在项目下,如utils/ali/...(博客文件中下载)
(2)settings中配置如下代码:
THIRD_PART_CONFIG = { # 阿里云服务配置 "ALI_YUN": { }, # 滑动验证码服务配置 "GEE_TEST": { }, # 支付宝支付相关配置 "ALI_PAY": { # 默认使用配置 "default": { "version": "1.0", # 支付宝支付调用的接口版本(固定值1.0) "debug": True, # 是否启用调试模式(False是正式环境) "app_id": "2016091800539084", # 支付宝分配给开发者的应用ID(启用线上环境请更改), "app_private_key_path": os.path.join( BASE_DIR, 'keys', 'app_private_2048.txt' ), # APP应用的私钥 "alipay_public_key_path": os.path.join( BASE_DIR, 'keys', 'alipay_public_2048.txt' ), # 支付宝的公钥 "callback_url": "http://47.94.172.250:8804/api/v1/trade/alipay/", # 添加回调域名 }, # 目前针对支付业务进行切换 "pay": { } } }
(3)准备好应用私钥和支付宝公钥:keys/app_private_2048.txt keys/alipay_public_2048.txt
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCK5urihEF0Yje6KSylvVcdBGANbyPWInuTX7uJX3Uz7nDUEuKROF4W6c6QLSckt0LPeZTA6llB5pW0TrW8UPqVQCtcSkUpbYZ7srTV9uy4y3oDoQwz5AWUL97iYRCubewiqsrATTGxRyDTp3OvBncIKt8+wm++3/IO7fHX/dNttobkXO0FP0tkVc+zaslmpN7HsEMFYvbfHQzmrGQ2gobPONYJOueUEw8jWpQcTnKq3SgfUhjX7kgoKenoZf+2Ao6/rmyYQqnxtqzstgdJzuD/Wb2dFIYjIP/fWplDVZEEcan/VlHcuAfXv3s9iXVLMmTTyvqU77s+2gdyYwNLXhxFAgMBAAECggEAQ0Tf/kGk3XNvn6WvLLlMxg3HYtovVdYvWMklLrtfLH5OgDaBKWlOD/S9iA+GBH8ISSiNhPw5q/O7Dq6Lzx68rKl+Fl0Vr6GOXrvGXlUOgdRxS+6j1UGZ/hFM9P+jL4amtIdYV9dKuJtE55wSJ3KPFRKGOYO60IruVJKh7EPOMDRbSSfWQbU0S6AoChpDyGbYdQn/oWc6sdrZHLZtqXgjgp9AdbWVDEZvY7g7kBxjaIpaAs5CWxVDc1O29E1I4Mg7hssxe401PgFtofIgV19v6siPvSs/wI/sZhJjJddG0ih17CWWdTzkrgtEfh+sVowtkd+KtP7QMMaFrnrkJAlGpQKBgQDfbhVS9IiBlHrWtyvHQsPLJrQ9IfIO+ZjEOGSRiMWxK/+4DoJkQU6Yj6v9O3g0oQg0VnPCoTEtKhYKpgP/tue02hAPyRxdvO4mU545s7YeVNDgrk2L5GPr8QTJnfLjTvYoy2hZdoga+moBW8u/YmO3k0QRLB82jActqK6vC+PQYwKBgQCfJm972sIc168AKWuOft9RyOPqwozFuJww8Etu9EAW1+UFxjZPFyY5JYfUu2VZck9WjIazYQO5pEm3jzMOP4qj9fhf3kBRz2g3EwyzxZLfxAbWnH+OPNZx8BIxoJtqgFOeZWanXyaoiCCZvQ54NmFBXysDeUXJCxLfjg+1nmR9NwKBgQDVFUGU+c1t91Mnj01bHdto1aKzYrpdecEt8bJH8a7Ih3O772p/fqEccnjOa3b6ilEuyPxhtCUYM7kNssLBj4hvPEBxLZW1+EcPmlOeKDwZtT336YPfVJPPIu8z8UUBb/7nbQY5vAeV4xhR71/jSExdeT9DOVcTSHxYGTVvj+FWjQKBgGazE8/127tnB1vwXqLmhk+tdj5A6zyQI+KEvfjMjyruiLDQNq2U/6py6JNDlmRBGqd8KVRJ73B1bsiQFN9F675gdLXQouroD5Uyqsi7X0scoVkORlXQNoXx6Juzy3bPdqZJQxQQl867gWYUFOlIFjxsIEKumHTiu3wdnU+S9b/DAoGARB/tRjp3iyOzKWLXvS3Dv4JZ/ZxuO/hnM8s+oJu5qODuYm9wD/rdw3L2bc+GsDK58xOB8+Avt/cYvakyno9dH9y8bA2wCCe9Ho1y1oBWQ0AJ9UxrFqHVB6iIYzLxxNcVrdoUE7XrEvKJE9Zm3Ss0x+E7VYU2Oz93zHda6oINijI=
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB -----END PUBLIC KEY-----
(4)在views.payment.py中的post接口中使用,支付之前要先校验所有数据,然后生成订单,再通过封装的支付接口生成支付的url返回给前端,用户扫码支付成功后页面会重新向后端发一个我们在settings中设置好的回调url "callback_url",该url的视图trade的post接口用于接收支付宝返回的数据并修改支付成功的数据库中的订单状态等信息,get接口为支付成功后返回给前端一个url(包含商户订单号)来展示给用户看的。
from django.contrib import admin from django.urls import path, re_path, include from rest_framework.routers import DefaultRouter from api.views.course import ( CourseView, CourseDetailView, CourseCategoryView ) from api.views.login import LoginView from api.views.shoppingcart import ShoppingCartView from api.views.account import AccountView from api.views.payment import PaymentView from api.views.captcha import CaptchaView from api.views.logout import LogoutView from api.views.order import OrderView from api.views.trade import AlipayTradeView router = DefaultRouter() router.register("courses", CourseView) router.register("course_detail", CourseDetailView) router.register("course/category", CourseCategoryView) urlpatterns = [ path('admin/', admin.site.urls), path('login/', LoginView.as_view()), # 极验geetest path("api/captcha_check/", CaptchaView.as_view()), path("logout", LogoutView.as_view()), re_path("", include(router.urls)), # 购物车 path('shoppingcart/', ShoppingCartView.as_view()), # 结算 path('account/', AccountView.as_view()), path("myorder/", OrderView.as_view()), # 支付 path('payment/', PaymentView.as_view()), path("api/v1/trade/alipay/", AlipayTradeView), ]
import json import datetime import random from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response import redis from django.conf import settings from api.utils.auth import LoginAuth from api.utils.response import BaseResponse from api.utils.exceptions import CommonException from api.utils.ali.api import ali_api from api.models import * cache = redis.Redis(decode_responses=True) class PaymentView(APIView): authentication_classes = [LoginAuth] def get_pay_url(self, request, order_number, final_price): # 4 调用支付宝支付接口(二维码页面) if request.META["HTTP_USER_AGENT"]: pay_api = ali_api.pay.pc elif request == "APP": pay_api = ali_api.pay.app else: pay_api = ali_api.pay.wap pay_url = pay_api.direct( subject="路飞学城", # 商品简单描述 out_trade_no=order_number, # 商户订单号 total_amount=final_price, # 交易金额(单位: 元 保留俩位小数) ) return pay_url def cal_coupon_price(self, price, coupon_record_obj): coupon_type = coupon_record_obj.coupon.coupon_type money_equivalent_value = coupon_record_obj.coupon.money_equivalent_value off_percent = coupon_record_obj.coupon.off_percent minimum_consume = coupon_record_obj.coupon.minimum_consume rebate_price = 0 if coupon_type == 0: # 立减券 rebate_price = price - money_equivalent_value if rebate_price <= 0: rebate_price = 0 elif coupon_type == 1: # 满减券 if minimum_consume > price: raise CommonException(1007, "优惠券未达到最低消费") else: rebate_price = price - money_equivalent_value elif coupon_type == 2: rebate_price = price * off_percent / 100 print("原价格", price) print("计算优惠券价格", coupon_record_obj.coupon.get_coupon_type_display(), rebate_price) return rebate_price def get_order_num(self): now = datetime.datetime.now() orderType = "1" dateStr4yyyyMMddHHmmss = "{0}{1}{2}".format(now.year, now.month, now.day) rand = str(random.randint(1000, 9999)) s = orderType+dateStr4yyyyMMddHHmmss+rand return s def post(self, request): """ 模拟前端数据: { "is_beli":"true", "course_list"=[ { "course_id":1 "default_price_policy_id":1, "coupon_record_id":2 }, { "course_id":2 "default_price_policy_id":4, "coupon_record_id":6 } ], "global_coupon_id":3, "pay_money":298 } :param request: :return: """ # 1 获取数据 course_list = request.data.get("course_list") global_coupon_id = request.data.get("global_coupon_id") is_beli = request.data.get("is_beli") pay_money = request.data.get("pay_money") user_id = request.user.pk res = BaseResponse() # 2 校验数据 # 2.1 校验每一个课程 try: price_list = [] for course in course_list: course_id = course.get("course_id") default_price_policy_id = course.get("default_price_policy_id") coupon_record_id = course.get("coupon_record_id") # 2.1.1 校验课程是否存在 course_obj = Course.objects.filter(pk=course_id).first() if not course_obj: raise CommonException(1001, "课程不存在") # 2.1.2 校验价格策略 if default_price_policy_id not in [obj.pk for obj in course_obj.price_policy.all()]: raise CommonException(1002, "价格策略错误!") pp = PricePolicy.objects.get(pk=default_price_policy_id) # 2.1.3 校验课程优惠券 if coupon_record_id: now = datetime.datetime.now() coupon_record_obj = CouponRecord.objects.filter( pk=coupon_record_id, account=request.user, coupon__content_type=8, coupon__object_id=course_id, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now ).first() if not coupon_record_obj: raise CommonException(1003, "课程优惠券异常!") # 计算折后价格 rebate_money = self.cal_coupon_price(pp.price, coupon_record_obj) price_list.append(rebate_money) else: price_list.append(pp.price) print("price_list", price_list) final_price = sum(price_list) # 2.2 校验通用优惠券 if global_coupon_id: # 2.2.1 校验通用优惠券合法 now = datetime.datetime.now() g_coupon_record_obj = CouponRecord.objects.filter( pk=global_coupon_id, account=request.user, coupon__content_type=8, coupon__object_id=None, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now ).first() if not g_coupon_record_obj: raise CommonException(1004, "通用优惠券异常!") # 2.2.2 计算折后价格 final_price = self.cal_coupon_price(final_price, g_coupon_record_obj) # 2.3 校验贝里 if json.loads(is_beli): final_price = final_price - request.user.beli / 10 cost_beli_num = request.user.beli if final_price < 0: final_price = 0 cost_beli_num = final_price * 10 # 2.4 校验最终价格 print("final_price", final_price) if final_price != pay_money: raise CommonException(1005, "支付价格异常!") # 3 生成订单记录 order_number = self.get_order_num() order_obj = Order.objects.create( payment_type=1, order_number=order_number, account=request.user, status=1, order_type=1, actual_amount=pay_money, ) print("course_list", course_list) for course_item in course_list: OrderDetail.objects.create( order=order_obj, content_type_id=14, object_id=course_item.get("course_id"), # original_price=course_item.get("original_price"), # price=course_item.get("rebate_price") or course_item.get("original_price"), # valid_period=course_item.get("valid_period"), # valid_period_display=course_item.get("valid_period_display"), ) request.user.beli = request.user.beli - cost_beli_num request.user.save() cache.set(order_number + "|" + str(cost_beli_num), "", 20) account_key = settings.ACCOUNT_KEY % (user_id, "*") cache.delete(*cache.keys(account_key)) # 生成支付的url res.data = self.get_pay_url(request, order_number, final_price) except ObjectDoesNotExist: res.code = 1001 res.msg = "课程不存在!" except CommonException as e: res.code = e.code res.msg = e.msg return Response(res.dict)
from rest_framework.views import APIView from api.utils.ali.api import ali_api from api.utils.ali.tools import verify_signature from rest_framework.response import Response from api.models import Order import datetime,time from django.shortcuts import HttpResponse,redirect from api.utils.response import BaseResponse class AlipayTradeView(APIView): def get(self, request, *args, **kwargs): try: processed_dict = {} for key, value in self.request.query_params.items(): processed_dict[key] = value # 校验签名 verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key) if not verify_result: return HttpResponse("sign is invalid") out_trade_no = processed_dict.get("out_trade_no") # 商户订单号 redirect_to = "{0}?order_num={1}".format("http://47.94.172.250:8804/order/pay_success", out_trade_no) return redirect(redirect_to) except Exception as e: return HttpResponse("fail!") def post(self, request): """ 处理支付宝的notify_url 支付宝对应交易的四种状态: 1,WAIT_BUYER_PAY 交易创建,等待买家付款 2,TRADE_CLOSED 未付款交易超时关闭,或支付完成后全额退款 3,TRADE_SUCCESS 交易支付成功 4,TRADE_FINISHED 交易结束,不可退款 如果支付成功, 将要处理的事件: 1, 订单状态更改为交易完成(变更状态后, 通过信号进行如下操作) 2, 如果使用优惠券, 支付成功将要把优惠券的状态更改为已使用 3, 如果使用余额, 将要进行扣除 """ processed_dict = {} for key, value in self.request.data.items(): processed_dict[key] = value # 校验签名 verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key) if not verify_result: return Response("fail") order_sn = processed_dict.get("out_trade_no", "") # 商户网站唯一订单号 trade_no = processed_dict.get("trade_no", "") # 该交易在支付宝系统中的交易流水号。最长64位 trade_status = processed_dict.get("trade_status", "") # 支付宝系统的交易状态 # 支付成功 # 为放止支付宝重复请求, 获取到订单号并查询该订单的状态是否为交易完成, 如果交易完成, 即直接返回成功信号 # 为该用户创建报名课程, 创建报名时间及结束时间 # 区分LuffyX课程 和 付费课程 # 当前策略LuffyX课程是不能加入购物车的 if trade_status == "TRADE_SUCCESS": gmt_payment = processed_dict.get("gmt_payment") # 买家付款时间 格式 yyyy-MM-dd HH:mm:ss passback_params = processed_dict.get("passback_params", "{}") # 公共回传参数 # 修改订单状态 save_status = self.change_order_status(order_sn, trade_no, gmt_payment, "alipay", passback_params) if save_status is True: return Response("success") return Response("fail") def change_order_status(order_num, payment_number, gmt_payment, trade_type, extra_params): """交易成功修改订单相关的状态 Parameters ---------- order_num : string 订单号 payment_number : string or None 第三方订单号 gmt_payment : string 交易时间(要根据不同的交易方式格式化交易时间) trade_type: string 交易方式 extra_params: string json 交易回传参数 Returns ------- bool """ try: exist_order = Order.objects.get(order_number=order_num) pay_time = datetime.datetime.strptime(gmt_payment, "%Y-%m-%d %H:%M:%S") if exist_order.status == 0: return True # 变更订单状态 exist_order.payment_number = payment_number exist_order.status = 0 exist_order.pay_time = pay_time exist_order.save(update_fields=("payment_number", "status", "pay_time", ),) except Exception as e: pass return True
封装版支付接口 示例:
AUTH_USER_MODEL = 'api.UserInfo' SHOPPINGCAR_KEY = "shoppingcar_%s_%s" ACCOUNT_KEY = "account_%s_%s" GEE_TEST = { "gee_test_access_id": "37ca5631edd1e882721808d35163b3ad", "gee_test_access_key": "7eb11ccf3e0953bdd060ed8b60b0c4f5", "verify_status": True, # 是否启用滑动验证码验证组件(True表示启用) "not_verify": [ "2ba6b08d53a4fd27057a32537e2d55ae", ], # 不用验证的用户(存放着用户的uid) } THIRD_PART_CONFIG = { # 阿里云服务配置 "ALI_YUN": { }, # 滑动验证码服务配置 "GEE_TEST": { }, # 支付宝支付相关配置 "ALI_PAY": { # 默认使用配置 "default": { "version": "1.0", # 支付宝支付调用的接口版本(固定值1.0) "debug": True, # 是否启用调试模式(False是正式环境) "app_id": "2016091800539084", # 支付宝分配给开发者的应用ID(启用线上环境请更改), "app_private_key_path": os.path.join( BASE_DIR, 'keys', 'app_private_2048.txt' ), # APP应用的私钥 "alipay_public_key_path": os.path.join( BASE_DIR, 'keys', 'alipay_public_2048.txt' ), # 支付宝的公钥 "callback_url": "http://47.94.172.250:8804/api/v1/trade/alipay/", # 添加回调域名 }, # 目前针对支付业务进行切换 "pay": { } } }
from django.contrib import admin from django.urls import path, re_path, include from rest_framework.routers import DefaultRouter from api.views.course import ( CourseView, CourseDetailView, CourseCategoryView ) from api.views.login import LoginView from api.views.shoppingcart import ShoppingCartView from api.views.account import AccountView from api.views.payment import PaymentView from api.views.captcha import CaptchaView from api.views.logout import LogoutView from api.views.order import OrderView from api.views.trade import AlipayTradeView router = DefaultRouter() router.register("courses", CourseView) router.register("course_detail", CourseDetailView) router.register("course/category", CourseCategoryView) urlpatterns = [ path('admin/', admin.site.urls), path('login/', LoginView.as_view()), # 极验geetest path("api/captcha_check/", CaptchaView.as_view()), path("logout", LogoutView.as_view()), re_path("", include(router.urls)), # 购物车 path('shoppingcart/', ShoppingCartView.as_view()), # 结算 path('account/', AccountView.as_view()), path("myorder/", OrderView.as_view()), # 支付 path('payment/', PaymentView.as_view()), path("api/v1/trade/alipay/", AlipayTradeView), ]
-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB -----END PUBLIC KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCK5urihEF0Yje6KSylvVcdBGANbyPWInuTX7uJX3Uz7nDUEuKROF4W6c6QLSckt0LPeZTA6llB5pW0TrW8UPqVQCtcSkUpbYZ7srTV9uy4y3oDoQwz5AWUL97iYRCubewiqsrATTGxRyDTp3OvBncIKt8+wm++3/IO7fHX/dNttobkXO0FP0tkVc+zaslmpN7HsEMFYvbfHQzmrGQ2gobPONYJOueUEw8jWpQcTnKq3SgfUhjX7kgoKenoZf+2Ao6/rmyYQqnxtqzstgdJzuD/Wb2dFIYjIP/fWplDVZEEcan/VlHcuAfXv3s9iXVLMmTTyvqU77s+2gdyYwNLXhxFAgMBAAECggEAQ0Tf/kGk3XNvn6WvLLlMxg3HYtovVdYvWMklLrtfLH5OgDaBKWlOD/S9iA+GBH8ISSiNhPw5q/O7Dq6Lzx68rKl+Fl0Vr6GOXrvGXlUOgdRxS+6j1UGZ/hFM9P+jL4amtIdYV9dKuJtE55wSJ3KPFRKGOYO60IruVJKh7EPOMDRbSSfWQbU0S6AoChpDyGbYdQn/oWc6sdrZHLZtqXgjgp9AdbWVDEZvY7g7kBxjaIpaAs5CWxVDc1O29E1I4Mg7hssxe401PgFtofIgV19v6siPvSs/wI/sZhJjJddG0ih17CWWdTzkrgtEfh+sVowtkd+KtP7QMMaFrnrkJAlGpQKBgQDfbhVS9IiBlHrWtyvHQsPLJrQ9IfIO+ZjEOGSRiMWxK/+4DoJkQU6Yj6v9O3g0oQg0VnPCoTEtKhYKpgP/tue02hAPyRxdvO4mU545s7YeVNDgrk2L5GPr8QTJnfLjTvYoy2hZdoga+moBW8u/YmO3k0QRLB82jActqK6vC+PQYwKBgQCfJm972sIc168AKWuOft9RyOPqwozFuJww8Etu9EAW1+UFxjZPFyY5JYfUu2VZck9WjIazYQO5pEm3jzMOP4qj9fhf3kBRz2g3EwyzxZLfxAbWnH+OPNZx8BIxoJtqgFOeZWanXyaoiCCZvQ54NmFBXysDeUXJCxLfjg+1nmR9NwKBgQDVFUGU+c1t91Mnj01bHdto1aKzYrpdecEt8bJH8a7Ih3O772p/fqEccnjOa3b6ilEuyPxhtCUYM7kNssLBj4hvPEBxLZW1+EcPmlOeKDwZtT336YPfVJPPIu8z8UUBb/7nbQY5vAeV4xhR71/jSExdeT9DOVcTSHxYGTVvj+FWjQKBgGazE8/127tnB1vwXqLmhk+tdj5A6zyQI+KEvfjMjyruiLDQNq2U/6py6JNDlmRBGqd8KVRJ73B1bsiQFN9F675gdLXQouroD5Uyqsi7X0scoVkORlXQNoXx6Juzy3bPdqZJQxQQl867gWYUFOlIFjxsIEKumHTiu3wdnU+S9b/DAoGARB/tRjp3iyOzKWLXvS3Dv4JZ/ZxuO/hnM8s+oJu5qODuYm9wD/rdw3L2bc+GsDK58xOB8+Avt/cYvakyno9dH9y8bA2wCCe9Ho1y1oBWQ0AJ9UxrFqHVB6iIYzLxxNcVrdoUE7XrEvKJE9Zm3Ss0x+E7VYU2Oz93zHda6oINijI=
-----END RSA PRIVATE KEY-----
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.auth.models import AbstractUser from django.utils.safestring import mark_safe # 课程表 ######################################## class CourseCategory(models.Model): name = models.CharField(max_length=64, unique=True) def __str__(self): return "%s" % self.name class Meta: verbose_name = "课程类" verbose_name_plural = "课程类" class Course(models.Model): """ 专题课程 """ name = models.CharField(max_length=128, unique=True, verbose_name="模块") course_img = models.CharField(max_length=255) course_type_choices = ((0, '付费'), (1, 'VIP专享'), (2, '学位课程')) course_type = models.SmallIntegerField(choices=course_type_choices) 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) course_category = models.ForeignKey("CourseCategory", on_delete=models.CASCADE, null=True, blank=True) order_details = GenericRelation("OrderDetail", related_query_name="course") coupon = GenericRelation("Coupon") price_policy = GenericRelation("PricePolicy") # 用于GenericForeignKey反向查询,不会生成表字段,切勿删除,如有疑问请联系老村长 def __str__(self): return "%s(%s)" % (self.name, self.get_course_type_display()) class CourseDetail(models.Model): """ 课程详情页内容 """ course = models.OneToOneField("Course", on_delete=models.CASCADE) hours = models.IntegerField("课时") course_slogan = models.CharField(max_length=125, blank=True, null=True) video_brief_link = models.CharField(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 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 PricePolicy(models.Model): """ 价格与有课程效期表 """ content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 关联course or degree_course object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') # course = models.ForeignKey("Course") valid_period_choices = ((1, '1天'), (3, '3天'), (7, '1周'), (14, '2周'), (30, '1个月'), (60, '2个月'), (90, '3个月'), (120, '4个月'), (180, '6个月'), (210, '12个月'), (540, '18个月'), (720, '24个月'), (722, '24个月'), (723, '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 CourseChapter(models.Model): """ 课程章节 """ course = models.ForeignKey("Course", related_name='coursechapters', on_delete=models.CASCADE) chapter = models.SmallIntegerField(verbose_name="第几章", default=1) name = models.CharField(max_length=128) summary = models.TextField(verbose_name="章节介绍", blank=True, null=True) is_create = models.BooleanField(verbose_name="是否创建题库进度", default=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', on_delete=models.CASCADE) 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) 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) is_flash = models.BooleanField(verbose_name="是否使用FLASH播放", default=False) player_choices = ((0, "CC"), (1, "POLYV"), (2, "ALI")) player = models.SmallIntegerField(choices=player_choices, default=1, help_text="视频播放器选择") def course_chapter(self): return self.chapter.chapter def course_name(self): return self.chapter.course.name class Meta: unique_together = ('chapter', 'section_link') def __str__(self): return "%s-%s" % (self.chapter, self.name) class OftenAskedQuestion(models.Model): """常见问题""" content_type = models.ForeignKey(ContentType, limit_choices_to={'model__contains': 'course'}, on_delete=models.CASCADE) # 关联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') verbose_name_plural = "常见问题" # 优惠券 ######################################## 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.FloatField(verbose_name="等值货币", blank=True, null=True) 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, on_delete=models.CASCADE) 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="自券被领时开始算起") status_choices = ((0, "上线"), (1, "下线")) status = models.SmallIntegerField(choices=status_choices, default=0) date = models.DateTimeField(auto_now_add=True) def __str__(self): return "%s(%s)" % (self.get_coupon_type_display(), self.name) class CouponRecord(models.Model): """ 优惠券发放、消费纪录 """ coupon = models.ForeignKey("Coupon", on_delete=models.CASCADE) account = models.ForeignKey("UserInfo", blank=True, null=True, verbose_name="使用者", on_delete=models.CASCADE) status_choices = ((0, '未使用'), (1, '已使用'), (2, '已过期'), (3, '未领取')) status = models.SmallIntegerField(choices=status_choices, default=0) get_time = models.DateTimeField(blank=True, null=True, verbose_name="领取时间", help_text="用户领取时间") used_time = models.DateTimeField(blank=True, null=True, verbose_name="使用时间") order = models.ForeignKey("Order", blank=True, null=True, verbose_name="关联订单", on_delete=models.CASCADE) # 一个订单可以有多个优惠券 date = models.DateTimeField(auto_now_add=True, verbose_name="生成时间") # _coupon = GenericRelation("Coupon") def __str__(self): return self.coupon.name + "优惠券记录" # 订单表 ######################################## class Order(models.Model): """ 订单 """ payment_type_choices = ((0, '微信'), (1, '支付宝'), (2, '优惠码'), (3, '贝里'), (4, '银联')) 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("UserInfo", on_delete=models.CASCADE) actual_amount = models.FloatField(verbose_name="实付金额") # coupon = models.OneToOneField("Coupon", blank=True, null=True, verbose_name="优惠码") #一个订单可以有多个优惠券 status_choices = ((0, '交易成功'), (1, '待支付'), (2, '退费申请中'), (3, '已退费'), (4, '主动取消'), (5, '超时取消')) status = models.SmallIntegerField(choices=status_choices, verbose_name="状态") order_type_choices = ((0, '用户下单'), (1, '线下班创建'),) order_type = models.SmallIntegerField(choices=order_type_choices, default=0, 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", on_delete=models.CASCADE) content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) # 可关联普通课程或学位 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 UserInfo(AbstractUser): username = models.CharField("用户名", max_length=64, unique=True) email = models.EmailField( verbose_name='email address', max_length=255, unique=True, blank=True, null=True ) uid = models.CharField(max_length=64, unique=True) # 与第3方交互用户信息时,用这个uid,以避免泄露敏感用户信息 mobile = models.BigIntegerField(verbose_name="手机", unique=True, help_text="用于手机验证码登录", null=True) qq = models.CharField(verbose_name="QQ", max_length=64, blank=True, null=True, db_index=True) weixin = models.CharField(max_length=128, blank=True, null=True, db_index=True, verbose_name="微信") signature = models.CharField('个人签名', blank=True, null=True, max_length=255) brief = models.TextField("个人介绍", blank=True, null=True) openid = models.CharField(max_length=128, blank=True, null=True) alipay_card = models.CharField(max_length=128, blank=True, null=True, verbose_name="支付宝账户") gender_choices = ((0, '保密'), (1, '男'), (2, '女')) gender = models.SmallIntegerField(choices=gender_choices, default=0, verbose_name="性别") id_card = models.CharField(max_length=32, blank=True, null=True, verbose_name="身份证号或护照号") password = models.CharField('password', max_length=128, help_text=mark_safe('''<a class='btn-link' href='password'>重置密码</a>''')) is_active = models.BooleanField(default=True, verbose_name="账户状态") is_staff = models.BooleanField(verbose_name='staff status', default=False, help_text='决定着用户是否可登录管理后台') name = models.CharField(max_length=32, default="", verbose_name="真实姓名") head_img = models.CharField(max_length=256, default='/static/frontend/head_portrait/logo@2x.png', verbose_name="个人头像") role_choices = ((0, '学员'), (1, '导师'), (2, '讲师'), (3, '管理员'), (4, '班主任'), (5, '线下班主任')) role = models.SmallIntegerField(choices=role_choices, default=0, verbose_name="角色") # balance = models.PositiveIntegerField(default=0, verbose_name="可提现余额") # #此处通过transaction_record表就可以查到,所以不用写在这了 memo = models.TextField('备注', blank=True, null=True, default=None, help_text="json格式存储") date_joined = models.DateTimeField(auto_now_add=True, verbose_name="注册时间") beli = models.IntegerField(default=1000) class Meta: verbose_name = '用户信息' verbose_name_plural = "用户信息" def __str__(self): return "%s(%s)" % (self.username, self.get_role_display()) class Token(models.Model): """ The default authorization token model. """ key = models.CharField(max_length=40) user = models.OneToOneField(UserInfo, related_name='auth_token', on_delete=models.CASCADE, verbose_name="关联用户") created = models.DateTimeField(verbose_name="创建时间", auto_now_add=True) def __str__(self): return self.key
博客文件中下载
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import APIException from django.core.cache import cache import datetime import pytz from api.models import Token class LoginAuth(BaseAuthentication): def authenticate(self, request): token = request.META.get("HTTP_AUTHORIZATION") # 校验是否存在token字符串 # 缓存校验 user = cache.get(token) if user: # print(user, token) return user, token # 数据库校验 token_obj = Token.objects.filter(key=token).first() if not token_obj: raise APIException('认证失败') # 校验是否在有效期内 token_created_time = token_obj.created now = datetime.datetime.now() now = now.replace(tzinfo=pytz.timezone('UTC')) delta = now - token_created_time state = delta < datetime.timedelta(weeks=2) if state: # 校验成功,写入缓存中 delta = datetime.timedelta(weeks=2) - delta delta = delta.total_seconds() # 转化为时间戳格式 cache.set(token_obj.key, token_obj.user, min(delta, 3600*24*7)) # 通过min控制缓存时间不超过7天 return token_obj.user, token_obj.key else: raise APIException('认证超时')
from django.conf import settings from api.utils.geetest import GeeTestLib def verify(verify_data, uid=None, extend_params=None): """第三方滑动验证码校验. 选用第三方的验证组件, 根据参数进行校验 根据布尔值辨别是否校验通过 Parameters ---------- verify_data : dict 请求数据 uid: string, default: None 用户UID, 如果存在就免受滑动验证码的限制 extend_params : dict 预留的扩展参数 Returns ------- True OR False """ captcha_config = settings.GEE_TEST if captcha_config.get("verify_status"): status = True if uid in captcha_config.get("not_verify"): return True gt = GeeTestLib(captcha_config["gee_test_access_id"], captcha_config["gee_test_access_key"]) challenge = verify_data.get(gt.FN_CHALLENGE, '') validate = verify_data.get(gt.FN_VALIDATE, '') seccode = verify_data.get(gt.FN_SECCODE, '') # status = request.session.get(gt.GT_STATUS_SESSION_KEY, 1) # user_id = request.session.get("user_id") if status: result = gt.success_validate(challenge, validate, seccode, None) else: result = gt.failback_validate(challenge, validate, seccode) return True if result else False else: return True
# !/usr/bin/env python # -*- coding:utf-8 -*- import sys import random import json import requests import time from hashlib import md5 if sys.version_info >= (3,): xrange = range VERSION = "3.2.0" class GeeTestLib(object): FN_CHALLENGE = "geetest_challenge" FN_VALIDATE = "geetest_validate" FN_SECCODE = "geetest_seccode" GT_STATUS_SESSION_KEY = "gt_server_status" API_URL = "http://api.geetest.com" REGISTER_HANDLER = "/register.php" VALIDATE_HANDLER = "/validate.php" def __init__(self, captcha_id, private_key): self.private_key = private_key self.captcha_id = captcha_id self.sdk_version = VERSION self._response_str = "" def pre_process(self, user_id=None): """ 验证初始化预处理. """ status, challenge = self._register(user_id) self._response_str = self._make_response_format(status, challenge) return status def _register(self, user_id=None): challenge = self._register_challenge(user_id) if len(challenge) == 32: challenge = self._md5_encode("".join([challenge, self.private_key])) return 1, challenge else: return 0, self._make_fail_challenge() def get_response_str(self): return self._response_str def _make_fail_challenge(self): rnd1 = random.randint(0, 99) rnd2 = random.randint(0, 99) md5_str1 = self._md5_encode(str(rnd1)) md5_str2 = self._md5_encode(str(rnd2)) challenge = md5_str1 + md5_str2[0:2] return challenge def _make_response_format(self, success=1, challenge=None): if not challenge: challenge = self._make_fail_challenge() string_format = json.dumps( {'success': success, 'gt': self.captcha_id, 'challenge': challenge}) return string_format def _register_challenge(self, user_id=None): if user_id: register_url = "{api_url}{handler}?gt={captcha_ID}&user_id={user_id}".format( api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id, user_id=user_id) else: register_url = "{api_url}{handler}?gt={captcha_ID}".format( api_url=self.API_URL, handler=self.REGISTER_HANDLER, captcha_ID=self.captcha_id) try: response = requests.get(register_url, timeout=2) if response.status_code == requests.codes.ok: res_string = response.text else: res_string = "" except: res_string = "" return res_string def success_validate(self, challenge, validate, seccode, user_id=None, gt=None, data='', userinfo=''): """ 正常模式的二次验证方式.向geetest server 请求验证结果. """ if not self._check_para(challenge, validate, seccode): return 0 if not self._check_result(challenge, validate): return 0 validate_url = "{api_url}{handler}".format( api_url=self.API_URL, handler=self.VALIDATE_HANDLER) query = { "seccode": seccode, "sdk": ''.join(["python_", self.sdk_version]), "user_id": user_id, "data": data, "timestamp": time.time(), "challenge": challenge, "userinfo": userinfo, "captchaid": gt } backinfo = self._post_values(validate_url, query) if backinfo == self._md5_encode(seccode): return 1 else: return 0 def _post_values(self, apiserver, data): response = requests.post(apiserver, data) return response.text def _check_result(self, origin, validate): encodeStr = self._md5_encode(self.private_key + "geetest" + origin) if validate == encodeStr: return True else: return False def failback_validate(self, challenge, validate, seccode): """ failback模式的二次验证方式.在本地对轨迹进行简单的判断返回验证结果. """ if not self._check_para(challenge, validate, seccode): return 0 validate_str = validate.split('_') encode_ans = validate_str[0] encode_fbii = validate_str[1] encode_igi = validate_str[2] decode_ans = self._decode_response(challenge, encode_ans) decode_fbii = self._decode_response(challenge, encode_fbii) decode_igi = self._decode_response(challenge, encode_igi) validate_result = self._validate_fail_image( decode_ans, decode_fbii, decode_igi) return validate_result def _check_para(self, challenge, validate, seccode): return (bool(challenge.strip()) and bool(validate.strip()) and bool(seccode.strip())) def _validate_fail_image(self, ans, full_bg_index, img_grp_index): thread = 3 full_bg_name = str(self._md5_encode(str(full_bg_index)))[0:10] bg_name = str(self._md5_encode(str(img_grp_index)))[10:20] answer_decode = "" for i in range(0, 9): if i % 2 == 0: answer_decode += full_bg_name[i] elif i % 2 == 1: answer_decode += bg_name[i] x_decode = answer_decode[4:] x_int = int(x_decode, 16) result = x_int % 200 if result < 40: result = 40 if abs(ans - result) < thread: return 1 else: return 0 def _md5_encode(self, values): if type(values) == str: values = values.encode() m = md5(values) return m.hexdigest() def _decode_rand_base(self, challenge): str_base = challenge[32:] i = 0 temp_array = [] for i in xrange(len(str_base)): temp_char = str_base[i] temp_ascii = ord(temp_char) result = temp_ascii - 87 if temp_ascii > 57 else temp_ascii - 48 temp_array.append(result) decode_res = temp_array[0] * 36 + temp_array[1] return decode_res def _decode_response(self, challenge, userresponse): if len(userresponse) > 100: return 0 shuzi = (1, 2, 5, 10, 50) chongfu = set() key = {} count = 0 for i in challenge: if i in chongfu: continue else: value = shuzi[count % 5] chongfu.add(i) count += 1 key.update({i: value}) res = 0 for i in userresponse: res += key.get(i, 0) res = res - self._decode_rand_base(challenge) return res
class CommonException(Exception): def __init__(self, code, msg): self.code = code self.msg = msg
from api import models from rest_framework.filters import BaseFilterBackend class CourseFilter(BaseFilterBackend): """ 课程展示 过滤器 """ def filter_queryset(self, request, queryset, view): extra = {} # request.query_params等同于request.GET category_id = str(request.query_params.get("category_id")) # 如果分类ID不是数字或分类ID传输的为0 if not category_id.isdigit() or category_id == "0": extra = extra else: extra.update({"course_category_id": category_id}) return queryset.filter(**extra)
from django.utils.deprecation import MiddlewareMixin class CorsMiddleWare(MiddlewareMixin): def process_response(self, request, response): if request.method == "OPTIONS": # print("OK123") response["Access-Control-Allow-Methods"] = "GET,POST,DELETE,PUT" response["Access-Control-Allow-Headers"] = "Content-Type,AUTHORIZATION" response["Access-Control-Allow-Origin"] = "*" return response
from django.core.cache import cache import datetime from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from api.models import Token from rest_framework.permissions import BasePermission class LoginUserPermission(BaseAuthentication): def has_permission(self,request,view): if request.user: return True else: return False
from django.core.cache import cache import datetime from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions from api.models import Token from rest_framework.permissions import BasePermission class LoginUserPermission(BaseAuthentication): def has_permission(self,request,view): if request.user: return True else: return False
class BaseResponse(object): def __init__(self): self.code = 1000 self.data = None self.msg = "" @property def dict(self): return self.__dict__ if __name__ == '__main__': res = BaseResponse() res.data = 123 print(res.dict)
from rest_framework import serializers from api import models class CourseCategorySerializer(serializers.ModelSerializer): class Meta: model = models.Course fields = ( "id", "name", ) class CourseSerializer(serializers.ModelSerializer): level = serializers.CharField(source="get_level_display") coursedetail_id = serializers.CharField(source="coursedetail.pk") class Meta: model = models.Course fields = ( 'id', 'name', 'course_img', 'brief', 'level', "coursedetail_id" ) def to_representation(self, instance): data = super(CourseSerializer, self).to_representation(instance) # 购买人数 # data["people_buy"] = instance.order_details.all().count() # 价格套餐列表 price_policies = instance.price_policy.all().order_by("price").only("price") price = getattr(price_policies.first(), "price", 0) if price_policies and price == 0: is_free = True price = "免费" origin_price = "原价¥{}".format(price_policies.last().price) else: is_free = False price = "¥{}".format(price) origin_price = None # 是否免费 data["is_free"] = is_free # 展示价格 data["price"] = price # 原价 data["origin_price"] = origin_price return data class CourseDetailSerializer(serializers.ModelSerializer): name=serializers.CharField(source="course.name") prices = serializers.SerializerMethodField() brief = serializers.StringRelatedField(source='course.brief') study_all_time = serializers.StringRelatedField(source='hours') level = serializers.CharField(source='course.get_level_display') teachers_info = serializers.SerializerMethodField() is_online = serializers.SerializerMethodField() recommend_coursesinfo = serializers.SerializerMethodField() # learnnumber = serializers.SerializerMethodField() # OftenAskedQuestion = serializers.SerializerMethodField() class Meta: model = models.CourseDetail fields="__all__" def get_prices(self, obj): return PricePolicySerializer( obj.course.price_policy.all(), many=True, context=self.context ).data def get_study_all_time(self, obj): return "30小时" def get_recommend_coursesinfo(self, obj): courses = RecommendCourseSerializer(obj.recommend_courses.all(), many=True) return courses.data def get_teachers_info(self, obj): teachers = TeacherSerializer(obj.teachers.all(), many=True) return teachers.data def get_is_online(self, obj): if obj.course.status == 0: return True elif obj.course.status == 2: return False else: return '' # def get_learnnumber(self, obj): # return obj.course.order_details.all().count() # def get_OftenAskedQuestion(self, obj): # question_queryset = models.OftenAskedQuestion.objects.filter(content_type__model='Course', # object_id=obj.course.id) # serializer = OftenAskedQuestionSerializer(question_queryset, many=True) # return serializer.data # class RecommendCourseSerializer(serializers.ModelSerializer): course_id = serializers.CharField(source="pk") course_name = serializers.CharField(source="name") class Meta: model = models.Course fields = ('course_id', 'course_name',) class PricePolicySerializer(serializers.ModelSerializer): valid_period_name = serializers.StringRelatedField(source="get_valid_period_display") class Meta: model = models.PricePolicy fields = ('id', 'valid_period', 'valid_period_name', 'price',) class CourseChapterSerializer(serializers.ModelSerializer): chapter_name = serializers.SerializerMethodField() chapter_symbol = serializers.SerializerMethodField() class Meta: model = models.CourseChapter fields = ( 'id', 'chapter_name', 'chapter_symbol', ) def get_chapter_name(self, instance): return '第%s章·%s' % (instance.chapter, instance.name) def get_chapter_symbol(self, instance): return "chapter_%s_%s" % (self.context.get('enrolled_course_id', 1), instance.id) def to_representation(self, instance): data = super(CourseChapterSerializer, self).to_representation(instance) queryset = instance.coursesections.all().order_by("order") # 获取章节对应的课时数量 data["section_of_count"] = queryset.count() data["free_trail"] = queryset.filter(free_trail=True).exists() data["coursesections"] = serializers.SectionSerializer( queryset, many=True, read_only=True, context=self.context ).data return data class OftenAskedQuestionSerializer(serializers.ModelSerializer): question_tittle = serializers.SerializerMethodField() question_answer = serializers.SerializerMethodField() class Meta: model = models.OftenAskedQuestion fields = ('question_tittle', 'question_answer') def get_question_tittle(self, obj): return obj.question def get_question_answer(self, obj): return obj.answer class TeacherSerializer(serializers.ModelSerializer): teacher_id = serializers.CharField(source="pk") teacher_name = serializers.CharField(source="name") teacher_brief = serializers.CharField(source="brief") teacher_image = serializers.CharField(source="image") class Meta: model = models.Teacher fields = ('teacher_id', 'teacher_name', 'title', 'signature', 'teacher_image', 'teacher_brief') class UserInfoSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__"
from django.contrib import auth import uuid import datetime from rest_framework.views import APIView from rest_framework.response import Response from api.models import Token from api.utils.captcha_verify import verify class LoginView(APIView): def post(self, request): res = {'user': None, 'msg': None} is_valid = verify(request.data) try: if is_valid: user = request.data.get('user') pwd = request.data.get('pwd') user_obj = auth.authenticate(username=user, password=pwd) if user_obj: random_str = str(uuid.uuid4()) Token.objects.update_or_create(user=user_obj, defaults={"key": random_str, 'created': datetime.datetime.now()}) res['user'] = user_obj.username res['token'] = random_str else: res['msg'] = 'user or pwd error' else: res['msg'] = '验证码异常!' except Exception as e: res['msg'] = str(e) return Response(res)
from rest_framework.views import APIView from api.utils.auth import LoginAuth from api.models import Token from rest_framework.response import Response from api.utils.permission import LoginUserPermission class LogoutView(APIView): authentication_classes = [LoginAuth] permission_classes = [LoginUserPermission] def delete(self, request): user = request.user.pk Token.objects.filter(user=user).delete() return Response({"code": 1000})
from rest_framework.views import APIView from api.utils.geetest import GeeTestLib from django.conf import settings import json from rest_framework.response import Response class CaptchaView(APIView): def get(self, request): gt = GeeTestLib(settings.GEE_TEST["gee_test_access_id"], settings.GEE_TEST["gee_test_access_key"]) gt.pre_process() # 设置 geetest session, 用于是否启用滑动验证码向 geetest 发起远程验证, 如果取不到的话只是对本地轨迹进行校验 # self.request.session[gt.GT_STATUS_SESSION_KEY] = status # request.session["user_id"] = user_id response_str = gt.get_response_str() response_str = json.loads(response_str) return Response({"error_no": 0, "data": response_str})
from rest_framework.viewsets import ModelViewSet from rest_framework.response import Response from api.models import * from api.utils.serializer import ( CourseSerializer, CourseDetailSerializer, CourseCategorySerializer ) from api.utils.auth import LoginAuth from api.utils.filter import CourseFilter class CourseView(ModelViewSet): # authentication_classes = [LoginAuth] queryset = Course.objects.all() serializer_class = CourseSerializer filter_backends = [CourseFilter, ] def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) page = self.paginate_queryset(queryset) if page is not None: serializer = self.get_serializer(page, many=True) return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True) return Response({"code": 0, "data": serializer.data}) class CourseDetailView(ModelViewSet): # authentication_classes = [LoginAuth] queryset = CourseDetail.objects.all() serializer_class = CourseDetailSerializer class CourseCategoryView(ModelViewSet): queryset = CourseCategory.objects.all() serializer_class = CourseCategorySerializer def list(self, request, *args, **kwargs): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) return Response({"error_no": 0, "data": serializer.data})
from rest_framework.views import APIView from rest_framework.response import Response from django.conf import settings from django.core.exceptions import ObjectDoesNotExist import json import redis from api.utils.auth import LoginAuth from api.utils.exceptions import CommonException from api.utils.response import BaseResponse from api.models import * cache = redis.Redis(decode_responses=True) class ShoppingCartView(APIView): authentication_classes = [LoginAuth] def post(self, request): """ 状态码: 1000: 成功 1001: 课程不存在 模拟请求数据: { "course_id":1, "price_policy_id":2 } :param request: :return: """ # 1 获取请求数据 course_id = request.data.get('course_id') price_policy_id = request.data.get('price_policy_id') user_id = request.user.pk res = BaseResponse() try: # 2 校验数据 # 2.1 校验课程是否存在 course_obj = Course.objects.get(pk=course_id) # 2.2 校验价格策略是否合法 price_policy_dict = {} for price_policy in course_obj.price_policy.all(): price_policy_dict[price_policy.pk] = { "pk": price_policy.pk, "valid_period": price_policy.valid_period, "valid_period_text": price_policy.get_valid_period_display(), "price": price_policy.price, "default": price_policy_id == price_policy.pk } # print("price_policy_id:", price_policy_id) # print("price_policy_dict:", price_policy_dict) if price_policy_id not in price_policy_dict: raise CommonException(1002, "价格策略错误!") pp = PricePolicy.objects.get(pk=price_policy_id) # 3 写入redis shopping_car_key = settings.SHOPPINGCAR_KEY % (user_id, course_id) shopping_car_val = { "title": course_obj.name, "img": course_obj.course_img, "relate_price_policy": price_policy_dict, "choose_price_policy_id": price_policy_id, "price": pp.price, "valid_period": pp.valid_period, "valid_period_text": pp.get_valid_period_display() } cache.set(shopping_car_key, json.dumps(shopping_car_val)) res.data = "加入购物车成功!" """ REDIS={ # 方案1 user_id:{ shoppingcar:{ "course_id":{ "title":"....", "img":"...." }, "course_id":{ "title":"....", "img":"...." } "course_id":{ "title":"....", "img":"...." } } } } # 方案2 REDIS={ shoppingcar_1_1:{ "title":"....", "img":"...." } shoppingcar_1_2:{ "title":"....", "img":"...." } } """ except CommonException as e: res.code = e.code res.msg = e.msg except ObjectDoesNotExist as e: res.code = 1001 res.msg = "课程不存在" return Response(res.dict) def get(self, request): res = BaseResponse() try: # 1.获取user_id user_id = request.user.pk # 2.获取所有与当前user_id有关的key shopping_car_key = settings.SHOPPINGCAR_KEY % (user_id, "*") shopping_car_key_list = cache.scan_iter(shopping_car_key) # print("shopping_car_key:", shopping_car_key) # print("redis:", cache.keys()) # 循环从redis中获取当前用户加入购物车中的所有课程 shopping_car_list = [] for key in shopping_car_key_list: course_info = json.loads(cache.get(key)) shopping_car_list.append(course_info) res.data = {"shopping_car_list": shopping_car_list, "total": len(shopping_car_list) } except Exception: res.code = 1033 res.error = "获取购物车失败!" return Response(res.dict) def put(self, request): res = BaseResponse() try: # 1 获取前端传过来的course_id 以及price_policy_id course_id = request.data.get("course_id", "") price_policy_id = request.data.get("price_policy_id", "") user_id = request.user.id # 2 校验数据的合法性 # 2.1 校验course_id是否合法 shopping_car_key = settings.SHOPPING_CAR_KEY % (user_id, course_id) if not cache.exists(shopping_car_key): res.code = 1035 res.error = "课程不存在" return Response(res.dict) # 2.2 判断价格策略是否合法 course_info = cache.hgetall(shopping_car_key) price_policy_dict = json.loads(course_info["price_policy_dict"]) if str(price_policy_id) not in price_policy_dict: res.code = 1036 res.error = "所选的价格策略不存在" return Response(res.dict) # 3 修改redis中的default_policy_id course_info["default_policy_id"] = price_policy_id # 4 修改信息后写入redis cache.hmset(shopping_car_key, course_info) res.data = "更新成功" except Exception as e: res.code = 1034 res.error = "更新价格策略失败" return Response(res.dict) def delete(self, request): res = BaseResponse() try: # 获取前端传过来的course_id course_id = request.data.get("course_id", "") user_id = request.user.id # 判断课程id是否合法 shopping_car_key = settings.SHOPPING_CAR_KEY % (user_id, course_id) if not cache.exists(shopping_car_key): res.code = 1039 res.error = "删除的课程不存在" return Response(res.dict) # 删除redis中的数据 cache.delete(shopping_car_key) res.data = "删除成功" except Exception as e: res.code = 1037 res.error = "删除失败" return Response(res.dict) ''' 1 post接口构建数据结构: { "id": 2, "default_price_period": 14, "relate_price_policy": { "1": { "valid_period": 7, "valid_period_text": "1周", "default": false, "prcie": 100.0 }, "2": { "valid_period": 14, "valid_period_text": "2周", "default": true, "prcie": 200.0 }, "3": { "valid_period": 30, "valid_period_text": "1个月", "default": false, "prcie": 300.0 } }, "name": "Django框架学习", "course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", "default_price": 200.0 } 2 get接口查询数据结构: { "data": { "total": 2, "shopping_car_list": [ { "id": 2, "default_price_period": 14, "relate_price_policy": { "1": { "valid_period": 7, "valid_period_text": "1周", "default": false, "prcie": 100 }, "2": { "valid_period": 14, "valid_period_text": "2周", "default": true, "prcie": 200 }, "3": { "valid_period": 30, "valid_period_text": "1个月", "default": false, "prcie": 300 } }, "name": "Django框架学习", "course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", "default_price": 200 }, { "id": 4, "default_price_period": 30, "relate_price_policy": { "4": { "valid_period": 30, "valid_period_text": "1个月", "default": true, "prcie": 1000 }, "5": { "valid_period": 60, "valid_period_text": "2个月", "default": false, "prcie": 1500 } }, "name": "Linux系统基础5周入门精讲", "course_img": "https://luffycity.com/static/frontend/course/12/Linux5周入门_1509589530.6144893.png", "default_price": 1000 } ] }, "code": 1000, "msg": "" } '''
from rest_framework.views import APIView from rest_framework.response import Response from django.conf import settings from django.core.exceptions import ObjectDoesNotExist import json import redis from api.utils.auth import LoginAuth from api.utils.exceptions import CommonException from api.utils.response import BaseResponse from api.models import * cache = redis.Redis(decode_responses=True) class AccountView(APIView): authentication_classes = [LoginAuth] # 获取优惠券字典 def get_coupons_dict(self, request, course_id=None): import datetime now = datetime.datetime.now() coupon_record_list = CouponRecord.objects.filter( account=request.user, coupon__content_type=8, coupon__object_id=course_id, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now, ) coupons = {} for coupon_record in coupon_record_list: coupons[coupon_record.pk] = { "name": coupon_record.coupon.name, "coupon_type": coupon_record.coupon.coupon_type, "money_equivalent_value": coupon_record.coupon.money_equivalent_value, "off_percent": coupon_record.coupon.off_percent, "minimum_consume": coupon_record.coupon.minimum_consume, } return coupons def cal_coupon_price(self, price, coupon_info): coupon_type = coupon_info["coupon_type"] money_equivalent_value = coupon_info.get("money_equivalent_value") off_percent = coupon_info.get("off_percent") minimum_consume = coupon_info.get("minimum_consume") rebate_price = 0 if coupon_type == "立减券": rebate_price = price - money_equivalent_value if rebate_price <= 0: rebate_price = 0 elif coupon_type == "满减券": if minimum_consume > price: raise CommonException(3000, "优惠券未达到最低消费") else: rebate_price = price - money_equivalent_value elif coupon_type == "折扣券": rebate_price = price * off_percent/100 return rebate_price def post(self, request): """ 状态码: 1000: 成功 1001: 课程不存在 模拟请求数据: { "course_id_list":[1,2] } :param request: :return: """ # 1 获取请求数据 course_id_list = request.data.get('course_id_list') user_id = request.user.pk res = BaseResponse() # 清空操作 del_list = cache.keys(settings.ACCOUNT_KEY % (user_id, "*")) if del_list: cache.delete(*del_list) price_list = [] try: for course_id in course_id_list: # 结算情况: 1 直接购买 2 购物车结算 account_key = settings.ACCOUNT_KEY % (user_id, course_id) account_val = {} # 获取课程基本信息 shopping_car_key = settings.SHOPPINGCAR_KEY % (user_id, course_id) # course_obj = Course.objects.get(pk=course_id) course_info = json.loads(cache.get(shopping_car_key)) if not cache.exists(shopping_car_key): raise CommonException(1040, "购物车不存在该课程") # 添加到结算字典中 account_val["course_info"] = course_info # 课程价格加入到价格列表中 price_list.append(float(course_info["price"])) # 获取优惠券信息:查询当前用户的当前课程的有效的未使用的优惠券 coupons = self.get_coupons_dict(request, course_id) # 将优惠券字典添加到结算字典中 account_val["coupons"] = coupons cache.set(account_key, json.dumps(account_val)) # 获取通用优惠券 global_coupons = self.get_coupons_dict(request) cache.set("global_coupons_%s" % user_id, json.dumps(global_coupons)) cache.set("total_price", sum(price_list)) except CommonException as e: res.code = e.code res.msg = e.msg except ObjectDoesNotExist: res.code = 1001 res.msg = "课程不存在" return Response(res.dict) def get(self, request, *args, **kwargs): res = BaseResponse() try: user_id = request.user.pk account_key = settings.ACCOUNT_KEY % (user_id, "*") account_key_list = cache.scan_iter(account_key) account_course_list = [] for key in account_key_list: course = json.loads(cache.get(key)) temp = {} for key, val in course["course_info"].items(): temp[key] = val coupon_list = [] for key, val in course["coupons"].items(): val["pk"] = key coupon_list.append(val) temp["coupon_list"] = coupon_list account_course_list.append(temp) global_coupons_dict = json.loads(cache.get("global_coupons_%s" % user_id)) total_price = cache.get("total_price") global_coupons = [] for key, val in global_coupons_dict.items(): global_coupons.append(val) res.data = { "account_course_list": account_course_list, "total": len(account_course_list), "global_coupons": global_coupons, "total_price": total_price } except Exception: res.code = 1033 res.error = "获取购物车失败!" return Response(res.dict) def put(self, request, *args, **kwargs): """ choose_coupons:{ choose_coupons: {"1": "2", "2": "3", "global_coupon_id": "5"}, is_beli: true } :param request: :param args: :param kwargs: :return: """ res = BaseResponse() try: choose_coupons = request.data.get("choose_coupons") is_beli = request.data.get("is_beli") user_id = request.user.pk # 获取结算课程表 cal_price = {} data = self.get(request).data.get("data") account_course_list = data.get("account_course_list") account_course_info = {} for account_course in account_course_list: temp = { "coupons":{}, "default_price": account_course["price"] } account_course_info[account_course["id"]] = temp for item in account_course["coupon_list"]: temp["coupons"][item["pk"]] = item price_list = [] total_price = 0 for key, val in account_course_info.items(): if str(key) not in choose_coupons: price_list.append(val["default_price"]) cal_price[key] = val["default_price"] else: coupon_info = val.get("coupons").get(str(choose_coupons[str(key)])) rebate_price = self.cal_coupon_price(val["default_price"], coupon_info) price_list.append(rebate_price) cal_price[key] = rebate_price total_price = sum(price_list) # 计算通用优惠券的价格 global_coupon_id = choose_coupons.get("global_coupon_id") if global_coupon_id: global_coupons = data.get("global_coupons") global_coupon_dict = {} for item in global_coupons: global_coupon_dict[item["pk"]] = item total_price = self.cal_coupon_price(total_price, global_coupon_dict[global_coupon_id]) # 计算贝里 if json.loads(is_beli): total_price = total_price - request.user.beli/10 if total_price < 0: total_price = 0 cal_price["total_price"] = total_price res.data = cal_price except Exception as e: res.code = 500 res.msg = "结算错误!" + str(e) return Response(res.dict) ''' 1 结算中心post添加接口数据结构: { "course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", "coupon_list": [{ "name": "清明节活动", "minimum_consume": 0, "money_equivalent_value": 0.0, "off_percent": 80, "pk": 3, "coupon_type": "折扣券" }], "relate_price_policy": { "1": { "valid_period": 7, "valid_period_text": "1周", "prcie": 100.0, "default": true }, "2": { "valid_period": 14, "valid_period_text": "2周", "prcie": 200.0, "default": false }, "3": { "valid_period": 30, "valid_period_text": "1个月", "prcie": 300.0, "default": false } }, "name": "Django框架学习", "default_price": 100.0, "id": 2, "default_price_period": 7, "default_price_policy_id": 1 } 2 结算中心get查询接口: { "data": { "total": 1, "global_coupons": [ { "name": "国庆节活动通用券", "coupon_type": "满减券", "minimum_consume": 100, "money_equivalent_value": 50, "off_percent": null, "pk": 2 } ], "total_price": "100.0", "account_course_list": [ { "course_img": "https://luffycity.com/static/frontend/course/3/Django框架学习_1509095212.759272.png", "name": "Django框架学习", "relate_price_policy": { "1": { "valid_period": 7, "prcie": 100, "valid_period_text": "1周", "default": true }, "2": { "valid_period": 14, "prcie": 200, "valid_period_text": "2周", "default": false }, "3": { "valid_period": 30, "prcie": 300, "valid_period_text": "1个月", "default": false } }, "coupon_list": [ { "name": "清明节活动", "coupon_type": "折扣券", "minimum_consume": 0, "money_equivalent_value": 0, "off_percent": 80, "pk": 3 } ], "default_price": 100, "id": 2, "default_price_period": 7, "default_price_policy_id": 1 } ] }, "code": 1000, "msg": "" } '''
from rest_framework.views import APIView from rest_framework.response import Response from api.utils.auth import LoginAuth from django.core.exceptions import ObjectDoesNotExist from api.utils.response import BaseResponse from api.models import Course, CouponRecord, Coupon, PricePolicy, Order, OrderDetail from api.utils.exceptions import CommonException import datetime import uuid import time import json from api.models import Order, OrderDetail class OrderView(APIView): authentication_classes = [LoginAuth] def get(self, request, *args, **kwargs): res = BaseResponse() order_list = Order.objects.filter(account=request.user).order_by("-date") data = [] for order in order_list: data.append({ "order_number": order.order_number, "date": order.date.strftime("%Y-%m-%d %H:%M:%S"), "status": order.get_status_display(), "actual_amount": order.actual_amount, "orderdetail_list": [{ "original_price": obj.original_price, "price": obj.price, "course_name": obj.content_object.name, "valid_period_display": obj.valid_period_display, } for obj in order.orderdetail_set.all()] }) print("data", data) res.data = data return Response(res.dict)
import json import datetime import random from django.core.exceptions import ObjectDoesNotExist from rest_framework.views import APIView from rest_framework.response import Response import redis from django.conf import settings from api.utils.auth import LoginAuth from api.utils.response import BaseResponse from api.utils.exceptions import CommonException from api.utils.ali.api import ali_api from api.models import * cache = redis.Redis(decode_responses=True) class PaymentView(APIView): authentication_classes = [LoginAuth] def get_pay_url(self, request, order_number, final_price): # 4 调用支付宝支付接口(二维码页面) if request.META["HTTP_USER_AGENT"]: pay_api = ali_api.pay.pc elif request == "APP": pay_api = ali_api.pay.app else: pay_api = ali_api.pay.wap pay_url = pay_api.direct( subject="路飞学城", # 商品简单描述 out_trade_no=order_number, # 商户订单号 total_amount=final_price, # 交易金额(单位: 元 保留俩位小数) ) return pay_url def cal_coupon_price(self, price, coupon_record_obj): coupon_type = coupon_record_obj.coupon.coupon_type money_equivalent_value = coupon_record_obj.coupon.money_equivalent_value off_percent = coupon_record_obj.coupon.off_percent minimum_consume = coupon_record_obj.coupon.minimum_consume rebate_price = 0 if coupon_type == 0: # 立减券 rebate_price = price - money_equivalent_value if rebate_price <= 0: rebate_price = 0 elif coupon_type == 1: # 满减券 if minimum_consume > price: raise CommonException(1007, "优惠券未达到最低消费") else: rebate_price = price - money_equivalent_value elif coupon_type == 2: rebate_price = price * off_percent / 100 print("原价格", price) print("计算优惠券价格", coupon_record_obj.coupon.get_coupon_type_display(), rebate_price) return rebate_price def get_order_num(self): now = datetime.datetime.now() orderType = "1" dateStr4yyyyMMddHHmmss = "{0}{1}{2}".format(now.year, now.month, now.day) rand = str(random.randint(1000, 9999)) s = orderType+dateStr4yyyyMMddHHmmss+rand return s def post(self, request): """ 模拟前端数据: { "is_beli":"true", "course_list"=[ { "course_id":1 "default_price_policy_id":1, "coupon_record_id":2 }, { "course_id":2 "default_price_policy_id":4, "coupon_record_id":6 } ], "global_coupon_id":3, "pay_money":298 } :param request: :return: """ # 1 获取数据 course_list = request.data.get("course_list") global_coupon_id = request.data.get("global_coupon_id") is_beli = request.data.get("is_beli") pay_money = request.data.get("pay_money") user_id = request.user.pk res = BaseResponse() # 2 校验数据 # 2.1 校验每一个课程 try: price_list = [] for course in course_list: course_id = course.get("course_id") default_price_policy_id = course.get("default_price_policy_id") coupon_record_id = course.get("coupon_record_id") # 2.1.1 校验课程是否存在 course_obj = Course.objects.filter(pk=course_id).first() if not course_obj: raise CommonException(1001, "课程不存在") # 2.1.2 校验价格策略 if default_price_policy_id not in [obj.pk for obj in course_obj.price_policy.all()]: raise CommonException(1002, "价格策略错误!") pp = PricePolicy.objects.get(pk=default_price_policy_id) # 2.1.3 校验课程优惠券 if coupon_record_id: now = datetime.datetime.now() coupon_record_obj = CouponRecord.objects.filter( pk=coupon_record_id, account=request.user, coupon__content_type=8, coupon__object_id=course_id, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now ).first() if not coupon_record_obj: raise CommonException(1003, "课程优惠券异常!") # 计算折后价格 rebate_money = self.cal_coupon_price(pp.price, coupon_record_obj) price_list.append(rebate_money) else: price_list.append(pp.price) print("price_list", price_list) final_price = sum(price_list) # 2.2 校验通用优惠券 if global_coupon_id: # 2.2.1 校验通用优惠券合法 now = datetime.datetime.now() g_coupon_record_obj = CouponRecord.objects.filter( pk=global_coupon_id, account=request.user, coupon__content_type=8, coupon__object_id=None, status=0, coupon__valid_begin_date__lte=now, coupon__valid_end_date__gte=now ).first() if not g_coupon_record_obj: raise CommonException(1004, "通用优惠券异常!") # 2.2.2 计算折后价格 final_price = self.cal_coupon_price(final_price, g_coupon_record_obj) # 2.3 校验贝里 if json.loads(is_beli): final_price = final_price - request.user.beli / 10 cost_beli_num = request.user.beli if final_price < 0: final_price = 0 cost_beli_num = final_price * 10 # 2.4 校验最终价格 print("final_price", final_price) if final_price != pay_money: raise CommonException(1005, "支付价格异常!") # 3 生成订单记录 order_number = self.get_order_num() order_obj = Order.objects.create( payment_type=1, order_number=order_number, account=request.user, status=1, order_type=1, actual_amount=pay_money, ) print("course_list", course_list) for course_item in course_list: OrderDetail.objects.create( order=order_obj, content_type_id=14, object_id=course_item.get("course_id"), # original_price=course_item.get("original_price"), # price=course_item.get("rebate_price") or course_item.get("original_price"), # valid_period=course_item.get("valid_period"), # valid_period_display=course_item.get("valid_period_display"), ) request.user.beli = request.user.beli - cost_beli_num request.user.save() cache.set(order_number + "|" + str(cost_beli_num), "", 20) account_key = settings.ACCOUNT_KEY % (user_id, "*") cache.delete(*cache.keys(account_key)) # 生成支付的url res.data = self.get_pay_url(request, order_number, final_price) except ObjectDoesNotExist: res.code = 1001 res.msg = "课程不存在!" except CommonException as e: res.code = e.code res.msg = e.msg return Response(res.dict)
from rest_framework.views import APIView from api.utils.ali.api import ali_api from api.utils.ali.tools import verify_signature from rest_framework.response import Response from api.models import Order import datetime,time from django.shortcuts import HttpResponse,redirect from api.utils.response import BaseResponse class AlipayTradeView(APIView): def get(self, request, *args, **kwargs): try: processed_dict = {} for key, value in self.request.query_params.items(): processed_dict[key] = value # 校验签名 verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key) if not verify_result: return HttpResponse("sign is invalid") out_trade_no = processed_dict.get("out_trade_no") # 商户订单号 redirect_to = "{0}?order_num={1}".format("http://47.94.172.250:8804/order/pay_success", out_trade_no) return redirect(redirect_to) except Exception as e: return HttpResponse("fail!") def post(self, request): """ 处理支付宝的notify_url 支付宝对应交易的四种状态: 1,WAIT_BUYER_PAY 交易创建,等待买家付款 2,TRADE_CLOSED 未付款交易超时关闭,或支付完成后全额退款 3,TRADE_SUCCESS 交易支付成功 4,TRADE_FINISHED 交易结束,不可退款 如果支付成功, 将要处理的事件: 1, 订单状态更改为交易完成(变更状态后, 通过信号进行如下操作) 2, 如果使用优惠券, 支付成功将要把优惠券的状态更改为已使用 3, 如果使用余额, 将要进行扣除 """ processed_dict = {} for key, value in self.request.data.items(): processed_dict[key] = value # 校验签名 verify_result = verify_signature(processed_dict, ali_api.pay.ali_public_key) if not verify_result: return Response("fail") order_sn = processed_dict.get("out_trade_no", "") # 商户网站唯一订单号 trade_no = processed_dict.get("trade_no", "") # 该交易在支付宝系统中的交易流水号。最长64位 trade_status = processed_dict.get("trade_status", "") # 支付宝系统的交易状态 # 支付成功 # 为放止支付宝重复请求, 获取到订单号并查询该订单的状态是否为交易完成, 如果交易完成, 即直接返回成功信号 # 为该用户创建报名课程, 创建报名时间及结束时间 # 区分LuffyX课程 和 付费课程 # 当前策略LuffyX课程是不能加入购物车的 if trade_status == "TRADE_SUCCESS": gmt_payment = processed_dict.get("gmt_payment") # 买家付款时间 格式 yyyy-MM-dd HH:mm:ss passback_params = processed_dict.get("passback_params", "{}") # 公共回传参数 # 修改订单状态 save_status = self.change_order_status(order_sn, trade_no, gmt_payment, "alipay", passback_params) if save_status is True: return Response("success") return Response("fail") def change_order_status(order_num, payment_number, gmt_payment, trade_type, extra_params): """交易成功修改订单相关的状态 Parameters ---------- order_num : string 订单号 payment_number : string or None 第三方订单号 gmt_payment : string 交易时间(要根据不同的交易方式格式化交易时间) trade_type: string 交易方式 extra_params: string json 交易回传参数 Returns ------- bool """ try: exist_order = Order.objects.get(order_number=order_num) pay_time = datetime.datetime.strptime(gmt_payment, "%Y-%m-%d %H:%M:%S") if exist_order.status == 0: return True # 变更订单状态 exist_order.payment_number = payment_number exist_order.status = 0 exist_order.pay_time = pay_time exist_order.save(update_fields=("payment_number", "status", "pay_time", ),) except Exception as e: pass return True
1.公钥与私钥原理
公钥加密 保护数据;
私钥加密 数字签名;
首先要明白公钥和私钥只是一个相对概念,就是说我们不能单纯的去称呼一对密钥中的一个为公钥,另一个为私钥,它们的公私性总是相对于生成者来说的。一对密钥生成后,保存在生成者手里的就是生成者私钥,生成者发布出去的就是生成者公钥,可以看到我们在称呼它们的时候前面带上了生成者,这样可以便于我们理解,避免混淆概念。一对儿公私钥,不能由其中的一个导出另一个。
可以暂时这么理解:
一对密钥在刚生成的时候是没有公私之分的,但是生成者会保留一个在自己手里,发布一个给别人用,正是这个“保留与发布”的操作才使得这对密钥有了公私之分,那么对于生成者来说,保留在自己手里的密钥就被称作生成者私钥,发布给别人用的那个密钥就被称作生成者公钥,注意这里的称呼带上了生成者,就是为了表明一对密钥的公私性总是相对于它们的生成者来说的。(实际中私钥和公钥在生成的时候已经具备了公私性,因为公钥和私钥是不同的生成机理,但这样理解也是没有错的,有助于帮助我们理清后面的关系)
比如:
我们使用支付宝SDK的时候,我们商户端会生成一对密钥A和B,支付宝也会生成一对密钥C和D。
那么如果我们商户端保存了A,而把B发布给了支付宝,A就被称作商户端私钥,B就被称作商户端公钥。(注意称呼公私钥的时候带上生成者的名字,这样可以便于我们理解,避免混淆概念)
当然我们也可以保存B,而把A发布给支付宝,这样B就被称作商户端私钥,A就被称作商户端公钥。(实际中不会这么做,因为公私钥已经提前确定好了,它们的生成机理不同但这样理解也是没有错的,有助于帮助我们理清后面的关系)
同样,假设支付宝保存了C,而把D发布给了我们,那么C就被称作支付宝私钥,D就被称作支付宝公钥,反之同理
1)鲍勃有两把钥匙,一把是公钥,另一把是私钥
2)鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。
3)苏珊要给鲍勃写一封保密的信。她写完后用鲍勃的公钥加密,就可以达到保密的效果。
4)鲍勃收信后,用私钥解密,就看到了信件内容。这里要强调的是,只要鲍勃的私钥不泄露,这封信就是安全的,即使落在别人手里,也无法解密。
5)鲍勃给苏珊回信,决定采用"数字签名"。他写完后先用Hash函数,生成信件的摘要(digest)。
6)然后,鲍勃使用私钥,对这个摘要加密,生成"数字签名"(signature)。
7)鲍勃将这个签名,附在信件下面,一起发给苏珊。
8)苏珊收信后,取下数字签名,用鲍勃的公钥解密,得到信件的摘要。由此证明,这封信确实是鲍勃发出的。
9)苏珊再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
10)复杂的情况出现了。道格想欺骗苏珊,他偷偷使用了苏珊的电脑,用自己的公钥换走了鲍勃的公钥。此时,苏珊实际拥有的是道格的公钥,但是还以为这是鲍勃的公钥。因此,道格就可以冒充鲍勃,用自己的私钥做成"数字签名",写信给苏珊,让苏珊用假的鲍勃公钥进行解密。
11)后来,苏珊感觉不对劲,发现自己无法确定公钥是否真的属于鲍勃。她想到了一个办法,要求鲍勃去找"证书中心"(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对鲍勃的公钥和一些相关信息一起加密,生成"数字证书"(Digital Certificate)。
12)鲍勃拿到数字证书以后,就可以放心了。以后再给苏珊写信,只要在签名的同时,再附上数字证书就行了。
13)苏珊收信后,用CA的公钥解开数字证书,就可以拿到鲍勃真实的公钥了,然后就能证明"数字签名"是否真的是鲍勃签的。
2.HTTPS详解
HTTP协议的网站容易被篡改和劫持,如一些不良的运营商会通过代理服务器在你的页面中植入广告等。
因此很多网站选择使用HTTPS协议。HTTPS协议通过TLS层和证书机制提供了内容加密,身份认证,数据完整性三大功能。
1)下面,我们看一个应用"数字证书"的实例:https协议。这个协议主要用于网页加密。
2)首先,客户端向服务器发出加密请求。
3)服务器用自己的私钥加密网页以后,连同本身的数字证书,一起发送给客户端。
4)客户端(浏览器)的"证书管理器",有"受信任的根证书颁发机构"列表。客户端会根据这张列表,查看解开数字证书的公钥是否在列表之内。
5)如果数字证书记载的网址,与你正在浏览的网址不一致,就说明这张证书可能被冒用,浏览器会发出警告。
6)如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告