专栏首页Corley的开发笔记Django+Vue开发生鲜电商平台之10.购物车、订单管理和支付功能

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

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

Github和Gitee代码同步更新

一、购物车功能实现

1.加入购物车功能实现

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

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

from rest_framework import serializers

from goods.models import Goods
from .models import ShoppingCart

class ShoppingCartSerializer(serializers.Serializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    nums = serializers.IntegerField(required=True, min_value=1, label='数量',
                                    error_messages={
                                        'required': '请选择商品数量',
                                        'min_value': '商品数量至少为1'
                                    })
    goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.filter(is_delete=False))

    def create(self, validated_data):
        '''新增数据'''
        user = self.context['request'].user
        nums = validated_data['nums']
        goods = validated_data['goods']
        existed = ShoppingCart.objects.filter(is_delete=False, user=user, goods=goods)
        if existed:
            existed = existed[0]
            existed.nums += 1
            existed.save()
        else:
            existed = ShoppingCart.objects.create(**validated_data)
        return existed

模型修改如下:

class ShoppingCart(models.Model):
    '''购物车'''
    user = models.ForeignKey(User, verbose_name='用户', null=True, on_delete=models.SET_NULL)
    goods = models.ForeignKey(Goods, verbose_name='商品', null=True, on_delete=models.SET_NULL)
    nums = models.IntegerField(default=0, verbose_name='商品数量')

    add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间')
    is_delete = models.BooleanField(default=False, verbose_name='是否删除')

    class Meta:
        verbose_name = '购物车'
        verbose_name_plural = verbose_name
        unique_together = ('user', 'goods')

    def __str__(self):
        return '%s(%d)'.format(self.goods.name, self.nums)

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

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

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import SessionAuthentication
from rest_framework_jwt.authentication import JSONWebTokenAuthentication

from utils.permissions import IsOwnerOrReadOnly
from .serializers import ShoppingCartSerializer
from .models import ShoppingCart

# Create your views here.


class ShoppingCartViewSet(viewsets.ModelViewSet):
    '''
    list:
        购物车列表
    create:
        加入购物车
    update:
        购物车修改
    delete:
        删除购物车
    '''

    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    serializer_class = ShoppingCartSerializer

    def get_queryset(self):
        return ShoppingCart.objects.filter(user=self.request.user, is_delete=False)

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

urls.py中定义路由如下:

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

API测试如下:

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

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

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

