路飞学城购买流程API
购物车
#!/usr/bin/env python # -*- coding:utf-8 -*- import json from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from rest_framework.views import APIView from rest_framework.viewsets import ViewSetMixin from rest_framework.viewsets import ModelViewSet from rest_framework.response import Response from repository import models from api.serializer.payment import ShoppingCarSerializer from api.utils.auth.token_auth import LuffyTokenAuthentication from api.utils.auth.token_permission import LuffyPermission from api.utils import redis_pool from api.utils.exception import PricePolicyDoesNotExist class ShoppingCarView(ViewSetMixin, APIView): """ 购物车接口 """ authentication_classes = [LuffyTokenAuthentication, ] permission_classes = [LuffyPermission, ] def get(self, request, *args, **kwargs): """ 根据用户ID获取购物车所有东西 :param request: :param args: :param kwargs: :return: """ response = {'code': 1000, 'data': None} try: product_dict = redis_pool.conn.hget(settings.REDIS_SHOPPING_CAR_KEY, request.user.id) if product_dict: product_dict = json.loads(product_dict.decode('utf-8')) response['data'] = product_dict except Exception as e: response['code'] = 1001 response['msg'] = "获取购物车列表失败" return Response(response) def post(self, request, *args, **kwargs): """ # 根据课程ID获取课程信息以及相关所有价格策略 chopping_car = { request.user.id:{ course.id:{ title:'xx', img:'xx', choice_policy_id:1, price_policy_dict:{ {id:1,price:'9.9', period:'1个月'}, {id:2,price:'19.9',period:'3个月'}, {id:3,price:'59.9',period:'8个月'}, }, } }, course.id:[ title:'xx', img:'xx', choice_policy_id:1, price_policy_dict:{ {id:1,price:'9.9', period:'1个月'}, {id:2,price:'19.9',period:'3个月'}, {id:3,price:'59.9',period:'8个月'}, }, ] } } } :param request: :param args: :param kwargs: :return: """ response = {'code': 1000, 'msg': None} try: course_id = int(request.data.get('course_id')) policy_id = int(request.data.get('policy_id')) # 获取课程信息 course = models.Course.objects.exclude(course_type=2).filter(status=0).get(id=course_id) # 序列化课程信息,并获取其关联的所有价格策略 ser = ShoppingCarSerializer(instance=course, many=False) product = ser.data # 判断价格策略是否存在 policy_exist = False for policy in product['price_policy_list']: if policy['id'] == policy_id: policy_exist = True break if not policy_exist: raise PricePolicyDoesNotExist() # 设置默认选中的价格策略 product.setdefault('choice_policy_id', policy_id) # 获取当前用户在购物车中已存在的课程,如果存在则更新,否则添加新课程 product_dict = redis_pool.conn.hget(settings.REDIS_SHOPPING_CAR_KEY, request.user.id) if not product_dict: product_dict = {course_id: product} else: product_dict = json.loads(product_dict.decode('utf-8')) product_dict[course_id] = product # 将新课程写入到购物车 redis_pool.conn.hset(settings.REDIS_SHOPPING_CAR_KEY, request.user.id, json.dumps(product_dict)) except ObjectDoesNotExist as e: response['code'] = 1001 response['msg'] = '视频不存在' except PricePolicyDoesNotExist as e: response['code'] = 1002 response['msg'] = '价格策略不存在' except Exception as e: print(e) response['code'] = 1003 response['msg'] = '添加购物车失败' return Response(response) def delete(self, request, *args, **kwargs): """ 删除购物车中的课程 :param request: :param args: :param kwargs: :return: """ response = {'code': 1000} try: course_id = kwargs.get('pk') product_dict = redis_pool.conn.hget(settings.REDIS_SHOPPING_CAR_KEY, request.user.id) if not product_dict: raise Exception('购物车中无课程') product_dict = json.loads(product_dict.decode('utf-8')) if course_id not in product_dict: raise Exception('购物车中无该商品') del product_dict[course_id] redis_pool.conn.hset(settings.REDIS_SHOPPING_CAR_KEY, request.user.id, json.dumps(product_dict)) except Exception as e: response['code'] = 1001 response['msg'] = str(e) return Response(response) def put(self, request, *args, **kwargs): """ 更新购物车中的课程的默认的价格策略 :param request: :param args: :param kwargs: :return: """ response = {'code': 1000} try: course_id = kwargs.get('pk') policy_id = request.data.get('policy_id') product_dict = redis_pool.conn.hget(settings.REDIS_SHOPPING_CAR_KEY, request.user.id) if not product_dict: raise Exception('购物车清单不存在') product_dict = json.loads(product_dict.decode('utf-8')) if course_id not in product_dict: raise Exception('购物车清单中商品不存在') policy_exist = False for policy in product_dict[course_id]['price_policy_list']: if policy['id'] == policy_id: policy_exist = True break if not policy_exist: raise PricePolicyDoesNotExist() product_dict[course_id]['choice_policy_id'] = policy_id redis_pool.conn.hset(settings.REDIS_SHOPPING_CAR_KEY, request.user.id, json.dumps(product_dict)) except PricePolicyDoesNotExist as e: response['code'] = 1001 response['msg'] = '价格策略不存在' except Exception as e: response['code'] = 1002 response['msg'] = str(e) return Response(response)
结算
#!/usr/bin/env python # -*- coding:utf-8 -*- import json import datetime from django.conf import settings from rest_framework.views import APIView from rest_framework.response import Response from api.utils.auth.token_auth import LuffyTokenAuthentication from api.utils.auth.token_permission import LuffyPermission from api.utils import redis_pool from repository import models class PaymentView(APIView): """ 去结算接口 """ authentication_classes = [LuffyTokenAuthentication, ] permission_classes = [LuffyPermission, ] def get(self, request, *args, **kwargs): """ 获取结算列表 :param request: :param args: :param kwargs: :return: """ response = {'code': 1000} try: # 结算商品列表 payment_list = redis_pool.conn.hget(settings.REDIS_PAYMENT_KEY, request.user.id) if not payment_list: raise Exception() response['data'] = { 'payment_list': json.loads(payment_list.decode('utf-8')), # 结算信息(课程、价格和优惠券) "balance": request.user.balance # 个人贝里账户,可使用贝里金额 } except Exception as e: response['code'] = 1001 response['msg'] = "结算列表为空" return Response(response) def post(self, request, *args, **kwargs): """ 去结算 方案一(示例):用户提交课程id,去redis购物车中获取其选好的价格策略,再次检测课程和价格策略的合法性。 PS: 直接购买时,需要先加入购物车,再立即去结算 方案二:用户提交课程id和价格策略id,去数据库验证其合法性。 PS: 直接购买时,直接去结算 user.id: { policy_course_dict:{ 课程ID:{ 'course_id': course_id, 'course_name': product['name'], 'course_img': product['course_img'], 'policy_id': product['choice_policy_id'], 'policy_price': policy_price, 'policy_': policy_period, 'coupon_record_list': [ {'id': 0, 'text': '请选择优惠券'}, {'id': 1, 'type':1, 'text': '优惠券1', ..}, {'id': 2, 'type':2, 'text': '优惠券1', ..}, {'id': 3, 'type':3, 'text': '优惠券1', ..}, ], }, 课程ID:{ 'course_id': course_id, 'course_name': product['name'], 'course_img': product['course_img'], 'policy_id': product['choice_policy_id'], 'policy_price': policy_price, 'policy_': policy_period, 'coupon_record_list': [ {'id': 0, 'text': '请选择优惠券'}, {'id': 1, 'type':1, 'text': '优惠券1', ..}, {'id': 2, 'type':2, 'text': '优惠券1', ..}, {'id': 3, 'type':3, 'text': '优惠券1', ..}, ], } }, global_coupon_dict:{ 1:{'type': 0, 'text': "通用优惠券", 'id': 1, ..}, 2:{'type': 0, 'text': "通用优惠券", 'id': 2, ..}, 3:{'type': 0, 'text': "通用优惠券", 'id': 3, ...}, 4:{'type': 0, 'text': "通用优惠券", 'id': 4, ...}, } } :param request: :param args: :param kwargs: :return: """ response = {'code': 1001} try: """ 1. 获取要支付的课程ID 2. 检查购物车中是否存在,不存在则报错 循环用户提交的课程ID,去购物车中获取,如果不存在,就报错。 """ # 获取用户提交的课程id course_id_list = request.data.get('course_list') if not course_id_list or not isinstance(course_id_list, list): raise Exception('请选择要结算的课程') # 购物车中检查是否已经有课程(应该有课程的) product_dict = redis_pool.conn.hget(settings.REDIS_SHOPPING_CAR_KEY, request.user.id) if not product_dict: raise Exception('购物车无课程') # 购物车中是否有用户要购买的课程 product_dict = json.loads(product_dict.decode('utf-8')) # ###### 课程、价格和优惠券 ####### policy_course_dict = {} for course_id in course_id_list: course_id = str(course_id) product = product_dict.get(course_id) if not product: raise Exception('购买的课程必须先加入购物车') policy_exist = False for policy in product['price_policy_list']: if policy['id'] == product['choice_policy_id']: policy_price = policy['price'] policy_period = policy['period'] policy_valid_period = policy['valid_period'] policy_exist = True break if not policy_exist: raise Exception('购物车中的课程无此价格') policy_course = { 'course_id': course_id, 'course_name': product['name'], 'course_img': product['course_img'], 'policy_id': product['choice_policy_id'], 'policy_price': policy_price, 'policy_period': policy_period, 'policy_valid_period': policy_valid_period, 'coupon_record_list': [ {'id': 0, 'text': '请选择优惠券'}, ], } policy_course_dict[course_id] = policy_course # 获取当前所有优惠券 user_coupon_list = models.CouponRecord.objects.filter(account=request.user, status=0) # ###### 全局优惠券 ####### global_coupon_record_dict = {} # 课程优惠券添加到课程中;全局优惠券添加到全局 current_date = datetime.datetime.now().date() for record in user_coupon_list: # 检查优惠券是否已经过期 begin_date = record.coupon.valid_begin_date end_date = record.coupon.valid_end_date if begin_date: if current_date < begin_date: continue if end_date: if current_date > end_date: continue # 全局优惠券 if not record.coupon.content_type: if record.coupon.coupon_type == 0: temp = {'type': 0, 'text': "通用优惠券", 'id': record.id, 'begin_date': begin_date, 'end_date': end_date, 'money_equivalent_value': record.coupon.money_equivalent_value} elif record.coupon.coupon_type == 1: temp = {'type': 1, 'text': "满减券", 'id': record.id, 'begin_date': begin_date, 'end_date': end_date, 'minimum_consume': record.coupon.minimum_consume, 'money_equivalent_value': record.coupon.money_equivalent_value} elif record.coupon.coupon_type == 2: temp = {'type': 2, 'text': "折扣券", 'id': record.id, 'begin_date': begin_date, 'end_date': end_date, 'off_percent': record.coupon.off_percent} else: continue global_coupon_record_dict[record.id] = temp # 课程优惠券 else: cid = record.coupon.object_id if record.coupon.content_type.model == 'course' and cid in policy_course_dict: # 课程价格:满减,打折,通用 if record.coupon.coupon_type == 0: temp = {'type': 0, 'text': "通用优惠券", 'id': record.id, 'begin_date': begin_date, 'end_date': end_date, 'money_equivalent_value': record.coupon.money_equivalent_value} elif record.coupon.coupon_type == 1 and policy_course_dict[cid][ 'policy_price'] >= record.coupon.minimum_consume: temp = {'type': 1, 'text': "满减券", 'id': record.id, 'begin_date': begin_date, 'end_date': end_date, 'minimum_consume': record.coupon.minimum_consume, 'money_equivalent_value': record.coupon.money_equivalent_value} elif record.coupon.coupon_type == 2: temp = {'type': 2, 'text': "折扣券", 'id': record.id, 'begin_date': begin_date, 'end_date': end_date, 'off_percent': record.coupon.off_percent} else: continue policy_course_dict[cid]['coupon_record_list'].append(temp) user_pay = { 'policy_course_dict': policy_course_dict, 'global_coupon_record_dict': global_coupon_record_dict } redis_pool.conn.hset(settings.REDIS_PAYMENT_KEY, request.user.id, json.dumps(user_pay)) except Exception as e: response['code'] = 1002 response['msg'] = str(e) return Response(response)
去支付
#!/usr/bin/env python # -*- coding:utf-8 -*- import json import time import random import datetime from django.conf import settings from django.db import transaction from django.db.models import F from rest_framework.views import APIView from rest_framework.response import Response from api.utils.auth.token_auth import LuffyTokenAuthentication from api.utils.auth.token_permission import LuffyPermission from api.utils import redis_pool from api.utils.alipay import AliPay from repository import models def generate_order_num(): """ 生成订单编号, 且必须唯一 :return: """ while True: order_num = time.strftime('%Y%m%d%H%M%S', time.localtime()) + str(random.randint(111, 999)) if not models.Order.objects.filter(order_number=order_num).exists(): break return order_num def generate_transaction_num(): """ 生成流水编号, 且必须唯一 :return: """ while True: transaction_number = time.strftime('%Y%m%d%H%M%S', time.localtime()) + str(random.randint(111, 999)) if not models.TransactionRecord.objects.filter(transaction_number=transaction_number).exists(): break return transaction_number class PayOrderView(APIView): authentication_classes = [LuffyTokenAuthentication, ] permission_classes = [LuffyPermission, ] def post(self, request, *args, **kwargs): """ 去支付,生成订单。 获取前端提交的购买信息 { course_price_list:[ {'policy_id':1, '':'course_id':1, 'coupon_record_id':1}, {'policy_id':2, '':'course_id':2, 'coupon_record_id':2}, ], coupon_record_id:1, alipay: 99, balance: 1 } 1. 用户提交 - balance - alipay 2. 获取去结算列表 课程 3. 循环所有课程 - 获取原价 - 抵扣的钱 :param request: :param args: :param kwargs: :return: """ response = {'code': 1000} try: # 用户请求验证 policy_course_list = request.data.get('course_price_list') coupon_record_id = request.data.get('coupon_record_id') alipay = request.data.get('alipay') # >= 0 balance = request.data.get('balance') # >= 0 if balance > request.user.balance: raise Exception('账户中贝里余额不足') # 检查用户提交的信息在 redis结算列表 中是否存在,如果不存在,则需要用户从购物车中再次去结算 payment_dict_bytes = redis_pool.conn.hget(settings.REDIS_PAYMENT_KEY, request.user.id) payment_dict = json.loads(payment_dict_bytes.decode('utf-8')) policy_course_dict = payment_dict['policy_course_dict'] global_coupon_record_dict = payment_dict['global_coupon_record_dict'] global_coupon_record = {} # 全局优惠券 if coupon_record_id: if coupon_record_id not in global_coupon_record_dict: raise Exception('全局优惠券在缓存中不存在') global_coupon_record = global_coupon_record_dict[coupon_record_id] # 当前时间 current_date = datetime.datetime.now().date() current_datetime = datetime.datetime.now() # 原价 total_price = 0 # 总抵扣的钱 discount = 0 # 使用优惠券ID列表 if coupon_record_id: use_coupon_record_id_list = [coupon_record_id, ] else: use_coupon_record_id_list=[] # 课程和优惠券 buy_course_record = [] for cp in policy_course_list: _policy_id = cp['policy_id'] _course_id = cp['course_id'] _coupon_record_id = cp['coupon_record_id'] temp = { 'course_id': _course_id, 'course_name': "course", 'valid_period': 0, # 有效期:30 'period': 0, # 有效期:一个月 'original_price': 0, 'price': 0, } if str(_course_id) not in policy_course_dict: raise Exception('课程在缓存中不存在') redis_course = policy_course_dict[str(_course_id)] if str(_policy_id) != str(redis_course['policy_id']): raise Exception('价格策略在缓存中不存在') # 课程是否已经下线或价格策略被修改 policy_object = models.PricePolicy.objects.get(id=_policy_id) # 价格策略对象 course_object = policy_object.content_object # 课程对象 if course_object.id != _course_id: raise Exception('课程和价格策略对应失败') if course_object.status != 0: raise Exception('课程已下线,无法购买') # 选择的优惠券是否在缓存中 redis_coupon_list = redis_course['coupon_record_list'] redis_coupon_record = None for item in redis_coupon_list: if item['id'] == _coupon_record_id: redis_coupon_record = item break if not redis_coupon_record: raise Exception('单课程优惠券在缓存中不存在') # 计算购买原总价 total_price += policy_object.price # 未使用单课程优惠券 if redis_coupon_record['id'] == 0: temp['price'] = policy_object.price buy_course_record.append(temp) continue temp['original_price'] = policy_object.price temp['valid_period'] = redis_coupon_record['policy_valid_period'] temp['period'] = redis_coupon_record['policy_period'] # 缓存中的优惠券是否已经过期 begin_date = redis_coupon_record.get('begin_date') end_date = redis_coupon_record.get('end_date') if begin_date: if current_date < begin_date: raise Exception('优惠券使用还未到时间') if end_date: if current_date > end_date: raise Exception('优惠券已过期') # 使用的是单课程优惠券抵扣了多少钱;使用的 个人优惠券ID if redis_coupon_record['type'] == 0: # 通用优惠券 money = redis_coupon_record['money_equivalent_value'] discount += money elif redis_coupon_record['type'] == 1: # 满减券 money = redis_coupon_record['money_equivalent_value'] minimum_consume = redis_coupon_record['minimum_consume'] if policy_object.price >= minimum_consume: discount += money elif redis_coupon_record['type'] == 2: # 打折券 money = policy_object.price * redis_coupon_record['off_percent'] discount += money temp['price'] = policy_object.price - money buy_course_record.append(temp) use_coupon_record_id_list.append(redis_coupon_record['id']) # 全局优惠券 print(global_coupon_record) begin_date = global_coupon_record.get('begin_date') end_date = global_coupon_record.get('end_date') if begin_date: if current_date < begin_date: raise Exception('优惠券使用还未到时间') if end_date: if current_date > end_date: raise Exception('优惠券已过期') # 使用全局优惠券抵扣了多少钱 if global_coupon_record.get('type') == 0: # 通用优惠券 money = global_coupon_record['money_equivalent_value'] discount += money elif global_coupon_record.get('type') == 1: # 满减券 money = global_coupon_record['money_equivalent_value'] minimum_consume = global_coupon_record['minimum_consume'] if (total_price - discount) >= minimum_consume: discount += money elif global_coupon_record.get('type') == 2: # 打折券 money = (total_price - discount) * global_coupon_record['off_percent'] discount += money # 贝里抵扣的钱 if balance: discount += balance if (alipay + discount) != total_price: raise Exception('总价、优惠券抵扣、贝里抵扣和实际支付的金额不符') # 创建订单 + 支付宝支付 # 创建订单详细 # 贝里抵扣 + 贝里记录 # 优惠券状态更新 actual_amount = 0 if alipay: payment_type = 1 # 支付宝 actual_amount = alipay elif balance: payment_type = 3 # 贝里 else: payment_type = 2 # 优惠码 with transaction.atomic(): order_num = generate_order_num() if payment_type == 1: order_object = models.Order.objects.create( payment_type=payment_type, order_number=order_num, account=request.user, actual_amount=actual_amount, status=1, # 待支付 ) else: order_object = models.Order.objects.create( payment_type=payment_type, order_number=order_num, account=request.user, actual_amount=actual_amount, status=0, # 支付成功,优惠券和贝里已够支付 pay_time=current_datetime ) for item in buy_course_record: detail = models.OrderDetail.objects.create( order=order_object, content_object=models.Course.objects.get(id=item['course_id']), original_price=item['original_price'], price=item['price'], valid_period_display=item['period'], valid_period=item['valid_period'] ) models.Account.objects.filter(id=request.user.id).update(balance=F('balance') - balance) models.TransactionRecord.objects.create( account=request.user, amount=request.user.balance, balance=request.user.balance - balance, transaction_type=1, content_object=order_object, transaction_number=generate_transaction_num() ) effect_row = models.CouponRecord.objects.filter(id__in=use_coupon_record_id_list).update( order=order_object, used_time=current_datetime) if effect_row != len(use_coupon_record_id_list): raise Exception('优惠券使用失败') response['payment_type'] = payment_type # 生成支付宝URL地址 if payment_type == 1: pay = AliPay(debug=True) query_params = pay.direct_pay( subject="路飞学城", # 商品简单描述 out_trade_no=order_num, # 商户订单号 total_amount=actual_amount, # 交易金额(单位: 元 保留俩位小数) ) pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params) response['pay_url'] = pay_url except IndentationError as e: response['code'] = 1001 response['msg'] = str(e) return Response(response)