購物車
#!/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)