class ShoppingCartSerializer(serializers.Serializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    nums = serializers.IntegerField(required=True, min_value=1, label='数量',
                                    error_messages={
                                        'required': '请选择商品数量',
                                        'min_value': '商品数量至少为1'
                                    })
    goods = serializers.PrimaryKeyRelatedField(required=True, queryset=Goods.objects.filter(is_delete=False))

    def create(self, validated_data):
        '''新增数据'''
        user = self.context['request'].user
        nums = validated_data['nums']
        goods = validated_data['goods']
        existed = ShoppingCart.objects.filter(is_delete=False, user=user, goods=goods)
        if existed:
            existed = existed[0]
            existed.nums += 1
            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

再次访问测试修改和删除某一个购物车记录如下:

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

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

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

class ShoppingCartDetailSerializer(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False)

    class Meta:
        model = ShoppingCart
        fields = '__all__'

views.py如下:

class ShoppingCartViewSet(viewsets.ModelViewSet):
    '''
    list:
        购物车列表
    create:
        加入购物车
    update:
        购物车修改
    delete:
        删除购物车
    '''

    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    serializer_class = ShoppingCartSerializer
    lookup_field = 'goods_id'

    def get_serializer_class(self):
        if self.action == 'list':
            return ShoppingCartDetailSerializer
        else:
            return ShoppingCartSerializer

    def get_queryset(self):
        return ShoppingCart.objects.filter(user=self.request.user, is_delete=False)

现在访问购物车列表,如下:

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

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

<li class="skunum_li cle">
    <span class="lbl">数&nbsp;&nbsp;&nbsp;量</span>
    <div class="skunum" id="skunum"> <span class="minus" title="减少1个数量" @click="reduceNum"><i class="iconfont">-</i></span>
        <input id="number" name="number" type="text" min="1" v-model="buyNum"  onchange="countNum(0)">
        <span class="add" title="增加1个数量" @click="addNum"><i class="iconfont">+</i></span> <cite class="storage"> 件 </cite>
    </div>
    <div class="skunum" id="skunum">

        <cite class="storage">(<font id="shows_number">{{proDetail.goods_num}}件</font>)</cite>

    </div>
</li>
<li class="add_cart_li">
    <a class="btn" id="buy_btn" @click="addShoppingCart">
        <i class="iconfont">&#xe600;</i>
        加入购物车</a>
</li>

addShoppingCart () { //加入购物车
    addShopCart({
        goods: this.productId, // 商品id
        nums: this.buyNum, // 购买数量
    }).then((response)=> {
        this.$refs.model.setShow();
        // 更新store数据
        this.$store.dispatch('setShopList');

    }).catch(function (error) {
        console.log(error);
    });
},

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

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

<div class="hd_cart" id="ECS_CARTINFO"  @mouseover="overShopCar" @mouseout="outShopCar">
    <router-link class="tit" :to="'/app/shoppingcart/cart'" target = _blank>

        <b class="iconfont">&#xe600;</b>去购物车结算<span><i class="iconfont">&#xe645;</i></span>
        <em class="num" id="hd_cartnum" style="visibility: visible;">{{goods_list.goods_list.length}}</em></router-link>
            <div class="list" v-show="showShopCar">
                <div class="data">
                    <dl v-for="(item,index) in goods_list.goods_list">
                    <dt><router-link :to="'/app/home/productDetail/'+item.goods.id" target = _blank><img :src="item.goods.goods_front_image"></router-link></dt>
                    <dd>
                        <h4><router-link :to="'/app/home/productDetail/'+item.goods.id" target = _blank>{{item.goods.name}}</router-link></h4>
                        <p><span class="red">{{item.goods.shop_price}}</span>&nbsp;<i>X</i>&nbsp;{{item.nums}}</p>
                        <a title="删除" class="iconfont del" @click="deleteGoods(index,item.goods.id)">×</a></dd>
                    </dl>
                </div>
                <div class="count">共<span class="red" id="hd_cart_count">{{goods_list.length}}</span>件商品哦~
                        <p>总价:<span class="red"><em id="hd_cart_total">{{goods_list.totalPrice}}</em></span>
                        <router-link class="btn" :to="'/app/shoppingcart/cart'" target = _blank>去结算
                        </router-link>
                        </p>
                </div>
            </div>
</div>

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

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

<div class="cart-box" id="cart-box">
    <div class="hd"> <span class="no2" id="itemsnum-top">{{goods.goods_list.length}}件商品</span>
    <span class="no4">单价</span> <span>数量</span> <span>小计</span>
    </div>
    <div class="goods-list">
    <ul>
        <li class="cle hover" style="border-bottom-style: none;" v-for="(item,index) in goods.goods_list">
        <div class="pic">
            <a target="_blank"> <img :alt="item.goods.name" :src="item.goods.goods_front_image"></a>
        </div>
        <div class="name">
            <a target="_blank">{{item.goods.name}}</a>
            <p></p>
        </div>
        <div class="price-xj">
            <p><em>¥{{item.goods.shop_price}}元</em></p>
        </div>
        <div class="nums" id="nums">
            <span class="minus" title="减少1个数量" @click="reduceCartNum(index, item.goods.id);">-</span>
            <input type="text"  v-model="item.nums" >
            <span class="add" title="增加1个数量" @click="addCartNum(index, item.goods.id);">+</span>
        </div>
        <div class="price-xj"><span></span>
            <em id="total_items_3137">¥{{item.goods.shop_price * item.nums}}元</em>
        </div>
        <div class="del">
            <a class="btn-del" @click="deleteGoods(index, item.goods.id)">删除</a>
        </div>
        </li>
    </ul>
    </div>

    <div class="fd cle">
    <div class="fl">
        <p class="no1"> <a id="del-all" @click="delAll">清空购物车</a> </p>
        <p><a class="graybtn" @click="continueShopping">继续购物</a></p>
    </div>
    <div class="fr" id="price-total">
        <p><span id="selectedCount">{{goods.goods_list.length}}</span>件商品,总价:<span class="red"><strong id="totalSkuPrice">¥{{totalPrice}}元</strong></span></p>
    </div>
    <div class="extr">
        <div class="address">
        <p class="title">配送地址</p>
        <ul>
            <li class="add" @click="addAddr">
            <router-link :to="'/app/home/member/receive'" target = _blank>
                +
                点击添加地址</router-link>
            </li>
            <li v-for="item in addrInfo" :class="{'addressActive':addressActive==item.id}" @click="selectAddr(item.id)">
            <p class="item">地址:{{item.province}} {{item.city}} {{item.district}} {{item.address}}</p>
            <p class="item">电话:{{item.signer_mobile}}</p>
            <p class="item">姓名:{{item.signer_name}}</p>
            </li>
        </ul>
        </div>
        <div class="pay">
        <p class="title">选择支付方式</p>
        <p class="payWrap"><img v-for="item in payWrapList" src="../../static/images/alipay.jpg" :class="{'payWrapActive':payWrapActive==item.id}" @click="selectPay(item.id)"></p>
        </div>
    </div>
    <textarea type="text" v-model="post_script" placeholder="请输入留言" style="margin-top: 10px; height:50px;width: 100%;">
    </textarea>
    <p class="sumup"><a class="btn" @click="balanceCount">去结算</a></p>
    </div>
</div>

created () {
    // 请求购物车商品
    getShopCarts().then((response)=> {
    console.log(response.data)
    // 更新store数据
    //this.goods_list = response.data;
    var totalPrice = 0
    this.goods.goods_list = response.data;
    response.data.forEach(function(entry) {
        totalPrice += entry.goods.shop_price*entry.nums
        console.log(entry.goods.shop_price);
    });

    this.goods.totalPrice = totalPrice
    this.totalPrice = totalPrice
    }).catch(function (error) {
    });
    this.getAllAddr ()

},

addCartNum(index, id) { //添加数量
updateShopCart(id,{
    nums: this.goods.goods_list[index].nums+1
}).then((response)=> {
    this.goods.goods_list[index].nums = this.goods.goods_list[index].nums + 1;
    // 更新store数据
    this.$store.dispatch('setShopList');
    //更新总价
    this.setTotalPrice();

}).catch(function (error) {
    console.log(error);
});
},
setTotalPrice(){
var goods_list = this.goods.goods_list;
var totalPrice = 0;
for(var i = 0;i<goods_list.length;i++){
    totalPrice=totalPrice+goods_list[i].nums* goods_list[i].goods.shop_price;
}
this.totalPrice = totalPrice;
},
deleteGoods(index,id) { //移除购物车
alert('您确定把该商品移除购物车吗');
deleteShopCart(id).then((response)=> {
    console.log(response.data);
    this.goods.goods_list.splice(index,1);

    // 更新store数据
    this.$store.dispatch('setShopList');

}).catch(function (error) {
    console.log(error);
});
},
reduceCartNum(index, id) { //删除数量
if(this.goods.goods_list[index].nums<=1){
    this.deleteGoods(index, id)
}else{
    updateShopCart(id,{
    nums: this.goods.goods_list[index].nums-1
    }).then((response)=> {
    this.goods.goods_list[index].nums = this.goods.goods_list[index].nums - 1;
    // 更新store数据
    this.$store.dispatch('setShopList');
    //更新总价
    this.setTotalPrice();

    }).catch(function (error) {
    console.log(error);
    });
}


},
continueShopping () { // 继续购物
this.$router.push({name: 'index'});
},
delAll () { //清空购物车

this.$http.post('/shoppingCart/clear', {

}).then((response)=> {
    console.log(response.data);
    this.goods.goods_list.splice(0, this.goods.goods_list.length);
    // 更新store数据
    this.$store.dispatch('setShopList');

}).catch(function (error) {
    console.log(error);
});
},
selectPay(id){
this.payWrapActive = id;
},
getAllAddr () { //获得所有配送地址
getAddress().then((response)=> {
    this.addrInfo = response.data;
}).catch(function (error) {
    console.log(error);
});
},
addAddr () { //添加地址

},
selectAddr (id) { //选择配送地址
this.addressActive = id;
var cur_address = ''
var cur_name = ''
var cur_mobile = ''
this.addrInfo.forEach(function(addrItem) {
    if(addrItem.id == id){
    cur_address = addrItem.province+addrItem.city+addrItem.district+addrItem.address
    cur_name = addrItem.signer_name
    cur_mobile = addrItem.signer_mobile
    }
});
this.address = cur_address
this.signer_mobile = cur_mobile
this.signer_name = cur_name
},
balanceCount () { // 结算
    if(this.addrInfo.length==0){
        alert("请选择收货地址")
    }else{
    createOrder(
        {
        post_script:this.post_script,
        address:this.address,
        signer_name:this.signer_name,
        singer_mobile:this.signer_mobile,
        order_mount:this.totalPrice
        }
    ).then((response)=> {
        alert('订单创建成功')
        window.location.href=response.data.alipay_url;
    }).catch(function (error) {
        console.log(error);
    });
    }
},

初始化时调用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修改接口如下:

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

演示如下:

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

二、订单功能实现

1.订单管理接口

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

class OrderInfo(models.Model):
    '''订单信息'''
    ORDER_STATUS = (
        ('success', '成功'),
        ('cancel', '取消'),
        ('paying', '待支付'),
    )
    user = models.ForeignKey(User, verbose_name='用户', null=True, on_delete=models.SET_NULL)
    order_sn = models.CharField(max_length=30, unique=True, null=True, blank=True, verbose_name='订单号')
    trade_no = models.CharField(max_length=50, unique=True, null=True, blank=True, verbose_name='交易号')
    pay_status = models.CharField(max_length=100, default='paying', choices=ORDER_STATUS, verbose_name='订单状态')
    post_script = models.CharField(max_length=11, 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='签收人')
    signer_mobile = models.CharField(max_length=11, verbose_name='联系电话')

    add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间')
    is_delete = models.BooleanField(default=False, verbose_name='是否删除')

    class Meta:
        verbose_name = u"订单"
        verbose_name_plural = verbose_name

    def __str__(self):
        return str(self.order_sn)


class OrderGoods(models.Model):
    '''订单商品详情'''
    order = models.ForeignKey(OrderInfo, verbose_name='订单信息', null=True, on_delete=models.CASCADE)
    goods = models.ForeignKey(Goods, verbose_name='商品', null=True, on_delete=models.SET_NULL)
    goods_num = models.IntegerField(default=0, verbose_name='商品数量')

    add_time = models.DateTimeField(default=datetime.now, verbose_name=u'添加时间')
    is_delete = models.BooleanField(default=False, verbose_name='是否删除')

    class Meta:
        verbose_name = '订单商品'
        verbose_name_plural = verbose_name

    def __str__(self):
        return str(self.order.order_sn)

定义序列化如下:

class OrderSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    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)
    is_delete = serializers.BooleanField(read_only=True)

    def generate_order_sn(self):
        # 生成订单编号
        return '%s%d%d' % (time.strftime('%Y%m%d%H%M%S'), self.context['request'].user.id, randint(1000, 9999))

    def validate(self, attrs):
        attrs['order_sn'] = self.generate_order_sn()
        return attrs

    class Meta:
        model = OrderInfo
        fields = '__all__'

