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),
]
urls.py
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('支付成功')
app01/views.py
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)
utils/pay.py
<!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>
templates/page1.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-----
keys/app_private_2048.txt
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB
-----END PUBLIC KEY-----
keys/alipay_public_2048.txt

 

支付接口 示例:

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()),

]
urls.py
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
models.py
-----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-----
keys/app_private_2048.txt
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB
-----END PUBLIC KEY-----
keys/alipay_public_2048.txt
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()
                ]
utils/serializer.py
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('认证超时')
utils/auth.py
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)
utils/response.py
class CommonException(Exception):
    def __init__(self, code, msg):
        self.code = code
        self.msg = msg
utils/exceptions.py
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)
utills/pay.py
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)
views/login.py
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
views/course.py
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": ""

"""
views/shoppingcart.py
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
                        }
                    }
"""
views/account.py
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)
views/payment.py
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": {
        }
    }
}
settings

(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-----
keys/app_private_2048.txt
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB
-----END PUBLIC KEY-----
keys/alipay_public_2048.txt

(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),

]
urls.py(准备好支付成功后的trade的url)
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)
views/payment.py
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
views/trade.py

封装版支付接口 示例:

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": {
        }
    }
}
settings.py
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),

]
urls.py
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwQ++By1wKadS5nz870VGdtxawS0RmikCTKqboUdhOtMDXt+drcc8miY+rXLi2BqGwjaQYic6Wx9LCWIwDDS/QGweH0a+4EATKBLwa4wyDdquyB3aNM0GFV3mR8oIWJZ3RUzJCXr8l8QNIV8IKNpcxRV05X82ibt1N3gHuKrtZObyfyMA4IGthABslJ6WCjqHsNXk0/cSpB9Z73AK3YlmCehMSEmSqZyNZt4F4JgcFuhUj+bo89Sjys2AQ6DMY/szdlDazzB5SzV5TnW9PBEfuNyPbI6wKwLPysiU8cHkNRILw9a2FsGyfo57YATR2z4EvXFD9lcOlJ/C5KofP7Yy2wIDAQAB
-----END PUBLIC KEY-----
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-----
keys/app_private_2048.txt
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
models.py
博客文件中下载
utils/ali/...
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('认证超时')
utils/auth.py
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
utils/captcha_verify.py
# !/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
utils/geetest.py
class CommonException(Exception):
    def __init__(self, code, msg):
        self.code = code
        self.msg = msg
utils/exceptions.py
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)
utils/filter.py
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
utils/middleware.py
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
utils/permission.py
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
utils/permission.py
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)
utils/response.py
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__"
utils/serializer.py
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)
views/login.py
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})
views/logout.py
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})
views/captcha.py
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})
views/course.py
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": ""
}

'''
views/shoppingcart.py
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": ""
}


'''
views/account.py
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)
views/order.py
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)
views/payment.py
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
views/trade.py

 

 

 

1.公钥与私钥原理

公钥加密 保护数据;
私钥加密 数字签名;



      首先要明白公钥和私钥只是一个相对概念,就是说我们不能单纯的去称呼一对密钥中的一个为公钥,另一个为私钥,它们的公私性总是相对于生成者来说的。一对密钥生成后,保存在生成者手里的就是生成者私钥,生成者发布出去的就是生成者公钥,可以看到我们在称呼它们的时候前面带上了生成者,这样可以便于我们理解,避免混淆概念。一对儿公私钥,不能由其中的一个导出另一个。

      可以暂时这么理解:
      一对密钥在刚生成的时候是没有公私之分的,但是生成者会保留一个在自己手里,发布一个给别人用,正是这个“保留与发布”的操作才使得这对密钥有了公私之分,那么对于生成者来说,保留在自己手里的密钥就被称作生成者私钥,发布给别人用的那个密钥就被称作生成者公钥,注意这里的称呼带上了生成者,就是为了表明一对密钥的公私性总是相对于它们的生成者来说的。(实际中私钥和公钥在生成的时候已经具备了公私性,因为公钥和私钥是不同的生成机理,但这样理解也是没有错的,有助于帮助我们理清后面的关系)

      比如:
      我们使用支付宝SDK的时候,我们商户端会生成一对密钥A和B,支付宝也会生成一对密钥C和D。
      那么如果我们商户端保存了A,而把B发布给了支付宝,A就被称作商户端私钥,B就被称作商户端公钥。(注意称呼公私钥的时候带上生成者的名字,这样可以便于我们理解,避免混淆概念)
      当然我们也可以保存B,而把A发布给支付宝,这样B就被称作商户端私钥,A就被称作商户端公钥。(实际中不会这么做,因为公私钥已经提前确定好了,它们的生成机理不同但这样理解也是没有错的,有助于帮助我们理清后面的关系)
      同样,假设支付宝保存了C,而把D发布给了我们,那么C就被称作支付宝私钥,D就被称作支付宝公钥,反之同理
View Code


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)如果这张数字证书不是由受信任的机构颁发的,浏览器会发出另一种警告

 

原文网址:http://www.youdzone.com/signature.html

posted on 2019-11-12 09:38  始终不够啊  阅读(1455)  评论(0编辑  收藏  举报