from rest_framework.views import APIView from mysystem.models import Users,Role,Dept from apps.oauth.models import OAuthWXUser from apps.mall.models import GoodsCategory,GoodsCoupon,CouponRecord,SPU,SKU,SKUImage,SKUSpecification,SPUSpecification,SPUSpecificationOption,OrderInfo,OrderGoods,CouponRecord,GoodsCoupon,OrderRefunds from apps.address.models import Address from utils.jsonResponse import SuccessResponse,ErrorResponse from rest_framework import serializers from utils.common import formatdatetime,float2dot,REGEX_MOBILE,get_parameter_dic,renameuploadimg,getminrandomodernum,geturlpath,ismoney,ast_convert,getrandomodernum,format_wechat_gmt_8_to_normal import re from django.db.models import Q,F,Sum, Avg, Max, Min, Count from rest_framework.serializers import ModelSerializer from rest_framework_simplejwt.authentication import JWTAuthentication from utils.serializers import CustomModelSerializer from utils.viewset import CustomModelViewSet from rest_framework.permissions import IsAuthenticated from utils.filters import FinanceOrderInfoTimeFilter from django.db import transaction from django_redis import get_redis_connection import datetime import os import json from django.contrib.auth.hashers import make_password from decimal import Decimal from utils.export_excel import export_excel from utils.pagination import CustomPagination from utils.wexinpay_cashout import Pay,get_client_ip,random_str from utils.weixinpay import WxAppPay from utils.alipay import initalipay,alipay_trade_app,alipay_trade_refund from config import DOMAIN_HOST from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from django.http import HttpResponse import logging logger = logging.getLogger(__name__) # Create your views here. # ================================================= # # ************** 后端商城管理 view ************** # # ================================================= # class GoodsCategoryManageSerializer(CustomModelSerializer): """ 商品分类管理-序列化器 """ class Meta: model = GoodsCategory read_only_fields = ["id"] fields = '__all__' class GoodsCategoryManageViewSet(CustomModelViewSet): """ 后台商品分类管理 接口 """ queryset = GoodsCategory.objects.all().order_by("sort") serializer_class = GoodsCategoryManageSerializer search_fields = ('name',) class GoodsCouponManageSerializer(CustomModelSerializer): """ 商品分类管理-序列化器 """ coupon_type_name = serializers.SerializerMethodField(read_only=True) menkan = serializers.SerializerMethodField(read_only=True) sendto = serializers.SerializerMethodField(read_only=True) def get_sendto(self,obj): if obj.receive_type in [1,2,4]: return "全部用户" def get_menkan(self,obj): if obj.is_condition:#有门槛 return float2dot(obj.use_min_price)+"元" else: return "无门槛" def get_coupon_type_name(self,obj): return obj.get_coupon_type_display() class Meta: model = GoodsCoupon read_only_fields = ["id"] fields = '__all__' class GoodsCouponManageViewSet(CustomModelViewSet): """ 后台优惠券管理 接口 """ queryset = GoodsCoupon.objects.all().order_by("-create_datetime") serializer_class = GoodsCouponManageSerializer search_fields = ('name',) class CouponRecordManageSerializer(CustomModelSerializer): """ 用户优惠券持有记录-序列化器 """ status_name = serializers.SerializerMethodField(read_only=True) coupon_name = serializers.SerializerMethodField(read_only=True) coupon_type_name = serializers.SerializerMethodField(read_only=True) used_time = serializers.SerializerMethodField(read_only=True) def get_used_time(self,obj): return formatdatetime(obj.used_time) def get_coupon_name(self,obj): return obj.coupon.name def get_coupon_type_name(self,obj): return obj.coupon.get_coupon_type_display() def get_status_name(self,obj): return obj.get_status_display() class Meta: model = CouponRecord read_only_fields = ["id"] fields = '__all__' class CouponRecordManageViewSet(CustomModelViewSet): """ 用户优惠券持有记录 接口 """ queryset = CouponRecord.objects.filter(is_delete=False).order_by("-create_datetime") serializer_class = CouponRecordManageSerializer # search_fields = ('name',) filterset_fields = ('user_id','status') class SPUSpecificationOptionSerializer(CustomModelSerializer): """ 商品SPU规格选项值 简单序列化器 """ class Meta: model = SPUSpecificationOption read_only_fields = ["id"] # fields = '__all__' exclude = ['dept_belong_id', 'modifier', 'creator'] extra_kwargs = { 'spec': {'required': False}, } class SPUSpecModelSerializer(CustomModelSerializer): """ 商品SPU规格名 简单序列化器 """ # spu = serializers.StringRelatedField() # spu_id = serializers.CharField() options = SPUSpecificationOptionSerializer(many=True,required=False) class Meta: model = SPUSpecification read_only_fields = ["id"] # fields = '__all__' exclude = ['dept_belong_id', 'modifier', 'creator'] extra_kwargs = { 'spu': {'required': False}, } class SKUSpecificationSerialzier(CustomModelSerializer): """ SKU规格表 简单序列化器 """ # spec_id = serializers.CharField() # option_id = serializers.CharField() # spec = serializers.StringRelatedField() # option = serializers.StringRelatedField() spec = serializers.CharField() option = serializers.CharField() class Meta: model = SKUSpecification # SKUSpecification中sku外键关联了SKU表 # fields = "__all__" # fields = ('id', 'spec', 'option') exclude = ['dept_belong_id', 'modifier', 'creator'] extra_kwargs = { 'sku': {'required': False}, 'spec': {'required': False}, 'option': {'required': False}, } class GoodsSPUImageSerialzier(CustomModelSerializer): """ SPU商品图片 简单序列化器 """ class Meta: model = SKUImage fields = ('image',) class GoodsSKUSerializer(CustomModelSerializer): """ SKU 简单序列化器 """ specs = SKUSpecificationSerialzier(many=True,required=False) class Meta: model = SKU # fields = "__all__" exclude = ['dept_belong_id', 'modifier', 'creator'] extra_kwargs = { 'spu': {'required': False}, } class GoodsSPUSerializer(CustomModelSerializer): """ 商品管理 SPU 简单序列化器 """ spu_specs = SPUSpecModelSerializer(many=True,required=False) skus = GoodsSKUSerializer(many=True,required=False) category1_name = serializers.SerializerMethodField(read_only=True) stock = serializers.SerializerMethodField(read_only=True)#默认多规格选择为所有sku库存的合计 def get_stock(self,obj): if obj.spec_type:#多规格 allskus = obj.skus.all().aggregate(sumstock=Sum('stock')) return allskus['sumstock'] else:#单规格 return obj.skus.first().stock def get_category1_name(self,obj): if obj.category1: return obj.category1.name def to_representation(self, instance): # 序列化 ret = super().to_representation(instance) ret['image_list'] = ast_convert(ret['image_list']) # 可以保存的修改字段值的方法 return ret def to_internal_value(self, data): # 进提取所需要的数据,对其进行反序列化,data代表未验证的数据 data['image_list'] = str(data['image_list']) return super().to_internal_value(data) def create(self, validated_data): """ 重写create方法,来手动插入中间表(SKUSpecification)数据记录新增sku拥有的规格和选项信息和轮播图 :param validated_data:有效数据 :return:新建的SPU对象 """ dept_belong_id = getattr(self.request.user, 'dept_id', None) # specs记录的是中间表数据,无法用于新建SKU,所以先从有效数据中移除 # specs = [{spec_id:1, option_id:2}, {...}...] # 获取规格信息,并从validated_data数据中,删除规格信息数据 spec_type = validated_data['spec_type'] spu_sepcs = validated_data.pop('spu_specs',[]) # 商品的spu规格字典数组形式[ { "name": "颜色", "options": [ { "value": "白色" }, { "value": "黑色" } ] }, { "name": "大小", "options": [ { "value": "X" }, { "value": "S" } ] } ] skus = validated_data.pop('skus',[]) validated_data[self.dept_belong_id_field_name] = dept_belong_id with transaction.atomic(): # 开启事务 try: savepoint = transaction.savepoint() spu = SPU.objects.create(**validated_data) if spec_type:#多规格 if len(spu_sepcs) <=0 or len(skus) <=0: # 回滚到保存点 transaction.savepoint_rollback(savepoint) raise serializers.ValidationError("spu_sepcs或skus不能为空",400) return for spu_sepc in spu_sepcs: new_spu_spec = SPUSpecification.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spu_id=spu.id,name=spu_sepc['name']) for spu_sepc_option in spu_sepc['options']: new_spu_spec_option = SPUSpecificationOption.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spec_id=new_spu_spec.id,value=spu_sepc_option['value']) for sk in skus: if 'default_image' not in sk.keys(): sk['default_image']="" sku_instance = SKU.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spu_id=spu.id,price=sk['price'],stock=sk['stock'],default_image=sk['default_image']) for sp in sk['specs']: spuspec_instance = SPUSpecification.objects.filter(spu_id=spu.id,name=sp['spec']).first() spuspec_option_instance = SPUSpecificationOption.objects.filter(spec_id=spuspec_instance.id, value=sp['option']).first() SKUSpecification.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,sku_id=sku_instance.id,spec_id=spuspec_instance.id,option_id=spuspec_option_instance.id) # 获取价格最低的sku保存到spu的price中 minpricesku = SKU.objects.filter(spu=spu).order_by('price').first() spu.price = minpricesku.price spu.save() else:#单规格 if len(skus)<=0: # 回滚到保存点 transaction.savepoint_rollback(savepoint) raise serializers.ValidationError("skus不能为空", 400) return SKU.objects.create(creator=self.request.user,dept_belong_id=dept_belong_id,spu=spu,price=skus[0]['price'],stock=skus[0]['stock'],default_image=skus[0]['default_image']) except Exception as e: # 回滚到保存点 transaction.savepoint_rollback(savepoint) return e # 清除保存点 transaction.savepoint_commit(savepoint) #返回spu return spu class Meta: model = SPU #fields = "__all__" exclude = ['dept_belong_id','modifier','creator','unit','brand','comments','desc_pack','desc_service'] read_only_fields = ["id"] extra_kwargs = { 'spu_specs': {'required': False}, } class SKUSpecificationSPUUpdateSerialzier(CustomModelSerializer): """ SKU规格表 简单序列化器 """ spec = serializers.CharField() option = serializers.CharField() id = serializers.CharField() def validate_id(self, value): return value class Meta: model = SKUSpecification # SKUSpecification中sku外键关联了SKU表 # fields = "__all__" # fields = ('id', 'spec', 'option') exclude = ['dept_belong_id', 'modifier', 'creator'] extra_kwargs = { 'sku': {'required': False}, 'spec': {'required': False}, 'option': {'required': False}, } class GoodsSKUSPUUpdateSerializer(CustomModelSerializer): """ SKU 简单序列化器 """ id = serializers.CharField() specs = SKUSpecificationSPUUpdateSerialzier(many=True,required=False) def validate_id(self, value): return value class Meta: model = SKU # fields = "__all__" exclude = ['dept_belong_id', 'modifier', 'creator'] extra_kwargs = { 'spu': {'required': False}, } class GoodsSPUUpdateSerializer(CustomModelSerializer): # skus = serializers.ListField(required=True) skus = GoodsSKUSPUUpdateSerializer(many=True, required=True) # spu_specs = serializers.ListField(required=False) def to_internal_value(self, data): # 进提取所需要的数据,对其进行反序列化,data代表未验证的数据 data['image_list'] = str(data['image_list']) return super().to_internal_value(data) def update(self, instance, validated_data): if self.modifier_field_id in self.fields.fields: validated_data[self.modifier_field_id] = self.get_request_user_id() # 获取规格信息,并从validated_data数据中,删除规格信息数据 spec_type = validated_data['spec_type'] if instance.spec_type != spec_type: raise serializers.ValidationError("商品规格类型不能更改", 400) return skus = validated_data.pop('skus',[]) # spu_sepcs = validated_data.pop('spu_specs') if len(skus) <= 0: raise serializers.ValidationError("skus不能为空", 400) return with transaction.atomic(): # 开启事务 try: savepoint = transaction.savepoint() # 方案一: 调用父类 ,去实现没有问题的数据更新 instance = super().update(instance, validated_data) if spec_type: # 多规格(只能修改现有规格库存和价格,不能添加和减少规格)前端直接修改,不通过本接口修改 pass else:#单规格 SKU.objects.filter(spu_id=instance.id,id=skus[0]['id']).update(price=skus[0]['price'],stock=skus[0]['stock'],default_image=skus[0]['default_image'],modifier=self.get_request_user_id()) except Exception as e: # 回滚到保存点 transaction.savepoint_rollback(savepoint) return e # 清除保存点 transaction.savepoint_commit(savepoint) return instance class Meta: model = SPU #fields = "__all__" exclude = ['dept_belong_id','modifier','creator'] read_only_fields = ["id"] class GoodsSPUViewSet(CustomModelViewSet): queryset = SPU.objects.all().order_by("sort") serializer_class = GoodsSPUSerializer create_serializer_class = GoodsSPUSerializer update_serializer_class =GoodsSPUUpdateSerializer search_fields = ('name',) filterset_fields = ('is_launched','category1') export_download_filename = "导出商品数据" export_field_dict = { "default_image": "导出图片演示", "name": "商品名称", "category1_name": "所属分类", "price": "售价", "stock": "库存", "is_tuijian": "是否推荐", "sort": "排序", "is_launched":"状态", "image_list":"导出列表字符串并换行", "spu_specs":"原样导出", "spu_specs.name":"导出列表字典某个属性并换行", "create_datetime": "创建时间", } def islaunched(self, request, *args, **kwargs): """上下架商品""" queryset = self.filter_queryset(self.get_queryset()) pk = kwargs.get('pk') if ',' in kwargs.get('pk'): # 批量 ids = pk.split(',') instance = queryset.filter(id__in=ids) if instance: instance.update(is_launched=True) return SuccessResponse(data=None, msg="修改成功") else: return ErrorResponse(msg="未获取到该商品") else: instance = SPU.objects.filter(id=kwargs.get('pk')).first() if instance: if instance.is_launched: instance.is_launched = False else: instance.is_launched = True instance.save() return SuccessResponse(data=None, msg="修改成功") else: return ErrorResponse(msg="未获取到该商品") def editsku_price_stock(self, request, *args, **kwargs): """多规格情况下修改商品的库存和价格和默认图片""" pk = kwargs.get('pk') instance = SKU.objects.filter(id=pk).first() if instance: price = get_parameter_dic(request)['price'] stock = get_parameter_dic(request)['stock'] default_image = get_parameter_dic(request)['default_image'] instance.price = price instance.stock = stock instance.default_image = default_image instance.save() #修改spu的最低price价格 skus = SKU.objects.filter(spu=instance.spu).order_by('price').first() SPU.objects.filter(id=instance.spu.id).update(price=skus.price) return SuccessResponse(data=None, msg="修改成功") else: return ErrorResponse(msg="未获取到该商品") class FinanceOrderInfoSerializer(CustomModelSerializer): """ 商品类财务流水-序列化器 """ userinfo = serializers.SerializerMethodField() pay_method = serializers.SerializerMethodField() gname = serializers.SerializerMethodField() def get_gname(self,obj): allgoods = obj.ordergoodsskus.all().first() return allgoods.sku.spu.name def get_pay_method(self,obj): return obj.get_pay_method_display() def get_userinfo(self, obj): return { 'id': obj.user.id, 'nickname': obj.user.nickname, 'avatar': obj.user.avatar } class Meta: model = OrderInfo read_only_fields = ["id"] fields = '__all__' class FinanceOrderInfoViewSet(CustomModelViewSet): """ 后台会商品类财务流水 接口 """ queryset = OrderInfo.objects.filter(pay_status=1).order_by("-create_datetime") serializer_class = FinanceOrderInfoSerializer # search_fields = ('name',) filterset_class = FinanceOrderInfoTimeFilter def orderstatistics(self,request): """ 订单金额 收益总金额统计 :param request: :return: """ #按照原来的过滤查询 queryset = self.filter_queryset(self.get_queryset()) totalmoney = 0 if queryset: totalmoney = queryset.aggregate(price = Sum('total_amount'))['price'] else: totalmoney = 0 data = { "totalmoney": float2dot(totalmoney), } return SuccessResponse(data=data,msg='success') class OrderInfoSerializer(CustomModelSerializer): address = serializers.SerializerMethodField(read_only=True) userinfo = serializers.SerializerMethodField(read_only=True) goodsinfo = serializers.SerializerMethodField(read_only=True) pay_time = serializers.SerializerMethodField(read_only=True) pay_method_name = serializers.SerializerMethodField(read_only=True) def get_pay_time(self,obj): return formatdatetime(obj.pay_time) def get_pay_method_name(self,obj): return obj.get_pay_method_display() def get_goodsinfo(self,obj): data=[] goods = OrderGoods.objects.filter(order_id=obj.id) if not goods: return None for good in goods: datasku={} if good.sku.spu.spec_type == 1:#多规格 sku_spec = "" temp_sepcs = good.sku.specs.all() for temp_sepc in temp_sepcs: if len(temp_sepcs) > 1: sku_spec = sku_spec + temp_sepc.option.value + ';' else: sku_spec = temp_sepc.option.value else: sku_spec = good.sku.spu.name datasku['sku_name'] = good.sku.name datasku['sku_spec'] = sku_spec datasku['sku_default_image'] = good.sku.default_image datasku['count'] = good.count datasku['price'] = good.price datasku['comment'] = good.comment datasku['score'] = good.score data.append(datasku) return data def get_address(self,obj): data={} data['areas'] = obj.address_place data['receiver'] = obj.address_name data['mobile'] = obj.address_mobile return data def get_userinfo(self, obj): return { 'id': obj.user.id, 'nickname': obj.user.nickname, 'name':obj.user.name, 'avatar': obj.user.avatar, 'mobile':obj.user.mobile, } class Meta: model = OrderInfo fields = "__all__" read_only_fields = ["id"] class GoodsOrderManageViewSet(CustomModelViewSet): """ 商品订单管理: list:查询 create:新增 update:修改 retrieve:单例 destroy:删除 """ queryset = OrderInfo.objects.all().order_by('-create_datetime') serializer_class = OrderInfoSerializer filterset_class = FinanceOrderInfoTimeFilter def closeorder(self,request): # 按照原来的过滤查询 queryset = self.filter_queryset(self.get_queryset()) # 接收的参数 id = get_parameter_dic(request)['id'] # 商品订单id if not id: return ErrorResponse(msg="提交的参数不能为空") order = queryset.filter(id=id, status=2).first() if not order: return ErrorResponse(msg='不存在此商品订单信息,或该状态订单不支持关闭') with transaction.atomic(): order.status = 6 order.save() # 订单退款逻辑 orderrefund() return SuccessResponse(msg='订单已关闭') def orderstatistics(self,request): """ 订单金额 订单量统计 :param request: :return: """ #按照原来的过滤查询 queryset = self.filter_queryset(self.get_queryset()) totalmoney = 0 totalcount = 0 if queryset: totalmoney = queryset.aggregate(price = Sum('total_amount'))['price'] totalcount = queryset.count() data = { "totalmoney": float2dot(totalmoney), "totalcount": totalcount, } return SuccessResponse(data=data,msg='success') def sendoutgoods(self,request): # 按照原来的过滤查询 queryset = self.filter_queryset(self.get_queryset()) #接收的参数 id = get_parameter_dic(request)['id'] # 商品订单id orderNo = get_parameter_dic(request)['orderNo'] # 商品物流单号 logistics_company = get_parameter_dic(request)['logistics_company'] # 商品物流公司 if not all([id,orderNo,logistics_company]): return ErrorResponse(msg="提交的参数不能为空") order = queryset.filter(id=id,status=2).first() if not order: return ErrorResponse(msg='不存在此商品订单信息,或该状态订单不支持发货') order.logistics_id = orderNo order.logistics_company = logistics_company order.send_time = datetime.datetime.now() order.status = 3 order.save() return SuccessResponse(msg='修改成功') def orderrefund(orderid,type=1): """ orderid为要退款的订单id type:1:商城订单退款 2服务订单退款 """ if type == 1:#商城订单 order = OrderInfo.objects.get(id=orderid) paymethod = order.pay_method transaction_id = order.trade_id money = order.total_amount reason = "订单退款" orderno = getrandomodernum() refundsorder = OrderRefunds.objects.create(order_no=orderno,order=order,amount=money,reason=reason) else:#其他订单 pass # 支付类型判断 if paymethod == 2:#微信 wxapppay = WxAppPay() data = wxapppay.refundsorder(orderno, transaction_id,reason, money*100,money*100,notify_url="%s/api/app/wechatpay_notify_refund/" % DOMAIN_HOST) elif paymethod == 3:#支付宝 data = alipay_trade_refund(transaction_id, float(money), notify_url="%s/api/app/ali_notify_refund/" % DOMAIN_HOST) #判断是否成功 # ================================================= # # ************** 前端商城接口 view ************** # # ================================================= # class GoodsTypeView(APIView): """ get: 获取商城分类 【参数】type = all 所有 、 tabs 标签 、 icons 图标 """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self,request): type = get_parameter_dic(request)['type'] if type == "all": queryset = GoodsCategory.objects.filter(is_delete=False).order_by('sort') elif type == "tabs": queryset = GoodsCategory.objects.filter(default_image__isnull=True,is_delete=False).order_by('sort') elif type == "icons": queryset = GoodsCategory.objects.filter(default_image__isnull=False,is_delete=False).order_by('sort') else: return ErrorResponse(msg='type类型错误') data = [] if queryset: for q in queryset: data.append({ 'id': q.id, 'name': q.name, 'default_image': q.default_image, }) return SuccessResponse(data=data,msg="success") class GoodsListView(APIView): """ get: 获取商城商品列表 【参数】 search:搜索(非必须) is_tuijian: 0非推荐、1推荐、2所有 (必须,默认为2获取说有) order_by:排序 sort 商品默认顺序 、price 正序 、-price 倒叙、sales 正序、-sales倒叙 (默认为sort)(非必须) category:商品分类id(非必须),默认为所有分类 """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self,request): search = get_parameter_dic(request).get('search') # 搜索 is_tuijian = int(get_parameter_dic(request)['is_tuijian']) # 是否获取推荐商品 order_by = get_parameter_dic(request).get('order_by') # 排序 category = get_parameter_dic(request).get('category') # 分类 if is_tuijian not in [0,1,2]: return ErrorResponse("is_tuijian error") if order_by and order_by not in ['sort','price','-price','-sales','sales']: return ErrorResponse("order_by error") else: order_by = "sort" if search: if is_tuijian == 2: if category: queryset = SPU.objects.filter(name__contains=search,category1_id=category,is_launched=True).order_by(order_by) else: queryset = SPU.objects.filter(name__contains=search, is_launched=True).order_by(order_by) else: queryset = SPU.objects.filter(name__contains=search,is_tuijian=is_tuijian,is_launched=True).order_by(order_by) else: if is_tuijian == 2: if category: queryset = SPU.objects.filter(category1_id=category,is_launched=True).order_by(order_by) else: queryset = SPU.objects.filter(is_launched=True).order_by(order_by) else: queryset = SPU.objects.filter(is_tuijian=is_tuijian,is_launched=True).order_by(order_by) # # 1. 实例化分页器对象 page_obj = CustomPagination() # # 2. 使用自己配置的分页器调用分页方法进行分页 page_data = page_obj.paginate_queryset(queryset, request) data = [] if queryset: for m in page_data: data.append({ 'spu_id':m.id, 'price':m.price, 'default_image':m.default_image, 'name':m.name, 'sales':m.sales, 'spec_type':m.spec_type }) return page_obj.get_paginated_response(data=data) class GoodsDetailView(APIView): """ get: 获取商城商品列表 【参数】 spu_id:商品id """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self,request): spu_id = get_parameter_dic(request).get('spu_id') # 商品id if not spu_id: return ErrorResponse(msg="参数spu_id不能为空") spu = SPU.objects.filter(id=spu_id,is_launched=True).first() if not spu: return ErrorResponse(msg='该商品不存在或已下架') skus = SKU.objects.filter(spu=spu).order_by("create_datetime") sum_stock = skus.aggregate(sumstock=Sum('stock')) stock=sum_stock['sumstock'] myspecs = []#规格 if spu.spec_type == 1:#多规格 spusepcs = SPUSpecification.objects.filter(spu=spu).order_by('create_datetime') for spusepc in spusepcs: spuspecvalues = spusepc.options.all() spu_options = {'values': []} spu_options['id'] = spusepc.id spu_options['spec_key'] = spusepc.name for temps in spuspecvalues: spu_options['values'].append({ 'id':temps.id, 'spec_value':temps.value }) myspecs.append(spu_options) skus_list = []#skus for temp_sku in skus: # temp_sku:每一个sku商品对象 sku_spec_options = SKUSpecification.objects.filter(sku=temp_sku).order_by('spec_id') sku_options = {'spec': []} sku_options['sku_id'] = temp_sku.id sku_options['stock'] = temp_sku.stock sku_options['price'] = temp_sku.price sku_options['default_image'] = temp_sku.default_image if spu.spec_type == 1:#多规格 for temp in sku_spec_options: sku_options['spec'].append({'spec_key': temp.spec.name, 'spec_value': temp.option.value, 'spec_key_id': temp.spec_id,'spec_value_id': temp.option_id}) skus_list.append(sku_options) data = { 'spu_id': spu.id, 'price': spu.price, 'default_image': spu.default_image, 'goods_imagelist': SKUImage.objects.filter(spu=spu).values_list('image', flat=True).order_by('sort'), 'name': spu.name, 'sales': spu.sales, 'spec_type': spu.spec_type, 'desc_detail':spu.desc_detail, 'stock':stock, 'skus':skus_list, 'specs': myspecs } return SuccessResponse(data=data, msg='success') class CartsView(APIView): """ get: 前端用户获取购物车列表 post: 更新购物车信息 【参数】:type为需要操作购物车的功能类型,del表示删除、edit表示编辑,add表示新增 """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self, request): user = request.user client = get_redis_connection('carts') sku_id_count = client.hgetall('carts_%s' % user.id) sku_id_selected = client.smembers('carts_selected_%s' % user.id) data_dict = {} for sku_id,count in sku_id_count.items(): data_dict[sku_id] = { 'count': int(count), 'selected': sku_id in sku_id_selected } sku_keys = data_dict.keys() skus = SKU.objects.filter(id__in=sku_keys) cart_skus = [] for sku in skus: spec_name = "" if sku.spu.spec_type == 1:#多规格 spec_names = sku.specs.all() for temp_spec_name in spec_names: if len(spec_names)>1: spec_name =spec_name + temp_spec_name.option.value+';' else: spec_name = temp_spec_name.option.value cart_skus.append({ 'spu_id': sku.spu.id, 'sku_id': sku.id, 'name': sku.spu.name, 'price': float2dot(sku.price), 'count': data_dict[sku.id]['count'], 'stock':sku.stock, 'spec_name':spec_name, 'default_image': sku.spu.default_image, 'selected': str(data_dict[sku.id]['selected']), }) return SuccessResponse(data=cart_skus,msg="success") def post(self, request): sku_id = get_parameter_dic(request)['sku_id']#商品id type = get_parameter_dic(request)['type']#购物车操作类型 # 验证商品是否存在 sku = SKU.objects.filter(id=sku_id).first() if not sku: return ErrorResponse(msg='商品不存在') user = request.user if type=="add":#添加购物车,在redis中保存用户的购物车记录 count = int(get_parameter_dic(request)['count']) # 购物车该商品的数量 selected = int(get_parameter_dic(request)['selected']) # 购物车操作类型 1勾选 0 不勾选 if selected not in [0, 1]: return ErrorResponse(msg="selected error") if sku_id is None or count is None: return ErrorResponse(msg='sku_id/count不能为空') if not isinstance(count,int): return ErrorResponse(msg='count类型错误') selected = True if int(count) > sku.stock: return ErrorResponse(msg='库存不足') # 登陆过则存入redis client = get_redis_connection('carts') # 保存用户购物车添加的商品id和对应数量count,如果redis购物车已添加该商品,数量需要进行累加 client.hincrby('carts_%s' % user.id, sku_id, count) if selected: client.sadd('carts_selected_%s' % user.id, sku_id) return SuccessResponse(msg='success') elif type == "edit":#购物车记录更新 count = int(get_parameter_dic(request)['count']) # 购物车该商品的数量 selected = int(get_parameter_dic(request)['selected']) # 是否选中 if selected not in [0, 1]: return ErrorResponse(msg="selected error") if sku_id is None or count is None: return ErrorResponse(msg='sku_id/count不能为空') if not isinstance(count,int): return ErrorResponse(msg='count类型错误') if int(count) > sku.stock: return ErrorResponse(msg='库存不足') # 登陆过对redis进行操作 client = get_redis_connection('carts') client.hset('carts_%s' % user.id, sku_id, count) if selected:# 勾选:将sku_id加入的set中 client.sadd('carts_selected_%s' % user.id, sku_id) else:# 不勾选:将sku_id从set中移除 client.srem('carts_selected_%s' % user.id, sku_id) cart_sku = { 'spu_id': sku.spu.id, 'sku_id': sku.id, 'name': sku.spu.name, 'price': float2dot(sku.price), 'count': count, 'default_image': sku.spu.default_image, 'amount': sku.price * count, 'selected': str(selected) } return SuccessResponse(data=cart_sku,msg='success') elif type == "del":#购物车记录删除 if sku_id is None: return ErrorResponse(msg='sku_id参数不能为空') client = get_redis_connection('carts') # (1)、删除商品和数量 client.hdel('carts_%s' % user.id, sku_id) # (2)、删除选中状态 client.srem('carts_selected_%s' % user.id, sku_id) return SuccessResponse(msg='success') else: return ErrorResponse(msg='type类型错误') class CartsSelectAllView(APIView): """ post: 购物车记录全选和取消全选 【参数】:selected:true、false选中/不选中购物车中的所有商品,(全选) """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def post(self, request): selected = int(get_parameter_dic(request)['selected']) if selected is None: return ErrorResponse(msg='selected参数不能为空') if selected not in [0,1]: return ErrorResponse(msg='selected类型错误') user = request.user client = get_redis_connection('carts') # 获取redis中用户所有商品 sku_id_count = client.hgetall('carts_%s' % user.id) # 购物车中所有的商品的id值 sku_ids = sku_id_count.keys() if selected: client.sadd('carts_selected_%s' % user.id, *sku_ids) else: client.srem('carts_selected_%s' % user.id, *sku_ids) return SuccessResponse(msg='success') class MyCouponView(APIView): """ get: 我的优惠券列表 【参数】:type ((0, '通用'),(1, '商城类'), (2, '服务类'), (10, '通用、商城'), (20, '通用、服务')) 【参数】:status ((0, '未领取'),(1, '未使用'), (2, '已使用'), (3, '已过期'), (4, '已撤回')) post: 立即领取优惠券 【参数】: coupon_id 优惠券ID type:del删除、get领取 """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self, request): user = request.user if user.identity != 1: return ErrorResponse(msg="用户类型错误") status = int(get_parameter_dic(request)['status']) if status not in [0, 1, 2, 3, 4, 10]: return ErrorResponse(msg="status error") type = get_parameter_dic(request).get('type') if type: type = int(type) if type not in [0, 1, 2, 10, 20]: return ErrorResponse(msg="type error") # 判断优惠券中是否有手动领取的劵,有的话则需要创建到用户优惠券表,并设置状态为未领取(手动领取的劵针对的是全部用户) coupon_res = GoodsCoupon.objects.filter(receive_type=1, is_delete=False, status=True).order_by( 'create_datetime') if coupon_res: for c in coupon_res: obj, created = CouponRecord.objects.get_or_create(user=user, coupon=c) # 检查自己的未使用的优惠券是否过期 queryset_isexpire = CouponRecord.objects.filter(user=user, status=1) nowtime = datetime.datetime.now() if queryset_isexpire: for e in queryset_isexpire: receive_time = e.receive_time expiretime = receive_time + datetime.timedelta(days=e.coupon.coupon_expiretime) # 领取后的过期时间 if expiretime < nowtime: e.status = 3 e.save() if type: if type == 10: if status == 10: queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], coupon__coupon_type__in=[0, 1],is_delete=False).order_by('-create_datetime') else: queryset = CouponRecord.objects.filter(user=user, status=status, coupon__coupon_type__in=[0, 1],is_delete=False).order_by('-create_datetime') elif type == 20: if status == 10: queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], coupon__coupon_type__in=[0, 2],is_delete=False).order_by('-create_datetime') else: queryset = CouponRecord.objects.filter(user=user, status=status, coupon__coupon_type__in=[0, 2],is_delete=False).order_by('-create_datetime') else: if status == 10: queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], coupon__coupon_type=type,is_delete=False).order_by('-create_datetime') else: queryset = CouponRecord.objects.filter(user=user, status=status, coupon__coupon_type=type,is_delete=False).order_by('-create_datetime') else: if status == 10: queryset = CouponRecord.objects.filter(user=user, status__in=[0, 1], is_delete=False).order_by('-create_datetime') else: queryset = CouponRecord.objects.filter(user=user, status=status, is_delete=False).order_by('-create_datetime') data = [] if queryset: for m in queryset: desc = '无门槛' if m.coupon.is_condition: # 有门槛 desc = "满" + str(int(m.coupon.use_min_price)) + "减" + str(int(m.coupon.price)) data.append({ 'id': m.id, 'name': m.coupon.name, 'price': m.coupon.price, 'coupon_expiretime': m.coupon.coupon_expiretime, 'create_datetime': formatdatetime(m.create_datetime), 'receive_time': formatdatetime(m.receive_time), 'is_condition': m.coupon.is_condition, 'condition_price': m.coupon.use_min_price, 'condition': desc, 'coupon_type': m.coupon.get_coupon_type_display(), 'type': m.coupon.coupon_type, 'status': m.status, 'status_name': m.get_status_display() }) return SuccessResponse(data=data, msg="success") def post(self, request): user = request.user if user.identity != 1: return ErrorResponse(msg="用户类型错误") coupon_id = get_parameter_dic(request)['coupon_id'] type = get_parameter_dic(request)['type'] if type not in ['del','get']: return ErrorResponse(msg='type error') if not coupon_id: return ErrorResponse(msg='coupon_id error') if type == 'get': res = CouponRecord.objects.filter(id=coupon_id, is_delete=False, user=user, status=0).first() if not res: return ErrorResponse(msg='无此优惠券') res.status = 1 res.receive_time = datetime.datetime.now() res.save() else: res = CouponRecord.objects.filter(id=coupon_id, is_delete=False, user=user, status__in=[2,3]).first() if not res: return ErrorResponse(msg='无此优惠券或该状态不支持删除') res.is_delete = 1 res.save() return SuccessResponse(msg='success') class OrdersCommitView(APIView): """ post: 购物车订单提交 【参数】: address_id用户的收货地址id remark:订单备注 type:1 购物车订单提交 2 商品直接购买订单提交 coupon_id:使用的优惠券id(非必须) count:购买数量(type=2时该参数为必须) sku_id:要购买的商品sku_id(type=2时该参数为必须) """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def post(self, request): address_id = get_parameter_dic(request)['address_id'] remark = get_parameter_dic(request)['remark'] type = int(get_parameter_dic(request)['type']) coupon_id = get_parameter_dic(request).get('coupon_id') addresses = Address.objects.filter(id=address_id,is_deleted=False).first() if not addresses: return ErrorResponse(msg='地址不存在') if not remark: remark = "" user = request.user # 生成订单编号 order_id = getrandomodernum() #运费 freight = Decimal(0) #优惠券判断 coupon = "" if coupon_id: coupon = CouponRecord.objects.filter(id=coupon_id,status=1,coupon__coupon_type__in=[0,1],user=user,is_delete=False).first() if not coupon: return ErrorResponse("coupon_id error") if type == 1:#购物车订单提交 # 开启事物 with transaction.atomic(): try: # 设置保存点 save_point = transaction.savepoint() # 生成订单的基本信息 order = OrderInfo.objects.create( order_id=order_id, user=user, remark=remark, address=addresses, total_count=0, total_amount=Decimal(0), freight=Decimal(0),#运费 status=1, pay_status=0, ) # 从redis获取选中状态的商品 client = get_redis_connection('carts') sku_id_count = client.hgetall('carts_%s' % user.id) sku_count = {} for sku_id, count in sku_id_count.items(): sku_count[sku_id] = int(count) # 获取选中的sku_id sku_ids = client.smembers('carts_selected_%s' % user.id) if not sku_ids: # 回滚到保存点 transaction.savepoint_rollback(save_point) return ErrorResponse(msg="购物车没有选中的商品") # 遍历商品订单商品表 for sku_id in sku_ids: # 每遍历出一个sku_id,就需要往OrderGoods表中插入一条数据 for i in range(5): # (1)乐观锁step1:获取旧库存和销量 sku = SKU.objects.get(id=sku_id) stock_old = sku.stock # 选中数量 count = sku_count[sku.id] sales_old = sku.sales if count > stock_old: # 回滚到保存点 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='库存不足') # (2)、乐观锁step2:根据旧数据计算新库存和销量 —— 耗时操作 stock_new = sku.stock - count sales_new = sku.sales + count # 乐观锁step3: 基于旧库存和销量过滤查找模型类对象,然后更新 # 查询集批量修改函数update有整数返回值 —— 表示被修改了的对象有几个再次按照旧的库存获取商品,获取到更新,获取不到返回再次操作(乐观锁) res = SKU.objects.filter(id=sku_id, stock=stock_old,sales=sales_old).update(stock=stock_new, sales=sales_new) if res == 0: # (3.2)、说明没有数据被成功修改,继而说明filter过滤出空查询集,进一步说明"根据旧库存找不到原有的sku,有别的事务介入" continue else:# (3.1)、res返回值不为0,说明,没有别的事务介入,成功修改了 break if res ==0:#重试了6此结果都没有操作成功 # 回滚到保存点 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='服务器繁忙,请稍后再试') # 修改spu表中的总销量 sku.spu.sales += count sku.spu.save() # 统计订单中的商品总数和订单总价格 order.total_count += count order.total_amount += sku.price * count # 新建订单商品数据,关联sku和订单 OrderGoods.objects.create( order=order, sku=sku, count=count, price=sku.price ) #order.total_amount += order.freight # 优惠券判断 if coupon: # 如果用了优惠券 if couponisexpire(coupon): # 回滚 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='优惠券已过期') if coupon.coupon.is_condition: # 有门槛 if float(sku.price) * count < float(coupon.coupon.use_min_price): # 回滚 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='优惠券不符合使用规则') coupon_price = coupon.coupon.price order.total_amount_pay = order.total_amount order.total_amount = order.total_amount - coupon_price order.couponrecord_id = coupon_id order.couponrecord_price = coupon_price coupon.status = 2 coupon.used_time = datetime.datetime.now() coupon.save() else: order.total_amount_pay = order.total_amount order.save() except: # 回滚到保存点 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='请稍后再试') else: # 事物结束提交数据 transaction.savepoint_commit(save_point) # 删除购物车选中状态的商品 client.hdel('carts_%s' % user.id, *sku_ids) client.srem('carts_selected_%s' % user.id, *sku_ids) return SuccessResponse(data={'id': order.id,'order_id': order.order_id,'total_amount':order.total_amount},msg='success') else:#直接购买 sku_id = get_parameter_dic(request)['sku_id'] count = int(get_parameter_dic(request)['count']) if not sku_id: return ErrorResponse(msg='要购买的商品不能为空') if count <= 0: return ErrorResponse(msg='要购买商品数量错误') sku = SKU.objects.filter(id=sku_id, spu__is_launched=True).first() if not sku: return ErrorResponse(msg='不存在该商品,或该商品暂不支持购买') if sku.stock < count: return ErrorResponse(msg='该商品库存不足') # 开启事物 with transaction.atomic(): # 设置保存点 save_point = transaction.savepoint() try: # 生成订单的基本信息 order = OrderInfo.objects.create( order_id=order_id, user=user, address=addresses, total_count=0, total_amount=Decimal(0), freight=Decimal(0), # 运费 remark=remark, status=1, pay_status=0, ) # 获取当前商品的库存 stock_old = sku.stock # 修改sku商品库存和销量 stock_new = sku.stock - count sales_new = sku.sales + count # 再次按照旧的库存获取商品,获取到更新,获取不到返回再次操作(乐观锁) for i in range(3): res = SKU.objects.filter(id=sku_id, stock=stock_old).update(stock=stock_new, sales=sales_new) if res == 0: if i == 2: # 如果连续3次都是更新失败,则回滚 # 回滚 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='请稍后再试') continue else: break # 修改spu表中的总销量 sku.spu.sales += count sku.spu.save() # 优惠券判断 if coupon: # 如果用了优惠券 if couponisexpire(coupon): # 回滚 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='优惠券已过期') if coupon.coupon.is_condition: # 有门槛 if float(sku.price) * count < float(coupon.coupon.use_min_price): # 回滚 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='优惠券不符合使用规则') coupon_price = coupon.coupon.price order.total_count = count order.total_amount_pay = sku.price * count order.total_amount = sku.price * count - coupon_price order.couponrecord_id = coupon_id order.couponrecord_price = coupon_price coupon.status = 2 coupon.used_time = datetime.datetime.now() coupon.save() else:#没有优惠券 order.total_count = count order.total_amount = sku.price * count order.total_amount_pay = sku.price * count # 保存订单商品数据 OrderGoods.objects.create( order=order, sku=sku, count=count, price=sku.price ) order.save() except: # 回滚到保存点 transaction.savepoint_rollback(save_point) return ErrorResponse(msg='请稍后再试') else: # 事物结束提交数据 transaction.savepoint_commit(save_point) return SuccessResponse(data={'id':order.id,'order_id':order.order_id,'total_amount':order.total_amount}, msg='success') def couponisexpire(coupon): """ coupon:CouponRecord的实例 return:True 过期,False未过期 """ receive_time = coupon.receive_time expire_days = coupon.coupon.coupon_expiretime expire_time = receive_time + datetime.timedelta(days=expire_days) now = datetime.datetime.now() if now > expire_time: return True return False class GoodsOrderCancleView(APIView): """ get: 商城订单取消 【参数】id,订单的id """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self,request): user = request.user id = get_parameter_dic(request)['id'] if not id: return ErrorResponse(msg='id参数错误') order = OrderInfo.objects.filter(id=id,user=user,status__in=[1,2]).first() if not order: return ErrorResponse(msg='该订单不存在,或该状态不支持取消') with transaction.atomic(): savepoint = transaction.savepoint() #把取消的库存增加回去 skus = order.ordergoodsskus.all() for s in skus: # 每遍历出一个sku_id,就还原一次库存和销量 for i in range(5): # (1)乐观锁step1:获取旧库存和销量 sku = SKU.objects.get(id=s.sku.id) stock_old = sku.stock count = s.count # 购买数量 sales_old = sku.sales # sku原销量 sales_spu_old = sku.spu.sales # spu原销量 # (2)、乐观锁step2:根据旧数据计算新库存和销量 —— 耗时操作 stock_new = stock_old + count sales_new = sales_old - count sales_spu_new = sales_spu_old - count # 乐观锁step3: 基于旧库存和销量过滤查找模型类对象,然后更新 # 查询集批量修改函数update有整数返回值 —— 表示被修改了的对象有几个再次按照旧的库存获取商品,获取到更新,获取不到返回再次操作(乐观锁) res = SKU.objects.filter(id=s.sku.id, stock=stock_old, sales=sales_old).update(stock=stock_new,sales=sales_new) if res == 0: # (3.2)、说明没有数据被成功修改,继而说明filter过滤出空查询集,进一步说明"根据旧库存找不到原有的sku,有别的事务介入" continue else: # (3.1)、res返回值不为0,说明,没有别的事务介入,成功修改了 for n in range(5): res2 = SPU.objects.filter(id=s.sku.spu.id,sales=sales_spu_old).update(sales=sales_spu_old) if res2 ==0: continue else: break break if res == 0 or res2==0: # 重试了6此结果都没有操作成功 # 回滚到保存点 transaction.savepoint_rollback(savepoint) return ErrorResponse(msg='服务器繁忙,请稍后再试') # 退款逻辑 if order.status == 2: orderrefund(order.id) order.status = 6 order.save() # 清除保存点 transaction.savepoint_commit(savepoint) return SuccessResponse(msg="订单取消成功") class GoodsOrderConfirmReceiveView(APIView): """ get: 商城订单确认收货 【参数】id,订单的id """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] #@method_decorator(ly_api_security) def get(self,request): user=request.user id = get_parameter_dic(request)['id'] if not id: return ErrorResponse(msg='id参数错误') order = OrderInfo.objects.filter(id=id,status=3,user=user).first() if not order: return ErrorResponse(msg='该订单不存在,或该状态不支持确认收货操作') order.status = 5 order.save() return SuccessResponse(msg="订单已完成") class GoodsOrdersListView(APIView): """ get: 前端用户获取商品订单列表 【参数】 status:获取订单的过滤状态 为空则默认返回全部、ORDER_STATUS_CHOICES = ( (1, "待支付"), (2, "待发货"), (3, "待收货"), (4, "待评价"), (5, "已完成"), (6, "已取消"), ) search:搜索订单号(非必须) """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self,request): user = request.user status = get_parameter_dic(request).get('status') search = get_parameter_dic(request).get('search') if search: if status: status = int(status) queryset = OrderInfo.objects.filter(order_id__contains=search,user=user,is_delete=False,status=status).order_by('-create_datetime') else: queryset = OrderInfo.objects.filter(order_id__contains=search,user=user, is_delete=False).order_by('-create_datetime') else: if status: status = int(status) queryset = OrderInfo.objects.filter(user=user,is_delete=False,status=status).order_by('-create_datetime') else: queryset = OrderInfo.objects.filter(user=user, is_delete=False).order_by('-create_datetime') # # 1. 实例化分页器对象 page_obj = CustomPagination() # # 2. 使用自己配置的分页器调用分页方法进行分页 page_data = page_obj.paginate_queryset(queryset, request) data = [] if queryset: for m in page_data: goods_list = [] querysets = m.ordergoodsskus.all() if querysets: for g in querysets: spec_name = "" if g.sku.spu.spec_type == 1: # 多规格 spec_names = g.sku.specs.all() for temp_spec_name in spec_names: if len(spec_names) > 1: spec_name = spec_name + temp_spec_name.option.value + ';' else: spec_name = temp_spec_name.option.value goods_list.append({ 'id': g.sku_id, 'price': g.price, 'name': g.sku.spu.name, 'count': g.count, 'spec_name':spec_name, 'default_image': g.sku.spu.default_image }) data.append({ 'id': m.id, 'order_id': m.order_id, 'total_count':m.total_count, 'total_amount': m.total_amount, 'status': m.status, 'status_name':m.get_status_display(), 'couponrecord_price':m.couponrecord_price, 'create_datetime':formatdatetime(m.create_datetime), 'goods_list':goods_list, }) return page_obj.get_paginated_response(data=data) class UserAddressSerializer(serializers.ModelSerializer): """ 用户收货地址序列化 """ class Meta: model = Address read_only_fields = ["id"] # fields = '__all__' exclude = ['is_deleted','user'] class GoodsOrdersDetailView(APIView): """ get: 前端用户获取商品订单详情 【参数】 id:订单id """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def get(self,request): user = request.user id = get_parameter_dic(request)['id'] if not id: return ErrorResponse(msg='id参数错误') m = OrderInfo.objects.filter(id=id,user=user, is_delete=False).first() data = {} if m: addresses = Address.objects.filter(id=m.address_id).first() address = UserAddressSerializer(addresses) goods_list = [] querysets = m.ordergoodsskus.all() if querysets: for g in querysets: spec_name = "" if g.sku.spu.spec_type == 1: # 多规格 spec_names = g.sku.specs.all() for temp_spec_name in spec_names: if len(spec_names) > 1: spec_name = spec_name + temp_spec_name.option.value + ';' else: spec_name = temp_spec_name.option.value goods_list.append({ 'id': g.sku_id, 'price': g.price, 'name': g.sku.spu.name, 'count': g.count, 'spec_name':spec_name, 'default_image': g.sku.spu.default_image }) data={ 'id': m.id, 'order_id': m.order_id, 'addresses': address.data, 'total_count':m.total_count, 'total_amount': m.total_amount, 'total_amount_pay':m.total_amount_pay, 'status': m.status, 'status_name':m.get_status_display(), 'couponrecord_price':m.couponrecord_price, 'create_datetime':formatdatetime(m.create_datetime), 'pay_method':m.get_pay_method_display(), 'pay_status':m.get_pay_status_display(), 'pay_time':m.pay_time, 'goods_list':goods_list, 'logistics_id': m.logistics_id, # 物流单号 'remark': m.remark, } return SuccessResponse(data=data, msg='success') class PaymentView(APIView): """ 平台支付接口 post: 平台支付接口 【参数】 paymethod: 2 微信支付 3 支付宝支付 platform:支付者平台: app 、xcx """ permission_classes = [IsAuthenticated] authentication_classes = [JWTAuthentication] def post(self, request): user = request.user if user.identity !=1: return ErrorResponse(msg="用户类型错误") paymethod = int(get_parameter_dic(request)['pay_method']) platform = get_parameter_dic(request).get('platform') paytype = get_parameter_dic(request)['pay_type'] if platform and platform not in ['app','xcx']: return ErrorResponse(msg="platform类型错误") if not platform: platform = 'app' if paymethod not in [2,3]: return ErrorResponse(msg="paymethod类型错误") money = 0 #业务逻辑 with transaction.atomic(): # 设置保存点 save_point = transaction.savepoint() try: orderno = getminrandomodernum() #其他业务逻辑 id = get_parameter_dic(request)['id'] if not id: return ErrorResponse(msg='id error1') order = OrderInfo.objects.filter(id=id,user=user,pay_status=0).first() if not order: return ErrorResponse(msg='id error2') money = order.total_amount orderno = order.order_id order.pay_method = paymethod order.save() #支付类型判断 if paymethod == 2: wxapppay = WxAppPay() if platform == 'app':#app支付 data = wxapppay.payorder(orderno, money * 100, orderno,paytype,notify_url="%s/api/app/wechatpay_notify/" % DOMAIN_HOST) # 要填写回调地址 else: openid = OAuthWXUser.objects.filter(user=user).values_list('xcx_openid', flat=True).first() if not openid: # 回滚到保存点 transaction.savepoint_rollback(save_point) data = wxapppay.payorder_jsapi(orderno, money * 100, orderno,paytype,openid,notify_url="%s/api/app/wechatpay_notify/" % DOMAIN_HOST) # 要填写回调地址 # 事物结束提交数据 transaction.savepoint_commit(save_point) return SuccessResponse(data={'id': order.id, 'order_no': order.order_id, 'paymethod': 2, 'paystatus': order.pay_status,'wechataydata': data}, msg="success") elif paymethod == 3: data = alipay_trade_app(orderno, float(money),"testpay",notify_url="%s/api/app/alipay_notify/" % DOMAIN_HOST) # 事物结束提交数据 transaction.savepoint_commit(save_point) return SuccessResponse(data={'order_no':order.order_id,'alipaydata':data}, msg="success") return ErrorResponse(msg="paytype类型错误") except Exception as e: transaction.savepoint_rollback(save_point) return ErrorResponse(msg=str(e)) class wechatpay_notify(APIView): """ 支付成功后,微信服务器异步通知回调(用于修改订单状态) """ def post(self,request): wxpay = WxAppPay() result = wxpay.decrypt_callback(request) logger.info("收到微信支付回调通知:%s" % (result)) if result: """ {"mchid":"1617830805","appid":"wx023a7d62457e3803","out_trade_no":"20220112163528260904706","transaction_id":"4200001335202201122963662545","trade_type":"JSAPI","trade_state":"SUCCESS","trade_state_desc":"支付成功","bank_type":"CMBC_DEBIT","attach":"","success_time":"2022-01-12T16:35:42+08:00","payer":{"openid":"ocBnz4pg2G05LaUFpNE9bxR2B88w"},"amount":{"total":1,"payer_total":1,"currency":"CNY","payer_currency":"CNY"}} """ resp = json.loads(result) appid = resp.get('appid') mchid = resp.get('mchid') out_trade_no = resp.get('out_trade_no') transaction_id = resp.get('transaction_id') trade_type = resp.get('trade_type') trade_state = resp.get('trade_state') trade_state_desc = resp.get('trade_state_desc') bank_type = resp.get('bank_type') attach = resp.get('attach')#自定义数据 success_time = format_wechat_gmt_8_to_normal(resp.get('success_time')) payer = resp.get('payer') amount = (resp.get('amount').get('total'))/100 # TODO: 根据返回参数进行必要的业务处理,处理完后返回200或204 if trade_state == "SUCCESS": # 支付成功 # 查询数据库中存在的订单 order = OrderInfo.objects.filter(order_id=out_trade_no, pay_status=0, pay_method=2).first() if not order: logger.info("处理收到微信支付回调通知:%s,没有找到该订单" % (result)) return HttpResponse('fail') order.trade_id = transaction_id order.pay_status = 1 order.pay_time = success_time order.save() # 其他逻辑处理 orderpaysuccess(order.id) return HttpResponse('success') else: return HttpResponse('fail') else: return HttpResponse('fail') class alipay_notify(APIView): """ 支付成功后,支付宝服务器异步通知回调(用于修改订单状态) 返回fail之后,支付宝会再回调一次,也就是失败的回调一共回调2次 """ def post(self,request): # 1. 先将sign剔除掉 processed_dict = {} for key, value in request.POST.items(): processed_dict[key] = value # 异步通知data参数转换字典后示例如下: """ {'gmt_create': '2021-11-22 08:53:05', 'charset': 'utf-8', 'seller_email': 'taijizhilu@126.com', 'subject': '20211122085257269506402001', 'sign': 'dAUOnG1u8/Fap+aDCSa+P2AXFv4vqr3BK4vKTxevai4F3sdN4X6an8GulmKjk3cx1Z9OMp05JAcBCPi+1CXJoy6opybYqr+M/uDUiAYH+MA4ilazSskS/WC22iZhS4oAVjwouGp+Wbu6pmNMM/gFNCxnlf3K+bCa/gzDDPTCTEoZT3IoeQdZ4ERmuNi7WdCCIm8jNaS8nRXS8bEkk7r7PvYs1kO3H9uZhViSKmlx+Qfklm+mRa1xheNd2UJ1pYcVGK4snlUJL4tKO/VEzPb2trFxYfI3y4q2EPBPCHcI24L1IZeZXrugx6mFJm02SntCrTA+/ysb7e59zoNrBmu+gQ==', 'buyer_id': '2088312606228550', 'invoice_amount': '0.01', 'notify_id': '2021112200222085305028551438751992', 'fund_bill_list': '[{"amount":"0.01","fundChannel":"PCREDIT"}]', 'notify_type': 'trade_status_sync', 'trade_status': 'TRADE_SUCCESS', 'receipt_amount': '0.01', 'app_id': '2021002194665673', 'buyer_pay_amount': '0.01', 'sign_type': 'RSA2', 'seller_id': '2088341077382474', 'gmt_payment': '2021-11-22 08:53:05', 'notify_time': '2021-11-22 08:56:13', 'version': '1.0', 'out_trade_no': '20211122085257269506402001', 'total_amount': '0.01', 'trade_no': '2021112222001428551424684280', 'auth_app_id': '2021002194665673', 'buyer_logon_id': '159****7057', 'point_amount': '0.00'} """ logger.error("收到支付宝回调通知:%s" % (processed_dict)) sign = processed_dict.pop("sign", None) # 2. 生成一个Alipay对象 alipay = initalipay() # 3. 进行验签,确保这是支付宝给我们的 verify_re = alipay.verify(processed_dict, sign) # 如果验签成功 if verify_re is True: order_sn = processed_dict.get('out_trade_no', None)#自己平台的订单号 trade_no = processed_dict.get('trade_no', None)#支付宝的订单号 trade_status = processed_dict.get('trade_status', None) pay_time = processed_dict.get('gmt_payment', None)#用户支付时间 if trade_status == "TRADE_SUCCESS":#支付成功 # 查询数据库中存在的订单 order = OrderInfo.objects.filter(order_id=order_sn, pay_status=0, pay_method=3).first() if not order: logger.info("处理收到支付宝支付回调通知:%s,没有找到该订单" % (processed_dict)) return HttpResponse('fail') order.trade_id = trade_no order.pay_status = 1 order.pay_time = pay_time order.save() #其他逻辑处理 orderpaysuccess(order.id) return HttpResponse('success') else: return HttpResponse('fail') else: return HttpResponse('fail') #用户支付成功处理对应逻辑 def orderpaysuccess(order_id): """ order:订单order的id(内部订单编号) """ order = OrderInfo.objects.get(id=order_id) order.status = 2 order.save() #支付成功其他业务处理