定义视图如下:

class OrderViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
    '''
    订单管理
    list:
        订单列表
    delete:
        删除订单
    create:
        新增订单
    '''

    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    serializer_class = OrderSerializer

    def get_queryset(self):
        return OrderInfo.objects.filter(user=self.request.user, is_delete=False)

    def perform_create(self, serializer):
        order = serializer.save()
        shop_carts = ShoppingCart.objects.filter(user=self.request.user, is_delete=False)
        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()
            shop_cart.delete()
        return order

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

配置路由如下:

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

现进行接口测试如下:

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

2.Vue接入订单接口

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

class OrderGoodsSerializer(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False)

    class Meta:
        model = OrderGoods
        fields = '__all__'


class OrderDetailSerializer(serializers.ModelSerializer):
    goods = OrderGoodsSerializer(many=True)

    class Meta:
        model = OrderInfo
        fields = '__all__'

视图完善如下:

class OrderViewSet(mixins.CreateModelMixin, mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet):
    '''
    订单管理
    list:
        订单列表
    delete:
        删除订单
    create:
        新增订单
    '''

    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    serializer_class = OrderSerializer

    def get_queryset(self):
        return OrderInfo.objects.filter(user=self.request.user, is_delete=False)

    def get_serializer_class(self):
        if self.action == 'retrieve':
            return OrderDetailSerializer
        return OrderSerializer

    def perform_create(self, serializer):
        order = serializer.save()
        shop_carts = ShoppingCart.objects.filter(user=self.request.user, is_delete=False)
        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()
            shop_cart.delete()
        return order

