Django+Vue开发生鲜电商平台之10.购物车、订单管理和支付功能

@[toc]

很多机遇是外界赋予的,这方面我们自己觉得很幸运,所以更加不能浪费这个机会,应该想得更多。而不能说你现在得到的是自然的,别人打不赢你,我们从来都会很担心,不会觉得自己很强。 ——马化腾

Github和Gitee代码同步更新https://github.com/PythonWebProject/Django_Fresh_Ecommercehttps://gitee.com/Python_Web_Project/Django_Fresh_Ecommerce

一、购物车功能实现

1.加入购物车功能实现

购物车需要实现在商品详情页面将该商品加入购物车后,右上角同步显示,并且点击去结算会同步显示,并且价格与数量同步,具体包括了增删改查等操作,在apps/trade中实现。

在spps/trade下新建serializers.py如下:

1from rest_framework import serializers 2 3from goods.models import Goods 4from .models import ShoppingCart 5 6class ShoppingCartSerializer(serializers.Serializer): 7 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) 8 nums = serializers.IntegerField(required=True, min_value=1, label='数量', 9 error_messages={ 10 'required': '请选择商品数量', 11 'min_value': '商品数量至少为1' 12 }) 13 goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.filter(is_delete=False)) 14 15 def create(self, validated_data): 16 '''新增数据''' 17 user = self.context['request'].user 18 nums = validated_data['nums'] 19 goods = validated_data['goods'] 20 existed = ShoppingCart.objects.filter(is_delete=False, user=user, goods=goods) 21 if existed: 22 existed = existed[0] 23 existed.nums += 1 24 existed.save() 25 else: 26 existed = ShoppingCart.objects.create(**validated_data) 27 return existed

模型修改如下:

1class ShoppingCart(models.Model): 2 '''购物车''' 3 user = models.ForeignKey(User, verbose_name='用户', null=True, on_delete=models.SET_NULL) 4 goods = models.ForeignKey(Goods, verbose_name='商品', null=True, on_delete=models.SET_NULL) 5 nums = models.IntegerField(default=0, verbose_name='商品数量') 6 7 add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间') 8 is_delete = models.BooleanField(default=False, verbose_name='是否删除') 9 10 class Meta: 11 verbose_name = '购物车' 12 verbose_name_plural = verbose_name 13 unique_together = ('user', 'goods') 14 15 def __str__(self): 16 return '%s(%d)'.format(self.goods.name, self.nums)

因为在一个用户的购物车中的一个商品是唯一的,因此需要给ShoppingCart模型增加unique_together = ('user', 'goods')约束,并且导致了在定义序列化时只能继承自Serializer而不能继承自ModelSerializer,因为在ModelSerializer中的create()方法可能因为插入数据时重复而验证不通过导致抛出异常,无法继续执行,而继承自Serializer有更高的灵活性代码复用性

apps/trade/views.py中定义视图如下:

1from rest_framework import viewsets 2from rest_framework.permissions import IsAuthenticated 3from rest_framework.authentication import SessionAuthentication 4from rest_framework_jwt.authentication import JSONWebTokenAuthentication 5 6from utils.permissions import IsOwnerOrReadOnly 7from .serializers import ShoppingCartSerializer 8from .models import ShoppingCart 9 10# Create your views here. 11 12 13class ShoppingCartViewSet(viewsets.ModelViewSet): 14 ''' 15 list: 16 购物车列表 17 create: 18 加入购物车 19 update: 20 购物车修改 21 delete: 22 删除购物车 23 ''' 24 25 permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] 26 authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication] 27 serializer_class = ShoppingCartSerializer 28 29 def get_queryset(self): 30 return ShoppingCart.objects.filter(user=self.request.user, is_delete=False)

为了获取购物车列表,需要在视图中实现get_queryset()方法。

urls.py中定义路由如下:

1# 配置购物车路由 2router.register(r'shopcarts', ShoppingCartViewSet, basename='shopcarts')

API测试如下: django personal center shoppingcart api

显然可以新增和更新数据。

2.修改购物车数量功能实现

因为定义的ShoppingCartSerializer继承自Serializer,而Serializer又继承自BaseSerializer,BaseSerializer中声明了update(instance, validated_data)方法但是直接抛出异常,并且Serializer中并未重写该方法,因此如果在ShoppingCartSerializer中不重写该方法会导致在修改购物车详情时会抛出异常,如果继承自ModelSerializer则不需要重写,在ShoppingCartSerializer中重写update(instance, validated_data)方法如下:

1class ShoppingCartSerializer(serializers.Serializer): 2 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) 3 nums = serializers.IntegerField(required=True, min_value=1, label='数量', 4 error_messages={ 5 'required': '请选择商品数量', 6 'min_value': '商品数量至少为1' 7 }) 8 goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.filter(is_delete=False)) 9 10 def create(self, validated_data): 11 '''新增数据''' 12 user = self.context['request'].user 13 nums = validated_data['nums'] 14 goods = validated_data['goods'] 15 existed = ShoppingCart.objects.filter(is_delete=False, user=user, goods=goods) 16 if existed: 17 existed = existed[0] 18 existed.nums += 1 19 existed.save() 20 else: 21 existed = ShoppingCart.objects.create(**validated_data) 22 return existed 23 24 def update(self, instance, validated_data): 25 # 修改购物车商品数量 26 instance.nums = validated_data['nums'] 27 instance.save() 28 return instance

再次访问测试修改和删除某一个购物车记录如下: django personal center shoppingcart api single update delete

此时,已实现修改和删除购物车记录详情。

3.和Vue结合实现购物车功能

可以看到,在购物车中会显示商品详情,因此需要再定义一个序列化实现商品详情动态显示:

1class ShoppingCartDetailSerializer(serializers.ModelSerializer): 2 goods = GoodsSerializer(many=False) 3 4 class Meta: 5 model = ShoppingCart 6 fields = '__all__'

views.py如下:

1class ShoppingCartViewSet(viewsets.ModelViewSet): 2 ''' 3 list: 4 购物车列表 5 create: 6 加入购物车 7 update: 8 购物车修改 9 delete: 10 删除购物车 11 ''' 12 13 permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] 14 authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication] 15 serializer_class = ShoppingCartSerializer 16 lookup_field = 'goods_id' 17 18 def get_serializer_class(self): 19 if self.action == 'list': 20 return ShoppingCartDetailSerializer 21 else: 22 return ShoppingCartSerializer 23 24 def get_queryset(self): 25 return ShoppingCart.objects.filter(user=self.request.user, is_delete=False)

现在访问购物车列表,如下: 购物商品详情

显然,购物车的商品详情已显示出来。

此时再看前端,在src/views/productDetail/productDetail.vue中:

1<li class="skunum_li cle"> 2 <span class="lbl">&nbsp;&nbsp;&nbsp;</span> 3 <div class="skunum" id="skunum"> <span class="minus" title="减少1个数量" @click="reduceNum"><i class="iconfont">-</i></span> 4 <input id="number" name="number" type="text" min="1" v-model="buyNum" onchange="countNum(0)"> 5 <span class="add" title="增加1个数量" @click="addNum"><i class="iconfont">+</i></span> <cite class="storage"></cite> 6 </div> 7 <div class="skunum" id="skunum"> 8 9 <cite class="storage">(<font id="shows_number">{{proDetail.goods_num}}件</font>)</cite> 10 11 </div> 12</li> 13<li class="add_cart_li"> 14 <a class="btn" id="buy_btn" @click="addShoppingCart"> 15 <i class="iconfont">&#xe600;</i> 16 加入购物车</a> 17</li> 18 19addShoppingCart () { //加入购物车 20 addShopCart({ 21 goods: this.productId, // 商品id 22 nums: this.buyNum, // 购买数量 23 }).then((response)=> { 24 this.$refs.model.setShow(); 25 // 更新store数据 26 this.$store.dispatch('setShopList'); 27 28 }).catch(function (error) { 29 console.log(error); 30 }); 31}, 32 33

