使用第三方支付大概就是按照第三方支付平台要求按步骤进行申请订单号、appid等,定义回调函数,然后加上自己数据库操作的逻辑组成
购物车设计
- 购物车 由于商品可以增加数量,这里用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
113class 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
34class 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) - serializerviews.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
39
40
41
42
43
44
45
46
47
48from 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__"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
38class 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
148from 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)