查看前端cart.vue如下:

<p class="sumup"><a class="btn" @click="balanceCount">去结算</a></p>

balanceCount () { // 结算
    if(this.address==''){
        alert("请选择收货地址")
    }else{
    createOrder(
        {
        post_script:this.post_script,
        address:this.address,
        signer_name:this.signer_name,
        signer_mobile:this.signer_mobile,
        order_mount:this.totalPrice
        }
    ).then((response)=> {
        alert('订单创建成功')
        window.location.href=response.data.alipay_url;
    }).catch(function (error) {
        console.log(error);
    });
    }
},

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

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

<table width="100%" border="0" cellpadding="5" cellspacing="1" bgcolor="#dddddd">
    <tbody>
        <tr align="center">
            <td bgcolor="#ffffff">订单号</td>
            <td bgcolor="#ffffff">下单时间</td>
            <td bgcolor="#ffffff">订单总金额</td>
            <td bgcolor="#ffffff">订单状态</td>
            <td bgcolor="#ffffff">操作</td>
        </tr>
        <tr v-for="item in orders">
            <td align="center" bgcolor="#ffffff"><a class="f6" @click="goDetail(item.id)">{{item.order_sn}}</a></td>
            <td align="center" bgcolor="#ffffff">{{item.add_time}}</td>
            <td align="right" bgcolor="#ffffff">¥{{item.order_mount}}元</td>
            <td v-if="item.pay_status == 'paying' " align="center" bgcolor="#ffffff">待支付</td>
            <td v-if="item.pay_status == 'TRADE_SUCCESS' " align="center" bgcolor="#ffffff">已支付</td>
            <td align="center" bgcolor="#ffffff"><font class="f6"><a @click="cancelOrder(item.id)">取消订单</a></font></td>
        </tr>
    </tbody>