在添加购物车时,调用addShoppingCart()方法,并调用addShopCart实现数据交互。

在src/views/head/head.vue中:

1<div class="hd_cart" id="ECS_CARTINFO" @mouseover="overShopCar" @mouseout="outShopCar"> 2 <router-link class="tit" :to="'/app/shoppingcart/cart'" target = _blank> 3 4 <b class="iconfont">&#xe600;</b>去购物车结算<span><i class="iconfont">&#xe645;</i></span> 5 <em class="num" id="hd_cartnum" style="visibility: visible;">{{goods_list.goods_list.length}}</em></router-link> 6 <div class="list" v-show="showShopCar"> 7 <div class="data"> 8 <dl v-for="(item,index) in goods_list.goods_list"> 9 <dt><router-link :to="'/app/home/productDetail/'+item.goods.id" target = _blank><img :src="item.goods.goods_front_image"></router-link></dt> 10 <dd> 11 <h4><router-link :to="'/app/home/productDetail/'+item.goods.id" target = _blank>{{item.goods.name}}</router-link></h4> 12 <p><span class="red">{{item.goods.shop_price}}</span>&nbsp;<i>X</i>&nbsp;{{item.nums}}</p> 13 <a title="删除" class="iconfont del" @click="deleteGoods(index,item.goods.id)">×</a></dd> 14 </dl> 15 </div> 16 <div class="count"><span class="red" id="hd_cart_count">{{goods_list.length}}</span>件商品哦~ 17 <p>总价:<span class="red"><em id="hd_cart_total">{{goods_list.totalPrice}}</em></span> 18 <router-link class="btn" :to="'/app/shoppingcart/cart'" target = _blank>去结算 19 </router-link> 20 </p> 21 </div> 22 </div> 23</div>

显示出购物车中的商品信息,并计算总价,并显示去结算的链接。

src/views/cart/cart.vue如下:

1<div class="cart-box" id="cart-box"> 2 <div class="hd"> <span class="no2" id="itemsnum-top">{{goods.goods_list.length}}件商品</span> 3 <span class="no4">单价</span> <span>数量</span> <span>小计</span> 4 </div> 5 <div class="goods-list"> 6 <ul> 7 <li class="cle hover" style="border-bottom-style: none;" v-for="(item,index) in goods.goods_list"> 8 <div class="pic"> 9 <a target="_blank"> <img :alt="item.goods.name" :src="item.goods.goods_front_image"></a> 10 </div> 11 <div class="name"> 12 <a target="_blank">{{item.goods.name}}</a> 13 <p></p> 14 </div> 15 <div class="price-xj"> 16 <p><em>¥{{item.goods.shop_price}}元</em></p> 17 </div> 18 <div class="nums" id="nums"> 19 <span class="minus" title="减少1个数量" @click="reduceCartNum(index, item.goods.id);">-</span> 20 <input type="text" v-model="item.nums" > 21 <span class="add" title="增加1个数量" @click="addCartNum(index, item.goods.id);">+</span> 22 </div> 23 <div class="price-xj"><span></span> 24 <em id="total_items_3137">¥{{item.goods.shop_price * item.nums}}元</em> 25 </div> 26 <div class="del"> 27 <a class="btn-del" @click="deleteGoods(index, item.goods.id)">删除</a> 28 </div> 29 </li> 30 </ul> 31 </div> 32 33 <div class="fd cle"> 34 <div class="fl"> 35 <p class="no1"> <a id="del-all" @click="delAll">清空购物车</a> </p> 36 <p><a class="graybtn" @click="continueShopping">继续购物</a></p> 37 </div> 38 <div class="fr" id="price-total"> 39 <p><span id="selectedCount">{{goods.goods_list.length}}</span>件商品,总价:<span class="red"><strong id="totalSkuPrice">¥{{totalPrice}}元</strong></span></p> 40 </div> 41 <div class="extr"> 42 <div class="address"> 43 <p class="title">配送地址</p> 44 <ul> 45 <li class="add" @click="addAddr"> 46 <router-link :to="'/app/home/member/receive'" target = _blank> 47 + 48 点击添加地址</router-link> 49 </li> 50 <li v-for="item in addrInfo" :class="{'addressActive':addressActive==item.id}" @click="selectAddr(item.id)"> 51 <p class="item">地址:{{item.province}} {{item.city}} {{item.district}} {{item.address}}</p> 52 <p class="item">电话:{{item.signer_mobile}}</p> 53 <p class="item">姓名:{{item.signer_name}}</p> 54 </li> 55 </ul> 56 </div> 57 <div class="pay"> 58 <p class="title">选择支付方式</p> 59 <p class="payWrap"><img v-for="item in payWrapList" src="../../static/images/alipay.jpg" :class="{'payWrapActive':payWrapActive==item.id}" @click="selectPay(item.id)"></p> 60 </div> 61 </div> 62 <textarea type="text" v-model="post_script" placeholder="请输入留言" style="margin-top: 10px; height:50px;width: 100%;"> 63 </textarea> 64 <p class="sumup"><a class="btn" @click="balanceCount">去结算</a></p> 65 </div> 66</div> 67 68created () { 69 // 请求购物车商品 70 getShopCarts().then((response)=> { 71 console.log(response.data) 72 // 更新store数据 73 //this.goods_list = response.data; 74 var totalPrice = 0 75 this.goods.goods_list = response.data; 76 response.data.forEach(function(entry) { 77 totalPrice += entry.goods.shop_price*entry.nums 78 console.log(entry.goods.shop_price); 79 }); 80 81 this.goods.totalPrice = totalPrice 82 this.totalPrice = totalPrice 83 }).catch(function (error) { 84 }); 85 this.getAllAddr () 86 87}, 88 89addCartNum(index, id) { //添加数量 90updateShopCart(id,{ 91 nums: this.goods.goods_list[index].nums+1 92}).then((response)=> { 93 this.goods.goods_list[index].nums = this.goods.goods_list[index].nums + 1; 94 // 更新store数据 95 this.$store.dispatch('setShopList'); 96 //更新总价 97 this.setTotalPrice(); 98 99}).catch(function (error) { 100 console.log(error); 101}); 102}, 103setTotalPrice(){ 104var goods_list = this.goods.goods_list; 105var totalPrice = 0; 106for(var i = 0;i<goods_list.length;i++){ 107 totalPrice=totalPrice+goods_list[i].nums* goods_list[i].goods.shop_price; 108} 109this.totalPrice = totalPrice; 110}, 111deleteGoods(index,id) { //移除购物车 112alert('您确定把该商品移除购物车吗'); 113deleteShopCart(id).then((response)=> { 114 console.log(response.data); 115 this.goods.goods_list.splice(index,1); 116 117 // 更新store数据 118 this.$store.dispatch('setShopList'); 119 120}).catch(function (error) { 121 console.log(error); 122}); 123}, 124reduceCartNum(index, id) { //删除数量 125if(this.goods.goods_list[index].nums<=1){ 126 this.deleteGoods(index, id) 127}else{ 128 updateShopCart(id,{ 129 nums: this.goods.goods_list[index].nums-1 130 }).then((response)=> { 131 this.goods.goods_list[index].nums = this.goods.goods_list[index].nums - 1; 132 // 更新store数据 133 this.$store.dispatch('setShopList'); 134 //更新总价 135 this.setTotalPrice(); 136 137 }).catch(function (error) { 138 console.log(error); 139 }); 140} 141 142 143}, 144continueShopping () { // 继续购物 145this.$router.push({name: 'index'}); 146}, 147delAll () { //清空购物车 148 149this.$http.post('/shoppingCart/clear', { 150 151}).then((response)=> { 152 console.log(response.data); 153 this.goods.goods_list.splice(0, this.goods.goods_list.length); 154 // 更新store数据 155 this.$store.dispatch('setShopList'); 156 157}).catch(function (error) { 158 console.log(error); 159}); 160}, 161selectPay(id){ 162this.payWrapActive = id; 163}, 164getAllAddr () { //获得所有配送地址 165getAddress().then((response)=> { 166 this.addrInfo = response.data; 167}).catch(function (error) { 168 console.log(error); 169}); 170}, 171addAddr () { //添加地址 172 173}, 174selectAddr (id) { //选择配送地址 175this.addressActive = id; 176var cur_address = '' 177var cur_name = '' 178var cur_mobile = '' 179this.addrInfo.forEach(function(addrItem) { 180 if(addrItem.id == id){ 181 cur_address = addrItem.province+addrItem.city+addrItem.district+addrItem.address 182 cur_name = addrItem.signer_name 183 cur_mobile = addrItem.signer_mobile 184 } 185}); 186this.address = cur_address 187this.signer_mobile = cur_mobile 188this.signer_name = cur_name 189}, 190balanceCount () { // 结算 191 if(this.addrInfo.length==0){ 192 alert("请选择收货地址") 193 }else{ 194 createOrder( 195 { 196 post_script:this.post_script, 197 address:this.address, 198 signer_name:this.signer_name, 199 singer_mobile:this.signer_mobile, 200 order_mount:this.totalPrice 201 } 202 ).then((response)=> { 203 alert('订单创建成功') 204 window.location.href=response.data.alipay_url; 205 }).catch(function (error) { 206 console.log(error); 207 }); 208 } 209},

初始化时调用getShopCarts接口获取所有购物车记录,并调用getAllAddr()获取收货地址,再通过for循环见给购物车里路和收货地址显示出来;减少商品数量调用reduceCartNum(index, id)方法,分情况调用deleteGoods(index, id)方法和updateShopCart接口;增加商品数量调用addCartNum(index, id)方法,调用updateShopCart接口实现数据交互;删除记录调用deleteGoods(index,id)方法,通过deleteShopCart接口实现数据交互;清空购物车通过delAll()方法实现;继续购物通过continueShopping()方法实现;通过调用addAddr()方法实现添加收货地址;通过selectAddr(id)方法选择收货地址;调用selectPay(id)方法选择支付方式;并调用balanceCount()进行结算。

api.js修改接口如下:

1//获取购物车商品 2export const getShopCarts = params => { return axios.get(`${local_host}/shopcarts/`) } 3// 添加商品到购物车 4export const addShopCart = params => { return axios.post(`${local_host}/shopcarts/`, params) } 5//更新购物车商品信息 6export const updateShopCart = (goodsId, params) => { return axios.patch(`${local_host}/shopcarts/`+goodsId+'/', params) } 7//删除某个商品的购物记录 8export const deleteShopCart = goodsId => { return axios.delete(`${local_host}/shopcarts/`+goodsId+'/') }

演示如下: django personal center shoppingcart operate

此时,可以实现购物车的基本功能。

二、订单功能实现

1.订单管理接口

OrderInfo模型中,有order_sn字段表示订单编号,是在提交订单之后生成的,因此在创建记录时应该允许为空,OrderInfo模型修改如下:

1class OrderInfo(models.Model): 2 '''订单信息''' 3 ORDER_STATUS = ( 4 ('success', '成功'), 5 ('cancel', '取消'), 6 ('paying', '待支付'), 7 ) 8 user = models.ForeignKey(User, verbose_name='用户', null=True, on_delete=models.SET_NULL) 9 order_sn = models.CharField(max_length=30, unique=True, null=True, blank=True, verbose_name='订单号') 10 trade_no = models.CharField(max_length=50, unique=True, null=True, blank=True, verbose_name='交易号') 11 pay_status = models.CharField(max_length=100, default='paying', choices=ORDER_STATUS, verbose_name='订单状态') 12 post_script = models.CharField(max_length=11, verbose_name='订单留言') 13 order_mount = models.FloatField(default=0.0, verbose_name='订单金额') 14 pay_time = models.DateTimeField(null=True, blank=True, verbose_name='支付时间') 15 16 # 用户基本信息 17 address = models.CharField(max_length=100, default='', verbose_name='收货地址') 18 signer_name = models.CharField(max_length=20, default='', verbose_name='签收人') 19 signer_mobile = models.CharField(max_length=11, verbose_name='联系电话') 20 21 add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间') 22 is_delete = models.BooleanField(default=False, verbose_name='是否删除') 23 24 class Meta: 25 verbose_name = u"订单" 26 verbose_name_plural = verbose_name 27 28 def __str__(self): 29 return str(self.order_sn) 30 31 32class OrderGoods(models.Model): 33 '''订单商品详情''' 34 order = models.ForeignKey(OrderInfo, verbose_name='订单信息', null=True, on_delete=models.CASCADE) 35 goods = models.ForeignKey(Goods, verbose_name='商品', null=True, on_delete=models.SET_NULL) 36 goods_num = models.IntegerField(default=0, verbose_name='商品数量') 37 38 add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间') 39 is_delete = models.BooleanField(default=False, verbose_name='是否删除') 40 41 class Meta: 42 verbose_name = '订单商品' 43 verbose_name_plural = verbose_name 44 45 def __str__(self): 46 return str(self.order.order_sn)

定义序列化如下:

1class OrderSerializer(serializers.ModelSerializer): 2 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) 3 pay_status = serializers.CharField(read_only=True) 4 trade_no = serializers.CharField(read_only=True) 5 order_sn = serializers.CharField(read_only=True) 6 pay_time = serializers.DateTimeField(read_only=True) 7 is_delete = serializers.BooleanField(read_only=True) 8 9 def generate_order_sn(self): 10 # 生成订单编号 11 return '%s%d%d' % (time.strftime('%Y%m%d%H%M%S'), self.context['request'].user.id, randint(1000, 9999)) 12 13 def validate(self, attrs): 14 attrs['order_sn'] = self.generate_order_sn() 15 return attrs 16 17 class Meta: 18 model = OrderInfo 19 fields = '__all__'

定义视图如下:

1class OrderViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): 2 ''' 3 订单管理 4 list: 5 订单列表 6 delete: 7 删除订单 8 create: 9 新增订单 10 ''' 11 12 permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] 13 authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication] 14 serializer_class = OrderSerializer 15 16 def get_queryset(self): 17 return OrderInfo.objects.filter(user=self.request.user, is_delete=False) 18 19 def perform_create(self, serializer): 20 order = serializer.save() 21 shop_carts = ShoppingCart.objects.filter(user=self.request.user, is_delete=False) 22 for shop_cart in shop_carts: 23 order_goods = OrderGoods() 24 order_goods.goods = shop_cart.goods 25 order_goods.goods_num = shop_cart.nums 26 order_goods.order = order 27 order_goods.save() 28 shop_cart.delete() 29 return order

