支付功能(支付宝)

购物车设计

  • 购物车 由于商品可以增加数量,这里用modelSerializer当购物车添加了某个商品第二次会报错,所以使用serializer继承即可, 一般购物车用cookie比较好
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    class ShoppingCart(models.Model):
    """
    购物车
    """
    user = models.ForeignKey(User, verbose_name=u"用户", on_delete=models.CASCADE)
    goods = models.ForeignKey(Goods, verbose_name=u"商品", on_delete=models.CASCADE)
    nums = models.IntegerField(default=0, verbose_name="购买数量")

    add_time = models.DateTimeField(default=datetime.now, verbose_name=u"添加时间")

    class Meta:
    verbose_name = '购物车'
    verbose_name_plural = verbose_name
    unique_together = ("user", "goods")
    class ShopCartDetailSerializer(serializers.ModelSerializer):
    # 一个shopcart的记录只对应一个goods
    goods = GoodsSerializer(many=False)

    class Meta:
    model = ShoppingCart
    fields = "__all__"


    class ShoppingCartSerializer(serializers.Serializer):
    # Serializer灵活性最高
    user = serializers.HiddenField(
    default=serializers.CurrentUserDefault()
    )
    add_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M')
    nums = serializers.IntegerField(required=True, label="数量", min_value=1, error_messages={
    "min_value": "商品数量不能小于1",
    "required": "请选择购买数量"
    })
    # 请查看官网goods外键如何验证
    goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.all())

    def create(self, validated_data):
    # 在serializer里面request是放在self.context里面
    # 获取当前用户
    user = self.context['request'].user
    # 获取nums
    nums = validated_data['nums']
    # 是一个goods的对象
    goods = validated_data['goods']
    existed = ShoppingCart.objects.filter(user=user, goods=goods)
    if existed:
    existed = existed[0]
    existed.nums += nums
    existed.save()
    else:
    existed = ShoppingCart.objects.create(**validated_data)
    return existed

    def update(self, instance, validated_data):
    # 修改商品数量
    instance.nums = validated_data["nums"]
    instance.save()
    return instance

    class ShoppingCartViewset(viewsets.ModelViewSet):
    """
    购物车功能
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    # serializer_class = ShoppingCartSerializer
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)
    lookup_field = "goods_id"

    # 这里还是需要重载create才行
    def perform_create(self, serializer):
    goods = serializer.instance.goods
    tmp = goods.goods_num
    if tmp - serializer.instance.nums < 0:
    return Response(
    status=status.HTTP_400_BAD_REQUEST,
    data="库存数不足"
    )
    goods.goods_num -= serializer.instance.nums
    goods.save()
    serializer.save()

    def perform_destroy(self, instance):
    goods = instance.goods
    goods.goods_num += instance.nums
    goods.save()
    instance.delete()

    # 这里还是需要重载update
    def perform_update(self, serializer):
    existed_record = ShoppingCart.objects.get(id=serializer.instance.id)

    saved_record = serializer.instance
    nums = serializer.initial_data['nums'] - existed_record.nums
    goods = saved_record.goods
    tmp = goods.goods_num
    if tmp - nums < 0:
    # 这里return到上一层是无效的
    return Response(
    status=status.HTTP_400_BAD_REQUEST,
    data="库存数不足"
    )
    goods.goods_num -= nums
    goods.save()
    serializer.save()

    def get_serializer_class(self):
    if self.action == 'list':
    return ShopCartDetailSerializer
    else:
    return ShoppingCartSerializer

    def get_queryset(self):
    return ShoppingCart.objects.filter(user=self.request.user)
  • 注意电商一般是在提交订单后才减少库存,这里我们是加入购物车即减少库存

订单设计

  • models.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    class OrderInfo(models.Model):
    """
    订单
    """
    ORDER_STATUS = (
    ("TRADE_SUCCESS", "成功"),
    ("TRADE_CLOSED", "超时关闭"),
    ("WAIT_BUYER_PAY", "交易创建"),
    ("TRADE_FINISHED", "交易结束"),
    ("paying", "待支付"),
    )

    user = models.ForeignKey(User, verbose_name="用户", on_delete=models.CASCADE)
    order_sn = models.CharField(max_length=30, null=True, blank=True, unique=True, verbose_name="订单号")
    # 第三方支付需要用的 生成一个订单号返回给我们 我们需要将它和本系统的订单做一个关联
    trade_no = models.CharField(max_length=100, unique=True, null=True, blank=True, verbose_name=u"交易号")
    pay_status = models.CharField(choices=ORDER_STATUS, default="paying", max_length=30, verbose_name="订单状态")
    post_script = models.CharField(max_length=200, verbose_name="订单留言")
    order_mount = models.FloatField(default=0.0, verbose_name="订单金额")
    pay_time = models.DateTimeField(null=True, blank=True, verbose_name="支付时间")

    # 用户信息
    address = models.CharField(max_length=100, default="", verbose_name="收货地址")
    signer_name = models.CharField(max_length=20, default="", verbose_name="签收人")
    singer_mobile = models.CharField(max_length=11, verbose_name="联系电话")

    add_time = models.DateTimeField(default=datetime.now, verbose_name="添加时间")

    class Meta:
    verbose_name = u"订单"
    verbose_name_plural = verbose_name

    def __str__(self):
    return str(self.order_sn)
  • serializer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    from utils.alipay import AliPay
    from MxShop.settings import private_key_path, ali_pub_key_path


    class OrderSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(
    default=serializers.CurrentUserDefault()
    )
    add_time = serializers.DateTimeField(read_only=True, format='%Y-%m-%d %H:%M')
    pay_status = serializers.CharField(read_only=True)
    trade_no = serializers.CharField(read_only=True)
    order_sn = serializers.CharField(read_only=True)
    pay_time = serializers.DateTimeField(read_only=True)
    # alipay_url = serializers.SerializerMethodField(read_only=True)

    # def get_alipay_url(self, obj):
    # alipay = AliPay(
    # appid="2021000118645600",
    # app_notify_url="http://127.0.0.1:8090/alipay/return/",
    # app_private_key_path=private_key_path,
    # alipay_public_key_path=ali_pub_key_path, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    # debug=True, # 默认False,
    # return_url="http://127.0.0.1:8090/alipay/return/"
    # )
    # url = alipay.direct_pay(
    # subject=obj.order_sn,
    # out_trade_no=obj.order_sn,
    # total_amount=obj.order_mount,
    # )
    # re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    # return re_url

    def generate_order_sn(self):
    """当前时间 + userid + 随机数"""
    import time
    from random import Random
    random_ins = Random()
    order_sn = "{time_str}{userid}{randstr}".format(time_str=time.strftime("%Y%m%d%H%M%S"),
    userid=self.context['request'].user.id, randstr=random_ins.randint(10, 99))
    return order_sn

    def validate(self, attrs):
    attrs["order_sn"] = self.generate_order_sn()
    return attrs

    class Meta:
    model = OrderInfo
    fields = "__all__"
    views.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    class OrderViewset(viewsets.GenericViewSet, mixins.ListModelMixin, mixins.CreateModelMixin,
    mixins.RetrieveModelMixin, mixins.DestroyModelMixin):
    """
    订单管理
    list:
    获取个人订单
    delete:
    删除订单
    create:
    新增订单
    """
    permission_classes = (IsAuthenticated, IsOwnerOrReadOnly)
    # serializer_class = OrderSerializer
    authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication)

    def get_queryset(self):
    return OrderInfo.objects.filter(user=self.request.user)

    def get_serializer_class(self):
    if self.action == 'retrieve':
    return OrderDetailSerializer
    else:
    return OrderSerializer

    def perform_create(self, serializer):
    order = serializer.save()
    # 获取当前所有购物车的商品
    shop_carts = ShoppingCart.objects.filter(user=self.request.user)
    # 把购物车商品转为订单商品
    for shop_cart in shop_carts:
    order_goods = OrderGoods()
    order_goods.goods = shop_cart.goods
    order_goods.goods_num = shop_cart.nums
    order_goods.order = order
    order_goods.save()
    # 从购物车中delete掉
    shop_cart.delete()
    return order

支付宝沙箱使用(微信同)

  • 下载支付宝生成秘钥的工具(网址)保存公钥、私钥、支付宝公钥(在项目中创建一个key文件夹保存)
  • 按照官方文档要求定义alipay类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    from datetime import datetime
    from Crypto.PublicKey import RSA
    from Crypto.Signature import PKCS1_v1_5
    from Crypto.Hash import SHA256
    from base64 import b64encode, b64decode
    from urllib.parse import quote_plus
    from urllib.parse import urlparse, parse_qs
    from urllib.request import urlopen
    from base64 import decodebytes, encodebytes

    import json


    class AliPay:
    """
    支付宝支付接口
    """
    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.import_key(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 ordered_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 True

    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)


    if __name__ == "__main__":

    alipay = AliPay(
    appid="2021000118645600",
    app_notify_url="http://127.0.0.1:8090/alipay/return/",
    app_private_key_path="../trade/keys/private_2048.txt",
    alipay_public_key_path="../trade/keys/public_2048.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
    debug=True, # 默认False,
    return_url="http://127.0.0.1:8090/alipay/return/"
    )

    # o = urlparse(return_url)
    # query = parse_qs(o.query)
    # processed_query = {}
    # ali_sign = query.pop("sign")[0]
    # for key, value in query.items():
    # processed_query[key] = value[0]
    # print(alipay.verify(processed_query, ali_sign))

    url = alipay.direct_pay(
    subject="测试订单2",
    out_trade_no="202105141018371481",
    total_amount=100,
    return_url="http://127.0.0.1:8090/alipay/return/",

    )
    re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    print(re_url)

分享到