</table>

created () {
    this.getOrder();
},

getOrder () {
    getOrders().then((response)=> {
        this.orders = response.data;
    }).catch(function (error) {
        console.log(error);
    });
},
cancelOrder (id) {
    alert('您确认要取消该订单吗?取消后此订单将视为无效订单');
    delOrder(id).then((response)=> {
        alert('订单删除成功')
    }).catch(function (error) {
        console.log(error);
    });
},
goDetail (id) {
    this.$router.push({name: 'orderDetail', params: {orderId: id}});
}

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

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

<div class="userCenterBox boxCenterList clearfix" style="_height:1%;">
        <h5><span>订单状态</span></h5>
        <div class="blank"></div>
        <table width="100%" border="1" cellpadding="5" cellspacing="1" bgcolor="#09C762">
            <tbody>
                <tr>
                    <td width="15%" align="right" bgcolor="#ffffff">订单号:</td>
                    <td align="left" bgcolor="#ffffff">{{orderInfo.order_sn}}
                        <!-- <a href="http://sx.youxueshop.com/user.php?act=message_list&amp;order_id=778" class="f6">[发送/查看商家留言]</a> -->
                    </td>
                </tr>
                <tr>
                    <td align="right" bgcolor="#ffffff">订单状态:</td>
                    <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>
                    <td v-if="orderInfo.pay_status == 'TRADE_SUCCESS' " align="left" bgcolor="#ffffff">已支付</td>
                </tr>
            </tbody>
        </table>
        <table></table>
    <div class="blank"></div>
    <h5>
        <span>商品列表</span>
    </h5>
    <div class="blank"></div>
    <table width="100%" border="1" cellpadding="5" cellspacing="1" bgcolor="#09C762">
        <tbody>
            <tr>
                <th width="30%" align="center" bgcolor="#ffffff">商品名称</th>
                <!--<th>市场价</th>-->
                <th width="19%" align="center" bgcolor="#ffffff">商品价格</th>
                <th width="9%" align="center" bgcolor="#ffffff">购买数量</th>
                <th width="20%" align="center" bgcolor="#ffffff">小计</th>
            </tr>
            <tr v-for="item in orderInfo.goods">
                <td bgcolor="#ffffff">
                    <router-link  :to="'/app/home/productDetail/'+item.id" class="f6">{{item.goods.name}}</router-link>
                    <!-- <a href="" target="_blank" class="f6">{{item.name}}</a> -->
                </td>
                <td align="center" bgcolor="#ffffff">¥{{item.goods.shop_price}}元</td>
                <td align="center" bgcolor="#ffffff">{{item.goods_num}}</td>
                <td align="center" bgcolor="#ffffff">¥{{item.goods.shop_price*item.goods_num}}元</td>
            </tr>
            <tr>
                <td colspan="8" bgcolor="#ffffff" align="right">
                    商品总价: ¥{{totalPrice}}元
                </td>
            </tr>
        </tbody>
    </table>
    <div class="blank"></div>
    <div class="blank"></div>
    <h5><span>收货人信息</span></h5>
    <div class="blank"></div>
    <form name="formAddress" id="formAddress">
        <table width="100%" border="0" cellpadding="5" cellspacing="1" bgcolor="#09C762">
            <tbody>
            <tr>
                <td width="15%" align="right" bgcolor="#ffffff">收货人姓名: </td>
                <td width="35%" align="left" bgcolor="#ffffff"><input name="consignee" type="text" class="inputBg" v-model="orderInfo.signer_name" size="25">
                </td>
                <td width="15%" align="right" bgcolor="#ffffff">收货地址: </td>
                <td width="35%" align="left" bgcolor="#ffffff"><input name="email" type="text" class="inputBg" v-model="orderInfo.address" size="25">
                </td>
            </tr>

            <tr>
                <td align="right" bgcolor="#ffffff">电话: </td>
                <td align="left" bgcolor="#ffffff"><input name="address" type="text" class="inputBg" v-model="orderInfo.signer_mobile" size="25"></td>
            </tr>
        </tbody>
    </table>
</form>

created () {
    this.orderId = this.$route.params.orderId;
    this.getOrderInfo();
    this.getReceiveByOrderId();
},