因为订单一般不允许修改,因此不需要继承自UpdateModelMixin

配置路由如下:

1# 配置下订单路由 2router.register(r'orders', OrderViewSet, basename='orders')

现进行接口测试如下: django order add delete

显然,可以获取、添加和删除订单。

2.Vue接入订单接口

需要完善订单详情,新建序列化如下:

1class OrderGoodsSerializer(serializers.ModelSerializer): 2 goods = GoodsSerializer(many=False) 3 4 class Meta: 5 model = OrderGoods 6 fields = '__all__' 7 8 9class OrderDetailSerializer(serializers.ModelSerializer): 10 goods = OrderGoodsSerializer(many=True) 11 12 class Meta: 13 model = OrderInfo 14 fields = '__all__'

视图完善如下:

1class OrderViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet): 2 ''' 3 订单管理 4 list: 5 订单列表 6 delete: 7 删除订单 8 create: 9 新增订单 10 ''' 11 12 permission_classes = [IsAuthenticated, IsOwnerOrReadOnly] 13 authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication] 14 serializer_class = OrderSerializer 15 16 def get_queryset(self): 17 return OrderInfo.objects.filter(user=self.request.user, is_delete=False) 18 19 def get_serializer_class(self): 20 if self.action == 'retrieve': 21 return OrderDetailSerializer 22 return OrderSerializer 23 24 def perform_create(self, serializer): 25 order = serializer.save() 26 shop_carts = ShoppingCart.objects.filter(user=self.request.user, is_delete=False) 27 for shop_cart in shop_carts: 28 order_goods = OrderGoods() 29 order_goods.goods = shop_cart.goods 30 order_goods.goods_num = shop_cart.nums 31 order_goods.order = order 32 order_goods.save() 33 shop_cart.delete() 34 return order

查看前端cart.vue如下:

1<p class="sumup"><a class="btn" @click="balanceCount">去结算</a></p> 2 3balanceCount () { // 结算 4 if(this.address==''){ 5 alert("请选择收货地址") 6 }else{ 7 createOrder( 8 { 9 post_script:this.post_script, 10 address:this.address, 11 signer_name:this.signer_name, 12 signer_mobile:this.signer_mobile, 13 order_mount:this.totalPrice 14 } 15 ).then((response)=> { 16 alert('订单创建成功') 17 window.location.href=response.data.alipay_url; 18 }).catch(function (error) { 19 console.log(error); 20 }); 21 } 22},

通过调用balanceCount()方法创建订单,是通过调用createOrder接口实现的。

src/views/member/order.vue如下:

1<table width="100%" border="0" cellpadding="5" cellspacing="1" bgcolor="#dddddd"> 2 <tbody> 3 <tr align="center"> 4 <td bgcolor="#ffffff">订单号</td> 5 <td bgcolor="#ffffff">下单时间</td> 6 <td bgcolor="#ffffff">订单总金额</td> 7 <td bgcolor="#ffffff">订单状态</td> 8 <td bgcolor="#ffffff">操作</td> 9 </tr> 10 <tr v-for="item in orders"> 11 <td align="center" bgcolor="#ffffff"><a class="f6" @click="goDetail(item.id)">{{item.order_sn}}</a></td> 12 <td align="center" bgcolor="#ffffff">{{item.add_time}}</td> 13 <td align="right" bgcolor="#ffffff">¥{{item.order_mount}}元</td> 14 <td v-if="item.pay_status == 'paying' " align="center" bgcolor="#ffffff">待支付</td> 15 <td v-if="item.pay_status == 'TRADE_SUCCESS' " align="center" bgcolor="#ffffff">已支付</td> 16 <td align="center" bgcolor="#ffffff"><font class="f6"><a @click="cancelOrder(item.id)">取消订单</a></font></td> 17 </tr> 18 </tbody> 19</table> 20 21created () { 22 this.getOrder(); 23}, 24 25getOrder () { 26 getOrders().then((response)=> { 27 this.orders = response.data; 28 }).catch(function (error) { 29 console.log(error); 30 }); 31}, 32cancelOrder (id) { 33 alert('您确认要取消该订单吗?取消后此订单将视为无效订单'); 34 delOrder(id).then((response)=> { 35 alert('订单删除成功') 36 }).catch(function (error) { 37 console.log(error); 38 }); 39}, 40goDetail (id) { 41 this.$router.push({name: 'orderDetail', params: {orderId: id}}); 42}

初始化时调用getOrder()方法获取订单列表,通过调用getOrders接口实现,再通过for循环显示出来;取消订单调用cancelOrder(id)方法,调用delOrder接口实现;获取订单详情直接调用goDetail(id)方法。

src/views/member/orderDetail.vue如下:

1<div class="userCenterBox boxCenterList clearfix" style="_height:1%;"> 2 <h5><span>订单状态</span></h5> 3 <div class="blank"></div> 4 <table width="100%" border="1" cellpadding="5" cellspacing="1" bgcolor="#09C762"> 5 <tbody> 6 <tr> 7 <td width="15%" align="right" bgcolor="#ffffff">订单号:</td> 8 <td align="left" bgcolor="#ffffff">{{orderInfo.order_sn}} 9 <!-- <a href="http://sx.youxueshop.com/user.php?act=message_list&amp;order_id=778" class="f6">[发送/查看商家留言]</a> --> 10 </td> 11 </tr> 12 <tr> 13 <td align="right" bgcolor="#ffffff">订单状态:</td> 14 <td v-if="orderInfo.pay_status == 'paying' " align="left" bgcolor="#ffffff">待支付&nbsp;&nbsp;&nbsp;&nbsp;<div style="text-align:center"><a :href="orderInfo.alipay_url"><input type="button" onclick="" value="立即使用支付宝支付"></a></div></td> 15 <td v-if="orderInfo.pay_status == 'TRADE_SUCCESS' " align="left" bgcolor="#ffffff">已支付</td> 16 </tr> 17 </tbody> 18 </table> 19 <table></table> 20 <div class="blank"></div> 21 <h5> 22 <span>商品列表</span> 23 </h5> 24 <div class="blank"></div> 25 <table width="100%" border="1" cellpadding="5" cellspacing="1" bgcolor="#09C762"> 26 <tbody> 27 <tr> 28 <th width="30%" align="center" bgcolor="#ffffff">商品名称</th> 29 <!--<th>市场价</th>--> 30 <th width="19%" align="center" bgcolor="#ffffff">商品价格</th> 31 <th width="9%" align="center" bgcolor="#ffffff">购买数量</th> 32 <th width="20%" align="center" bgcolor="#ffffff">小计</th> 33 </tr> 34 <tr v-for="item in orderInfo.goods"> 35 <td bgcolor="#ffffff"> 36 <router-link :to="'/app/home/productDetail/'+item.id" class="f6">{{item.goods.name}}</router-link> 37 <!-- <a href="" target="_blank" class="f6">{{item.name}}</a> --> 38 </td> 39 <td align="center" bgcolor="#ffffff">¥{{item.goods.shop_price}}元</td> 40 <td align="center" bgcolor="#ffffff">{{item.goods_num}}</td> 41 <td align="center" bgcolor="#ffffff">¥{{item.goods.shop_price*item.goods_num}}元</td> 42 </tr> 43 <tr> 44 <td colspan="8" bgcolor="#ffffff" align="right"> 45 商品总价: ¥{{totalPrice}}元 46 </td> 47 </tr> 48 </tbody> 49 </table> 50 <div class="blank"></div> 51 <div class="blank"></div> 52 <h5><span>收货人信息</span></h5> 53 <div class="blank"></div> 54 <form name="formAddress" id="formAddress"> 55 <table width="100%" border="0" cellpadding="5" cellspacing="1" bgcolor="#09C762"> 56 <tbody> 57 <tr> 58 <td width="15%" align="right" bgcolor="#ffffff">收货人姓名: </td> 59 <td width="35%" align="left" bgcolor="#ffffff"><input name="consignee" type="text" class="inputBg" v-model="orderInfo.signer_name" size="25"> 60 </td> 61 <td width="15%" align="right" bgcolor="#ffffff">收货地址: </td> 62 <td width="35%" align="left" bgcolor="#ffffff"><input name="email" type="text" class="inputBg" v-model="orderInfo.address" size="25"> 63 </td> 64 </tr> 65 66 <tr> 67 <td align="right" bgcolor="#ffffff">电话: </td> 68 <td align="left" bgcolor="#ffffff"><input name="address" type="text" class="inputBg" v-model="orderInfo.signer_mobile" size="25"></td> 69 </tr> 70 </tbody> 71 </table> 72</form> 73 74created () { 75 this.orderId = this.$route.params.orderId; 76 this.getOrderInfo(); 77 this.getReceiveByOrderId(); 78}, 79 80getProList () { //根据订单号获取商品列表 81 82 83}, 84getOrderInfo () { //获取订单信息 85 getOrderDetail(this.orderId).then((response)=> { 86 this.orderInfo = response.data; 87 var totalPrice = 0 88 response.data.goods.forEach(function(entry) { 89 totalPrice += entry.goods_num*entry.goods.shop_price 90 }); 91 this.totalPrice = totalPrice 92 93 }).catch(function (error) { 94 console.log(error); 95 }); 96 97}, 98getReceiveByOrderId () { //通过orderid找收货人信息 99 100 this.$http.post('/order/receiveInfo', { 101 params: { 102 orderId: this.orderId 103 } 104 }).then((response)=> { 105 106 107 this.receiveData = response.data; 108 }).catch(function (error) { 109 console.log(error); 110 }); 111 112}, 113updateReceiveInfo () { //更新收货人信息 114 this.$http.post('/order/updateReceiveInfo', { 115 data: { 116 receiveInfo: this.receiveData 117 } 118 }).then((response)=> { 119 alert('更新成功'); 120 121 }).catch(function (error) { 122 console.log(error); 123 }); 124}

初始化时调用getOrderInfo()getReceiveByOrderId()方法: getOrderInfo()方法获取订单信息,通过getOrderDetail接口实现;getProList()方法获取订单中的商品列表;getReceiveByOrderId()方法获取收货人信息。

api.js中接口修改如下:

1//获取订单 2export const getOrders = () => { return axios.get(`${local_host}/orders/`) } 3//删除订单 4export const delOrder = orderId => { return axios.delete(`${local_host}/orders/`+orderId+'/') } 5//添加订单 6export const createOrder = params => {return axios.post(`${local_host}/orders/`, params)} 7//获取订单详情 8export const getOrderDetail = orderId => {return axios.get(`${local_host}/orders/`+orderId+'/')}

创建订单示意如下: django order add get

查看如下: django order info get

显然,创建和查看订单信息均实现。

三、支付宝支付接口完成

1.支付宝公钥、私钥生成和沙箱环境配置

要接入支付宝支付需要在支付宝开放平台https://openhome.alipay.com/platform/home.htm登录并进行验证。

因为对个人开发者不能开放,因此只能进行沙箱环境测试,地址为https://openhome.alipay.com/platform/appDaily.htm?tab=info,此时可以看到APPID、支付宝网关和RSA2(SHA256)密钥,如下: alipey 沙箱 设置公钥

首先需要下载支付宝开发平台开发助手生成应用公钥和应用私钥,可以点击https://download.csdn.net/download/CUFEECR/12680902https://ideservice.alipay.com/ide/getPluginUrl.htm?clientType=assistant&platform=win&channelType=WEB下载后安装按照以下示意生成应用公钥和私钥: alipay get key

然后将生成的公钥复制,在https://openhome.alipay.com/platform/appDaily.htm?tab=info中点击设置生成应用公钥和支付宝公钥,将应用公钥、应用私钥和支付宝公钥都按照固定格式分别保存到app_public.txt、app_private.txt和ali_public.txt中,如下:

1-----BEGIN PRIVATE KEY----- 2MIIBIjxxxxxxIDAQAB 3-----END PRIVATE KEY-----

其中中间部分对应着公钥或私钥,并将这3个文件保存到apps/trade/keys目录下。

2.支付宝开发文档

在支付宝开发文档https://opendocs.alipay.com/apis中有很多API接口,这里主要用到支付类API的统一收单下单并支付页面接口,地址为https://opendocs.alipay.com/apis/api_1/alipay.trade.page.pay,请求地址为https://openapi.alipay.com/gateway.do,包含公共参数、请求参数、公共参数等,这里只需要用到公共请求参数中的必填参数即可。

在公共请求参数中,大部分的参数已经获取到或为固定值,比较重要的两个为sign和biz_content,其中sign是商户请求参数的签名串,biz_content是请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递;在请求参数中,out_trade_no、product_code、total_amount和subject为必填参数,其他均为选填,可根据需要填写。

sign参数即签名,需要专门生成,可参考https://opendocs.alipay.com/open/291/105974,这里选择普通公钥方式生成签名,可以使用开放平台SDK接入,也可以未使用开放平台SDK、自行实现签名过程,还可以直接使用支付宝开放平台开发助手的签名功能。

这里先选择自行实现签名,以便了解签名的生成过程,可查看https://opendocs.alipay.com/open/291/106118,需要对参数进行筛选并排序、拼接,再请求,在apps/utils下新建ali_sign_self.py如下:

1from datetime import datetime 2from Crypto.PublicKey import RSA 3from Crypto.Signature import PKCS1_v1_5 4from Crypto.Hash import SHA256 5from urllib.parse import quote_plus 6from urllib.parse import urlparse, parse_qs 7from base64 import decodebytes, encodebytes 8 9import json 10 11 12class AliPay(object): 13 """ 14 支付宝支付接口 15 """ 16 def __init__(self, appid, app_private_key_path, 17 alipay_public_key_path, app_notify_url=None, return_url=None, debug=False): 18 self.appid = appid 19 self.app_notify_url = app_notify_url 20 self.app_private_key_path = app_private_key_path 21 self.app_private_key = app_notify_url 22 self.return_url = return_url 23 with open(self.app_private_key_path) as fp: 24 self.app_private_key = RSA.importKey(fp.read()) 25 26 self.alipay_public_key_path = alipay_public_key_path 27 with open(self.alipay_public_key_path) as fp: 28 self.alipay_public_key = RSA.import_key(fp.read()) 29 30 31 if debug is True: 32 self.__gateway = "https://openapi.alipaydev.com/gateway.do" 33 else: 34 self.__gateway = "https://openapi.alipay.com/gateway.do" 35 36 def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): 37 biz_content = { 38 "subject": subject, 39 "out_trade_no": out_trade_no, 40 "total_amount": total_amount, 41 "product_code": "FAST_INSTANT_TRADE_PAY", 42 # "qr_pay_mode":4 43 } 44 45 biz_content.update(kwargs) 46 data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) 47 return self.sign_data(data) 48 49 def build_body(self, method, biz_content, return_url=None): 50 data = { 51 "app_id": self.appid, 52 "method": method, 53 "charset": "utf-8", 54 "sign_type": "RSA2", 55 "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 56 "version": "1.0", 57 "biz_content": biz_content 58 } 59 60 if return_url is not None: 61 data["notify_url"] = self.app_notify_url 62 data["return_url"] = self.return_url 63 64 return data 65 66 def sign_data(self, data): 67 data.pop("sign", None) 68 # 排序后的字符串 69 unsigned_items = self.ordered_data(data) 70 unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) 71 sign = self.sign(unsigned_string.encode("utf-8")) 72 ordered_items = self.ordered_data(data) 73 quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in ordered_items) 74 75 # 获得最终的订单信息字符串 76 signed_string = quoted_string + "&sign=" + quote_plus(sign) 77 return signed_string 78 79 def ordered_data(self, data): 80 complex_keys = [] 81 for key, value in data.items(): 82 if isinstance(value, dict): 83 complex_keys.append(key) 84 85 # 将字典类型的数据dump出来 86 for key in complex_keys: 87 data[key] = json.dumps(data[key], separators=(',', ':')) 88 89 return sorted([(k, v) for k, v in data.items()]) 90 91 def sign(self, unsigned_string): 92 # 开始计算签名 93 key = self.app_private_key 94 signer = PKCS1_v1_5.new(key) 95 print(signer) 96 signature = signer.sign(SHA256.new(unsigned_string)) 97 # base64 编码,转换为unicode表示并移除回车 98 sign = encodebytes(signature).decode("utf8").replace("\n", "") 99 return sign 100 101 def _verify(self, raw_content, signature): 102 # 开始计算签名 103 key = self.alipay_public_key 104 signer = PKCS1_v1_5.new(key) 105 digest = SHA256.new() 106 digest.update(raw_content.encode("utf8")) 107 if signer.verify(digest, decodebytes(signature.encode("utf8"))): 108 return True 109 return False 110 111 def verify(self, data, signature): 112 if "sign_type" in data: 113 sign_type = data.pop("sign_type") 114 # 排序后的字符串 115 unsigned_items = self.ordered_data(data) 116 message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) 117 return self._verify(message, signature) 118 119 120if __name__ == "__main__": 121 return_url = 'https://openapi.alipaydev.com/gateway.do?app_id=2021000116666333&biz_content=%7B%22subject%22%3A%22%5Cu6d4b%5Cu8bd5%5Cu8ba2%5Cu5355%22%2C%22out_trade_no%22%3A%222020073117084113308%22%2C%22total_amount%22%3A10.01%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay&notify_url=http%3A%2F%2F127.0.0.1%3A8080%2F&return_url=http%3A%2F%2F123.56.18.248%3A8000%2F&sign_type=RSA2&timestamp=2020-08-01+08%3A56%3A07&version=1.0&sign=LuwD5iaqKfLxzcwQswFbuCIwqHg8THta3monZlRQiUkPI8u3MvbXhvSio8WqAvYCsBxnWMss14zzQU1awuJo7OVv%2BDG%2F6hkiO%2BEFlMIkYQzix518tgLgq30O8hfqDWbzch6sSo8RRthldlLHy9KrpWZYCjwSlR1ivFiHS2FGGXjeYxasUfU06LK04fNn%2ForwonJXslqRuUckRVOm56AczkXpuD5Zr3yI%2BOpwYJp4wFCo4tvaYd3qwsqjprGMnUiVWBDlElLJnMtYxf04YKVcDcsBiBTA%2FWffAZZmQv%2FDlyUrbggza1%2FqQZzggVnxtREL%2FYYUCO9enztEpJaVE0SXHw%3D%3D' 122 123 alipay = AliPay( 124 appid="2021000116666333", 125 app_private_key_path=u"../trade/keys/app_private.txt", 126 alipay_public_key_path="../trade/keys/ali_public.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 127 debug=True, # 默认False, 128 ) 129 130 o = urlparse(return_url) 131 query = parse_qs(o.query) 132 processed_query = {} 133 ali_sign = query.pop("sign")[0] 134 for key, value in query.items(): 135 processed_query[key] = value[0] 136 print (alipay.verify(processed_query, ali_sign)) 137 138 url = alipay.direct_pay( 139 subject="测试订单", 140 out_trade_no="2020073117084113310", 141 total_amount=10.01 142 ) 143 re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url) 144 print(re_url)

需要将AliPay初始化的参数换为自己的参数,包括公钥、私钥和appid等。 可以看到需要安装加密使用的依赖库Crypto,直接使用pip install pycryptodome -i https://pypi.douban.com/simple命令安装即可。

运行可以得到签名后的请求地址,例如:

1https://openapi.alipaydev.com/gateway.do?app_id=2021000116666333&biz_content=%7B%22subject%22%3A%22%5Cu6d4b%5Cu8bd5%5Cu8ba2%5Cu5355%22%2C%22out_trade_no%22%3A%222020073117084113308%22%2C%22total_amount%22%3A10.01%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay&notify_url=http%3A%2F%2F127.0.0.1%3A8080%2F&return_url=http%3A%2F%2F123.56.18.248%3A8000%2F&sign_type=RSA2&timestamp=2020-08-01+08%3A56%3A38&version=1.0&sign=EcVfBzcX%2BBPDUb6Gx9TDAMdlISmctAWVKi1R9Mdpv5bl6kl0GU7N7gUM2K%2FxGe%2F%2BMYFDhBuVfjpc6bQ5kleVvxK4Gt5lhH5SFFK%2BVgUd1ye1cKrocx59sNQlR5W26hHtVzp%2BN5i%2FT5nSIQr%2BaH3eGd892XB71Wj7rbOnElBO4w8tdlNtqa9YwmocC%2FZcvPkpkLHGMgoAFItMOR%2FqChPIeA4Za8s9gPlXRqhqHFe9PYGIQsD26q1ImJpYRLD0mra4G9S2Pioiox4uwFwySsRnEvwxRDdKErx%2BSpmzMFGUmBMYSEMYmG1up70ovx4ibPRoU5TutWYcwSrClzs632MwwQ%3D%3D

在配置成功的情况下即可使用该链接访问到支付页面。

由于沙箱环境是模拟测试环境,因此不允许使用自己的支付宝支付,需要使用提供的沙箱账号和密码进行支付测试,地址为https://openhome.alipay.com/platform/appDaily.htm?tab=account,还需要在沙箱工具中https://openhome.alipay.com/platform/appDaily.htm?tab=tool下载沙箱版钱包、登录后进行支付。

3.支付宝生成签名源码分析

在初始化AliPay类时,需要传入appid、应用私钥和支付宝公钥等参数,还需要用到app_notify_url和return_url。

direct_pay(subject, out_trade_no, total_amount, return_url=None, **kwargs)方法先生成biz_content,至少应包括4个必填参数,还可以添加可选参数,并调用build_body(method, biz_content, return_url=None)方法来生成完整参数。

build_body(method, biz_content, return_url=None)方法生成请求需要的公共参数,并将biz_content作为参数的一部分,并将两个链接放入data中。

在形成完整地址后,通过sign_data(data)方法对data生成签名,是最重要的部分,先调用ordered_data(data)对data进行排序,然后将其连接成字符串,然后对该字符串调用sign()方法进行签名,sign()方法中先使用SHA256加密,再进行base64编码。

有两个url,app_notify_url和return_url,前者是与支付宝进行异步交互链接,后者是同步接收跳转接口

在获取到签名后,就可以直接访问付款链接了,示意如下: django order alipay sandbox pay

显然,已经成功测试付款,这里选择的是通过账号和支付密码进行付款,还可以手机下载沙箱版钱包登录扫码付款。在付款成功后跳转到指定页面。

除了自定义签名生成和链接生成,还可以使用第三方库,如python-alipay-sdk,通过命令pip install python-alipay-sdk -i https://pypi.douban.com/simple安装到虚拟环境即可,然后新建ali_sign_sdk代码如下:

1from alipay import AliPay 2 3# 沙箱环境中 app 私钥 4app_private_key_string = open('../trade/keys/app_private.txt').read() 5# 支付宝公钥 6alipay_public_key_string = open( '../trade/keys/ali_public.txt').read() 7 8def get_alipay_url(): 9 alipay = AliPay( 10 appid="2021000116666333", # 沙箱appid 11 app_notify_url=None, # 默认回调url 12 app_private_key_string=app_private_key_string, 13 # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 14 alipay_public_key_string=alipay_public_key_string, 15 sign_type="RSA2", # RSA 或者 RSA2 16 debug=True, # 默认False,我们是沙箱,所以改成True(让访问沙箱环境支付宝地址) 17 ) 18 # 调用支付接口 19 # 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string 20 order_string = alipay.api_alipay_trade_page_pay( 21 out_trade_no="2020073117084113330", # 订单id,应该从前端获取 22 total_amount=10.01, # 订单总金额 23 subject="测试支付宝付款", # 付款标题信息, 24 return_url='http://127.0.0.1:8080/#/app/home/index' 25 ) 26 pay_url = "https://openapi.alipaydev.com/gateway.do?" + order_string 27 print(pay_url) # 将这个url复制到浏览器,就会打开支付宝支付页面 28 29 30if __name__ == '__main__': 31 get_alipay_url()

效果一样,但是显然代码更加简洁。

从前面可以看到,return_url是支付宝付款成功后直接跳转的页面,回到商家页面后,应该对支付宝返回的数据进行验证,验证部分为:

1return_url = 'https://openapi.alipaydev.com/gateway.do?app_id=2021000116666333&biz_content=%7B%22subject%22%3A%22%5Cu6d4b%5Cu8bd5%5Cu8ba2%5Cu5355%22%2C%22out_trade_no%22%3A%222020073117084113310%22%2C%22total_amount%22%3A%2210.01%22%2C%22product_code%22%3A%22FAST_INSTANT_TRADE_PAY%22%7D&charset=utf-8&method=alipay.trade.page.pay&sign_type=RSA2&timestamp=2020-08-01+20%3A52%3A55&version=1.0&sign=GMkujrhzKgS%2FTYpUJVcEVcyU9uZ2cQ%2BZApinxSYML1kceQ3NsghC13BnVHOHfP9cT1lGmyKDKA0at7UHhmeIhgMyEPuyb2SG085zkARlkIDKyzTQKVJ7K0QtQH8zenTv3TJEHJUJMWrm5rfXHsympNyF06bvu%2BMdc9TlYVFiexY7fe1c%2FO1pJADx7CSoPhRGQksKIChQ5HVoQxI6NTuaOXjQ%2B7SiJ9Q5qKsrMFtHzOiN5HwAzjljvFn7%2BxNNNnkG0BseTqIXi%2FWryK4v6bsAEE9SfPlNs6FIwvOa3Ve6q90YHmA48p7PcEmH7Mt0KPfhOEclvTIu7ExKoMlpQ7Wk%2Fg%3D%3D' 2alipay = AliPay( 3 appid="2021000116666333", 4 app_private_key_path=u"../trade/keys/app_private.txt", 5 alipay_public_key_path="../trade/keys/ali_public.txt", # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, 6 debug=True, # 默认False, 7) 8 9o = urlparse(return_url) 10query = parse_qs(o.query) 11print(query) 12processed_query = {} 13ali_sign = query.pop("sign")[0] 14for key, value in query.items(): 15 processed_query[key] = value[0] 16print(processed_query) 17print(alipay.verify(processed_query, ali_sign))

如果链接参数未被修改、验证成功,会打印True,否则会打印False。

4.Django集成支付宝renturn_url和notify_url

如果想在支付宝支付成功后跳转页面和返回数据,就需要指定notify_url和renturn_url,其中notify_url的应用范围更广,通过POST方法发送异步请求;renturn_url是通过GET方法同步发送请求。所以可以直接在后端配置一个路由即可实现两种方式的请求发送。

现在apps/trade/views.py中实现支付宝支付视图如下:

1class AliPayView(APIView): 2 ''' 3 get: 4 处理支付宝return_url请求 5 post: 6 处理支付宝notify_url请求 7 ''' 8 9 def get(self, request): 10 alipay = AliPay( 11 appid=ali_app_id, 12 app_notify_url=None, 13 app_private_key_string=open(app_private_key_path).read(), 14 alipay_public_key_string=open(alipay_public_key_path).read(), 15 sign_type="RSA2", 16 debug=True, 17 ) 18 data = dict(request.GET.items()) 19 signature = data.pop("sign", None) 20 print(data) 21 success = alipay.verify(data, signature) 22 print(success) 23 trade_status = alipay.api_alipay_trade_query(out_trade_no="2020073117084113309").get("trade_status", None) 24 print(trade_status) 25 if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"): 26 order_sn = data.get('out_trade_no', None) 27 trade_no = data.get('trade_no', None) 28 existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False) 29 if existed_orders: 30 for order in existed_orders: 31 order.pay_status = trade_status 32 order.trade_no = trade_no 33 order.pay_time = datetime.now() 34 order.save() 35 return Response('success') 36 return Response('failed') 37 38 def post(self, request): 39 alipay = AliPay( 40 appid=ali_app_id, 41 app_notify_url=None, 42 app_private_key_string=open(app_private_key_path).read(), 43 alipay_public_key_string=open(alipay_public_key_path).read(), 44 sign_type="RSA2", 45 debug=True, 46 ) 47 data = dict(request.POST.items()) 48 signature = data.pop("sign", None) 49 success = alipay.verify(data, signature) 50 trade_status = alipay.api_alipay_trade_query(out_trade_no="2020073117084113366").get("trade_status", None) 51 if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"): 52 order_sn = data.get('out_trade_no', None) 53 trade_no = data.get('trade_no', None) 54 existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False) 55 print(len(existed_orders)) 56 if existed_orders: 57 for order in existed_orders: 58 order.pay_status = trade_status 59 order.trade_no = trade_no 60 order.pay_time = datetime.now() 61 order.save() 62 return Response('success') 63 return Response('failed')

urls.py中注册路由如下:

1# 支付宝结果返回接口 2url(r'^alipay/return/', AliPayView.as_view(), name='alipay')

settings.py中配置如下:

1# 支付宝相关配置 2app_private_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/app_private.txt') 3alipay_public_key_path = os.path.join(BASE_DIR, 'apps/trade/keys/ali_public.txt') 4ali_app_id = "2021000116666333" 5return_url = 'http://127.0.0.1:8000/alipay/return/' 6notify_url = 'http://127.0.0.1:8000/alipay/return/'

进行测试如下: django order alipay add django

因为测试用的订单号不在数据库中,因此返回failed。

5.支付宝接口前端调试

首先要在后端生成支付宝支付链接,需要在序列化中定义并实现,需要用到serializers提供的SerializerMethodField字段,这是一个只读字段,它通过在附加的序列化器类上调用一个方法来获取其值,可以用于将任何类型的数据添加到对象的序列化表示中。 定义格式为SerializerMethodField(method_name=None),method_name是要调用的序列化程序上的方法的名称,如果未包括,则默认为get_<field_name>。method_name参数所对应的序列化程序方法应接受单个参数(除了self之外),该参数是要序列化的对象,它应该返回要包含在对象的序列化表示中的任何内容。

serializers.py完善如下:

1class OrderSerializer(serializers.ModelSerializer): 2 user = serializers.HiddenField(default=serializers.CurrentUserDefault()) 3 pay_status = serializers.CharField(read_only=True) 4 trade_no = serializers.CharField(read_only=True) 5 order_sn = serializers.CharField(read_only=True) 6 pay_time = serializers.DateTimeField(read_only=True) 7 is_delete = serializers.BooleanField(read_only=True) 8 alipay_url = serializers.SerializerMethodField(read_only=True) 9 10 def get_alipay_url(self, obj): 11 # 获取支付宝支付链接 12 alipay = AliPay( 13 appid=ali_app_id, 14 app_notify_url=notify_url, 15 app_private_key_string=open(app_private_key_path).read(), 16 alipay_public_key_string=open(alipay_public_key_path).read(), 17 sign_type="RSA2", 18 debug=True, 19 ) 20 order_string = alipay.api_alipay_trade_page_pay( 21 out_trade_no=obj.order_sn, 22 total_amount=obj.order_mount, 23 subject='订单号:%s' % obj.order_sn, 24 return_url=return_url, 25 notify_url=notify_url 26 ) 27 pay_url = "https://openapi.alipaydev.com/gateway.do?" + order_string 28 return pay_url 29 30 def generate_order_sn(self): 31 # 生成订单编号 32 return '%s%d%d' % (time.strftime('%Y%m%d%H%M%S'), self.context['request'].user.id, randint(1000, 9999)) 33 34 def validate(self, attrs): 35 attrs['order_sn'] = self.generate_order_sn() 36 return attrs 37 38 class Meta: 39 model = OrderInfo 40 fields = '__all__' 41 42 43class OrderGoodsSerializer(serializers.ModelSerializer): 44 goods = GoodsSerializer(many=False) 45 46 class Meta: 47 model = OrderGoods 48 fields = '__all__' 49 50 51class OrderDetailSerializer(serializers.ModelSerializer): 52 goods = OrderGoodsSerializer(many=True) 53 alipay_url = serializers.SerializerMethodField(read_only=True) 54 55 def get_alipay_url(self, obj): 56 # 获取支付宝支付链接 57 alipay = AliPay( 58 appid=ali_app_id, 59 app_notify_url=notify_url, 60 app_private_key_string=open(app_private_key_path).read(), 61 alipay_public_key_string=open(alipay_public_key_path).read(), 62 sign_type="RSA2", 63 debug=True, 64 ) 65 order_string = alipay.api_alipay_trade_page_pay( 66 out_trade_no=obj.order_sn, 67 total_amount=obj.order_mount, 68 subject='订单号:%s' % obj.order_sn, 69 return_url=return_url, 70 notify_url=notify_url 71 ) 72 pay_url = alipay.gateway + '?' + order_string 73 return pay_url 74 75 class Meta: 76 model = OrderInfo 77 fields = '__all__'

进行测试如下: django order alipay add django api

显然,在创建订单时,即生成支付宝生成链接,并成功支付。

前端,购物车组件中代码为:

1balanceCount () { // 结算 2 if(this.address==''){ 3 alert("请选择收货地址") 4 }else{ 5 createOrder( 6 { 7 post_script:this.post_script, 8 address:this.address, 9 signer_name:this.signer_name, 10 signer_mobile:this.signer_mobile, 11 order_mount:this.totalPrice 12 } 13 ).then((response)=> { 14 alert('订单创建成功') 15 window.location.href=response.data.alipay_url; 16 }).catch(function (error) { 17 console.log(error); 18 }); 19 } 20},

可以看到,在进行结算时,点击弹框就会跳转到支付宝支付链接,在支付完成后应该跳转回订单页面,因此需要设置return_url,有两种实现思路,一种是在前端通过Vue实现,一种是通过后端实现,即支付完成后进行页面跳转。

views.py修改如下:

1class AliPayView(APIView): 2 ''' 3 get: 4 处理支付宝return_url请求 5 post: 6 处理支付宝notify_url请求 7 ''' 8 9 def get(self, request): 10 alipay = AliPay( 11 appid=ali_app_id, 12 app_notify_url=None, 13 app_private_key_string=open(app_private_key_path).read(), 14 alipay_public_key_string=open(alipay_public_key_path).read(), 15 sign_type="RSA2", 16 debug=True, 17 ) 18 data = dict(request.GET.items()) 19 signature = data.pop("sign", None) 20 print(data) 21 success = alipay.verify(data, signature) 22 order_sn = data.get('out_trade_no', None) 23 print(success) 24 trade_status = alipay.api_alipay_trade_query(out_trade_no=order_sn).get("trade_status", None) 25 print(trade_status) 26 if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"): 27 trade_no = data.get('trade_no', None) 28 existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False) 29 if existed_orders: 30 for order in existed_orders: 31 order.pay_status = trade_status 32 order.trade_no = trade_no 33 order.pay_time = datetime.now() 34 order.save() 35 response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order') 36 response.set_cookie('nextPath', 'pay', max_age=2) 37 print('cookie', response.cookies) 38 return response 39 return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart') 40 41 def post(self, request): 42 alipay = AliPay( 43 appid=ali_app_id, 44 app_notify_url=None, 45 app_private_key_string=open(app_private_key_path).read(), 46 alipay_public_key_string=open(alipay_public_key_path).read(), 47 sign_type="RSA2", 48 debug=True, 49 ) 50 data = dict(request.POST.items()) 51 signature = data.pop("sign", None) 52 success = alipay.verify(data, signature) 53 trade_status = alipay.api_alipay_trade_query(out_trade_no="2020073117084113366").get("trade_status", None) 54 if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"): 55 order_sn = data.get('out_trade_no', None) 56 trade_no = data.get('trade_no', None) 57 existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False) 58 print(len(existed_orders)) 59 if existed_orders: 60 for order in existed_orders: 61 order.pay_status = trade_status 62 order.trade_no = trade_no 63 order.pay_time = datetime.now() 64 order.save() 65 response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order') 66 response.set_cookie('nextPath', 'pay', max_age=2) 67 print('cookie', response.cookies) 68 return response 69 return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart')

演示如下: django order alipay success

显然,已经实现支付的过程,并且在支付完成后跳转回订单页面。

本文原文首发来自博客专栏Python Web开发实战,由本人转发至https://www.helloworld.net/p/4eOKSnZUQAC8G,其他平台均属侵权,可点击https://blog.csdn.net/CUFEECR/article/details/107749353查看原文,也可点击https://blog.csdn.net/CUFEECR浏览更多优质原创内容。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )

mysql设置时区

mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0