getProList () { //根据订单号获取商品列表


},
getOrderInfo () { //获取订单信息
    getOrderDetail(this.orderId).then((response)=> {
        this.orderInfo = response.data;
        var totalPrice = 0
        response.data.goods.forEach(function(entry) {
            totalPrice += entry.goods_num*entry.goods.shop_price
        });
        this.totalPrice = totalPrice

    }).catch(function (error) {
        console.log(error);
    });

},
getReceiveByOrderId () { //通过orderid找收货人信息

    this.$http.post('/order/receiveInfo', {
        params: {
            orderId: this.orderId
        }
    }).then((response)=> {


        this.receiveData = response.data;
    }).catch(function (error) {
        console.log(error);
    });

},
updateReceiveInfo () { //更新收货人信息
    this.$http.post('/order/updateReceiveInfo', {
        data: {
            receiveInfo: this.receiveData
        }
    }).then((response)=> {
        alert('更新成功');

    }).catch(function (error) {
        console.log(error);
    });
}

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

api.js中接口修改如下:

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

创建订单示意如下:

查看如下:

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

三、**宝支付接口完成

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

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

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

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

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

-----BEGIN PRIVATE KEY-----
MIIBIjxxxxxxIDAQAB
-----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如下:

from datetime import datetime
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from urllib.parse import quote_plus
from urllib.parse import urlparse, parse_qs
from base64 import decodebytes, encodebytes

import json


class AliPay(object):
    """
    **宝支付接口
    """
    def __init__(self, appid, app_private_key_path,
                 alipay_public_key_path, app_notify_url=None, return_url=None, 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 = app_notify_url
        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)
        print(signer)
        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 False

    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__":
    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'

    alipay = AliPay(
        appid="2021000116666333",
        app_private_key_path=u"../trade/keys/app_private.txt",
        alipay_public_key_path="../trade/keys/ali_public.txt",  # **宝的公钥,验证**宝回传消息使用,不是你自己的公钥,
        debug=True,  # 默认False,
    )

    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="测试订单",
        out_trade_no="2020073117084113310",
        total_amount=10.01
    )
    re_url = "https://openapi.alipaydev.com/gateway.do?{data}".format(data=url)
    print(re_url)

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

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

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%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,前者是与**宝进行异步交互链接,后者是同步接收跳转接口

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

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

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

from alipay import AliPay

# 沙箱环境中 app 私钥
app_private_key_string = open('../trade/keys/app_private.txt').read()
# **宝公钥
alipay_public_key_string = open( '../trade/keys/ali_public.txt').read()

def get_alipay_url():
    alipay = AliPay(
        appid="2021000116666333",  # 沙箱appid
        app_notify_url=None,  # 默认回调url
        app_private_key_string=app_private_key_string,
        # **宝的公钥,验证**宝回传消息使用,不是你自己的公钥,
        alipay_public_key_string=alipay_public_key_string,
        sign_type="RSA2",  # RSA 或者 RSA2
        debug=True,  # 默认False,我们是沙箱,所以改成True(让访问沙箱环境**宝地址)
    )
    # 调用支付接口
    # 电脑网站支付,需要跳转到https://openapi.alipay.com/gateway.do? + order_string
    order_string = alipay.api_alipay_trade_page_pay(
        out_trade_no="2020073117084113330",  # 订单id,应该从前端获取
        total_amount=10.01,  # 订单总金额
        subject="测试**宝付款",  # 付款标题信息,
        return_url='http://127.0.0.1:8080/#/app/home/index'
    )
    pay_url = "https://openapi.alipaydev.com/gateway.do?" + order_string
    print(pay_url)  # 将这个url复制到浏览器,就会打开**宝支付页面


if __name__ == '__main__':
    get_alipay_url()

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

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

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%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'
alipay = AliPay(
    appid="2021000116666333",
    app_private_key_path=u"../trade/keys/app_private.txt",
    alipay_public_key_path="../trade/keys/ali_public.txt",  # **宝的公钥,验证**宝回传消息使用,不是你自己的公钥,
    debug=True,  # 默认False,
)

o = urlparse(return_url)
query = parse_qs(o.query)
print(query)
processed_query = {}
ali_sign = query.pop("sign")[0]
for key, value in query.items():
    processed_query[key] = value[0]
print(processed_query)
print(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中实现**宝支付视图如下:

class AliPayView(APIView):
    '''
    get:
        处理**宝return_url请求
    post:
        处理**宝notify_url请求
    '''

    def get(self, request):
        alipay = AliPay(
            appid=ali_app_id,
            app_notify_url=None,
            app_private_key_string=open(app_private_key_path).read(),
            alipay_public_key_string=open(alipay_public_key_path).read(),
            sign_type="RSA2",
            debug=True,
        )
        data = dict(request.GET.items())
        signature = data.pop("sign", None)
        print(data)
        success = alipay.verify(data, signature)
        print(success)
        trade_status = alipay.api_alipay_trade_query(out_trade_no="2020073117084113309").get("trade_status", None)
        print(trade_status)
        if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
            order_sn = data.get('out_trade_no', None)
            trade_no = data.get('trade_no', None)
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False)
            if existed_orders:
                for order in existed_orders:
                    order.pay_status = trade_status
                    order.trade_no = trade_no
                    order.pay_time = datetime.now()
                    order.save()
                return Response('success')
        return Response('failed')

    def post(self, request):
        alipay = AliPay(
            appid=ali_app_id,
            app_notify_url=None,
            app_private_key_string=open(app_private_key_path).read(),
            alipay_public_key_string=open(alipay_public_key_path).read(),
            sign_type="RSA2",
            debug=True,
        )
        data = dict(request.POST.items())
        signature = data.pop("sign", None)
        success = alipay.verify(data, signature)
        trade_status = alipay.api_alipay_trade_query(out_trade_no="2020073117084113366").get("trade_status", None)
        if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
            order_sn = data.get('out_trade_no', None)
            trade_no = data.get('trade_no', None)
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False)
            print(len(existed_orders))
            if existed_orders:
                for order in existed_orders:
                    order.pay_status = trade_status
                    order.trade_no = trade_no
                    order.pay_time = datetime.now()
                    order.save()
                return Response('success')
        return Response('failed')

urls.py中注册路由如下:

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

settings.py中配置如下:

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

进行测试如下:

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

5.**宝接口前端调试

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

serializers.py完善如下:

class OrderSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    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)
    is_delete = serializers.BooleanField(read_only=True)
    alipay_url = serializers.SerializerMethodField(read_only=True)

    def get_alipay_url(self, obj):
        # 获取**宝支付链接
        alipay = AliPay(
            appid=ali_app_id,
            app_notify_url=notify_url,
            app_private_key_string=open(app_private_key_path).read(),
            alipay_public_key_string=open(alipay_public_key_path).read(),
            sign_type="RSA2",
            debug=True,
        )
        order_string = alipay.api_alipay_trade_page_pay(
            out_trade_no=obj.order_sn,
            total_amount=obj.order_mount,
            subject='订单号:%s' % obj.order_sn,
            return_url=return_url,
            notify_url=notify_url
        )
        pay_url = "https://openapi.alipaydev.com/gateway.do?" + order_string
        return pay_url

    def generate_order_sn(self):
        # 生成订单编号
        return '%s%d%d' % (time.strftime('%Y%m%d%H%M%S'), self.context['request'].user.id, randint(1000, 9999))

    def validate(self, attrs):
        attrs['order_sn'] = self.generate_order_sn()
        return attrs

    class Meta:
        model = OrderInfo
        fields = '__all__'


class OrderGoodsSerializer(serializers.ModelSerializer):
    goods = GoodsSerializer(many=False)

    class Meta:
        model = OrderGoods
        fields = '__all__'


class OrderDetailSerializer(serializers.ModelSerializer):
    goods = OrderGoodsSerializer(many=True)
    alipay_url = serializers.SerializerMethodField(read_only=True)

    def get_alipay_url(self, obj):
        # 获取**宝支付链接
        alipay = AliPay(
            appid=ali_app_id,
            app_notify_url=notify_url,
            app_private_key_string=open(app_private_key_path).read(),
            alipay_public_key_string=open(alipay_public_key_path).read(),
            sign_type="RSA2",
            debug=True,
        )
        order_string = alipay.api_alipay_trade_page_pay(
            out_trade_no=obj.order_sn,
            total_amount=obj.order_mount,
            subject='订单号:%s' % obj.order_sn,
            return_url=return_url,
            notify_url=notify_url
        )
        pay_url = alipay.gateway + '?' + order_string
        return pay_url

    class Meta:
        model = OrderInfo
        fields = '__all__'

进行测试如下:

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

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

balanceCount () { // 结算
    if(this.address==''){
        alert("请选择收货地址")
    }else{
    createOrder(
        {
        post_script:this.post_script,
        address:this.address,
        signer_name:this.signer_name,
        signer_mobile:this.signer_mobile,
        order_mount:this.totalPrice
        }
    ).then((response)=> {
        alert('订单创建成功')
        window.location.href=response.data.alipay_url;
    }).catch(function (error) {
        console.log(error);
    });
    }
},

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

views.py修改如下:

class AliPayView(APIView):
    '''
    get:
        处理**宝return_url请求
    post:
        处理**宝notify_url请求
    '''

    def get(self, request):
        alipay = AliPay(
            appid=ali_app_id,
            app_notify_url=None,
            app_private_key_string=open(app_private_key_path).read(),
            alipay_public_key_string=open(alipay_public_key_path).read(),
            sign_type="RSA2",
            debug=True,
        )
        data = dict(request.GET.items())
        signature = data.pop("sign", None)
        print(data)
        success = alipay.verify(data, signature)
        order_sn = data.get('out_trade_no', None)
        print(success)
        trade_status = alipay.api_alipay_trade_query(out_trade_no=order_sn).get("trade_status", None)
        print(trade_status)
        if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
            trade_no = data.get('trade_no', None)
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False)
            if existed_orders:
                for order in existed_orders:
                    order.pay_status = trade_status
                    order.trade_no = trade_no
                    order.pay_time = datetime.now()
                    order.save()
                response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order')
                response.set_cookie('nextPath', 'pay', max_age=2)
                print('cookie', response.cookies)
                return response
        return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart')

    def post(self, request):
        alipay = AliPay(
            appid=ali_app_id,
            app_notify_url=None,
            app_private_key_string=open(app_private_key_path).read(),
            alipay_public_key_string=open(alipay_public_key_path).read(),
            sign_type="RSA2",
            debug=True,
        )
        data = dict(request.POST.items())
        signature = data.pop("sign", None)
        success = alipay.verify(data, signature)
        trade_status = alipay.api_alipay_trade_query(out_trade_no="2020073117084113366").get("trade_status", None)
        if success and trade_status in ("TRADE_SUCCESS", "TRADE_FINISHED"):
            order_sn = data.get('out_trade_no', None)
            trade_no = data.get('trade_no', None)
            existed_orders = OrderInfo.objects.filter(order_sn=order_sn, is_delete=False)
            print(len(existed_orders))
            if existed_orders:
                for order in existed_orders:
                    order.pay_status = trade_status
                    order.trade_no = trade_no
                    order.pay_time = datetime.now()
                    order.save()
                response = HttpResponseRedirect('http://127.0.0.1:8080/#/app/home/member/order')
                response.set_cookie('nextPath', 'pay', max_age=2)
                print('cookie', response.cookies)
                return response
        return HttpResponseRedirect('http://127.0.0.1:8080/#/app/shoppingcart/cart')

演示如下:

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Python 字典 使用技巧

    可以看出,键十从1到676,值是26个英文大写字母的组合。 我们首先要做的是找到字母组合的方法,首先想到的应该是利用chr()函数得到整型对应的字符,范围...

    cutercorley
  • 商业数据分析从入门到入职(1)商业数据分析综述

    先列举几个案例: (1)请估计一下2020年八月份在北京卖出有多少双鞋子? 显然,这是一个很开放的问题,并不像在学校里的题目都有标准答案,是需要经过自己的思...

    cutercorley
  • C语言入门系列之4.分支结构程序-关系、逻辑运算和if、switch语句

    关系运算符: 又叫比较运算符,在程序中经常需要比较两个量的大小关系,以决定程序下一步的工作。比较两个量的运算符称为关系运算符。

    cutercorley
  • [译] 定制 create-react-app:如何制作自己的模版

    原文:https://auth0.com/blog/how-to-configure-create-react-app/#Test-Your-Custom-Sc...

    江米小枣
  • 渗透测试该如何全面检测网站漏洞

    昨天给大家普及到了渗透测试中执行命令漏洞的检测方法,今天抽出时间由我们Sine安全的渗透工程师来讲下遇到文件包含漏洞以及模板注入漏洞的检测方法和防御手段,本文仅...

    技术分享达人
  • 渗透测试该如何全面检测网站漏洞

    昨天给大家普及到了渗透测试中执行命令漏洞的检测方法,今天抽出时间由我们Sine安全的渗透工程师来讲下遇到文件包含漏洞以及模板注入漏洞的检测方法和防御手段,本文仅...

    网站安全专家
  • R语言作图——Dot plot(点图)

    2020开年不凡, 生活果然充满了惊吓,可仍然要期待惊喜呀。今天要给大家介绍的是点图(Dot plot),点图展示的数据比较简单,但胜在好看啊。

    一只羊
  • 日志分析系统——Hangout源码学习

    这两天看了下hangout的代码,虽然没有运行体验过,但是也算是学习了一点皮毛。 架构浅谈 Hangout可以说是java版的Logstash,我是没有测...

    用户1154259
  • Class.forName 和 ClassLoader 到底有啥区别?

    来源 | https://www.cnblogs.com/jimoer/p/9185662.html

    用户1516716
  • 好雨云资深架构师祁世垚参加Qcon演讲,现场反响热烈

    Rainbond开源

扫码关注云+社区

领取腾讯云代金券