支付宝支付

打印 上一主题 下一主题

主题 983|帖子 983|积分 2949

支付宝支付流程


在python中封装alipay

安装
  1. >: pip install python-alipay-sdk --upgrade
  2. # 如果抛ssl相关错误,代表缺失该包
  3. >: pip install pyopenssl
复制代码
结构
  1. libs
  2.     ├── AliPay                                                          # aliapy二次封装包
  3.     │   ├── __init__.py                                 # 包文件
  4.     │   ├── pem                                                        # 公钥私钥文件夹
  5.     │   │   ├── alipay_public_key.pem        # 支付宝公钥文件
  6.     │   │   ├── app_private_key.pem                # 应用私钥文件
  7.     │   ├── pay.py                                                # 支付文件
  8.     └── └── settings.py                                  # 应用配置
复制代码
alipay_public_key.pem

https://openhome.alipay.com/develop/sandbox/account
前往支付宝开放平台,注册沙箱环境获取支付宝公钥和应用私钥,进行测试。
  1. -----BEGIN PUBLIC KEY-----
  2. 拿应用公钥跟支付宝换来的支付宝公钥
  3. -----END PUBLIC KEY-----
复制代码
app_private_key.pem
  1. -----BEGIN RSA PRIVATE KEY-----
  2. 通过支付宝公钥私钥签发软件签发的应用私钥
  3. -----END RSA PRIVATE KEY-----
复制代码
setting.py
  1. import os
  2. # 应用私钥
  3. APP_PRIVATE_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'app_private_key.pem')).read()
  4. # 支付宝公钥
  5. ALIPAY_PUBLIC_KEY_STRING = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'alipay_public_key.pem')).read()
  6. # 应用ID
  7. APP_ID = '2016093000631831'
  8. # 加密方式
  9. SIGN = 'RSA2'
  10. # 是否是支付宝测试环境(沙箱环境),如果采用真是支付宝环境,配置False
  11. DEBUG = True
  12. # 支付网关
  13. GATEWAY = 'https://openapi.alipaydev.com/gateway.do?' if DEBUG else 'https://openapi.alipay.com/gateway.do?'
复制代码
pay.py
  1. from alipay import AliPay
  2. from . import settings
  3. # 支付对象
  4. alipay = AliPay(
  5.     appid=settings.APP_ID,
  6.     app_notify_url=None,
  7.     app_private_key_string=settings.APP_PRIVATE_KEY_STRING,
  8.     alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING,
  9.     sign_type=settings.SIGN,
  10.     debug=settings.DEBUG
  11. )
  12. # 支付网关
  13. gateway = settings.GATEWAY
复制代码
init.py
  1. # 包对外提供的变量
  2. from .pay import gateway, alipay
复制代码
支付相关模型表的设计
  1. """
  2. class Order(models.Model):
  3.     # 主键、总金额、订单名、订单号、订单状态、创建时间、支付时间、流水号、支付方式、支付人(外键) - 优惠劵(外键,可为空)
  4.     pass
  5. class OrderDetail(models.Model):
  6.     # 订单号(外键)、商品(外键)、实价、成交价 - 商品数量
  7.     pass
  8. """
复制代码
  1. from django.db import models
  2. from user.models import User
  3. from course.models import Course
  4. class Order(models.Model):
  5.     """订单模型"""
  6.     status_choices = (
  7.         (0, '未支付'),
  8.         (1, '已支付'),
  9.         (2, '已取消'),
  10.         (3, '超时取消'),
  11.     )
  12.     pay_choices = (
  13.         (1, '支付宝'),
  14.         (2, '微信支付'),
  15.     )
  16.     subject = models.CharField(max_length=150, verbose_name="订单标题")
  17.     total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0)
  18.     out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True)
  19.     trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号")
  20.     order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态")
  21.     pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式")
  22.     pay_time = models.DateTimeField(null=True, verbose_name="支付时间")
  23.     user = models.ForeignKey(User, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户")
  24.     created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
  25.     class Meta:
  26.         db_table = "luffy_order"
  27.         verbose_name = "订单记录"
  28.         verbose_name_plural = "订单记录"
  29.     def __str__(self):
  30.         return "%s - ¥%s" % (self.subject, self.total_amount)
  31.     @property
  32.     def courses(self):
  33.         data_list = []
  34.         for item in self.order_courses.all():
  35.             data_list.append({
  36.                 "id": item.id,
  37.                 "course_name": item.course.name,
  38.                 "real_price": item.real_price,
  39.             })
  40.         return data_list
  41. class OrderDetail(models.Model):
  42.     """订单详情"""
  43.     order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False, verbose_name="订单")
  44.     course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False, verbose_name="课程")
  45.     price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价")
  46.     real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价")
  47.     class Meta:
  48.         db_table = "luffy_order_detail"
  49.         verbose_name = "订单详情"
  50.         verbose_name_plural = "订单详情"
  51.     def __str__(self):
  52.         try:
  53.             return "%s的订单:%s" % (self.course.name, self.order.out_trade_no)
  54.         except:
  55.             return super().__str__()
复制代码
订单模块接口的设计分析

1.点击购买,产生支付接口(生成支付订单(往order和订单详情表插入数据),生成支付链接,返回支付链接)
2.支付宝异步回调的post接口(验证签名、修改订单状态)
3.当支付宝get回调给前端页面(告诉前端支付成功了),此时vue组件一创建,我们在写一个get请求询问后端是否支付成功。
支付接口

serializes.py
  1. from rest_framework import serializers
  2. from . import models
  3. from rest_framework.exceptions import ValidationError
  4. from course.models import Course
  5. from django.conf import settings
  6. class OrderSerializer(serializers.ModelSerializer):
  7.     # 要支持单购物和群购物(购物车),前台要提交 课程主键(们)
  8.     # PrimaryKeyRelatedField方法的作用就是把课程id的列表对象,转换为课程对象的列表对象
  9.     course = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True)
  10.     class Meta:
  11.         model = models.Order
  12.         fields = ['subject','total_amount','pay_type','course'] # 这些字段是需要前端传给我们进行反序列化的
  13.     def _check_price(self,attrs):
  14.         total_amount = attrs.get('total_amount')
  15.         course_list = attrs.get('course')
  16.         total_price = 0
  17.         for course in course_list:
  18.             total_price += course.price
  19.         if total_amount != total_price:
  20.             raise ValidationError('价格不正确')
  21.         return total_amount
  22.     def _gen_order(self):
  23.         import uuid
  24.         return str(uuid.uuid4()).replace('-','')
  25.     def _get_user(self):
  26.         request = self.context.get('request')
  27.         return request.user
  28.     def _get_pay_url(self,out_trade_no,total_amount,subject):
  29.         from luffyapi.libs.al_pay import alipay,gateway
  30.         order_string = alipay.api_alipay_trade_page_pay    (
  31.             out_trade_no=out_trade_no,
  32.             total_amount=total_amount,
  33.             subject=subject,
  34.             return_url=settings.RETURN_URL,
  35.             notify_url=settings.NOTIFY_URL
  36.         )
  37.         return gateway+order_string
  38.     def _before_create(self,attrs,user,pay_url,out_trade_no):
  39.         attrs['user'] = user
  40.         attrs['out_trade_no'] = out_trade_no
  41.         self.context['pay_url']=pay_url
  42.     def validate(self, attrs):
  43.         """
  44.         #1)订单总价校验
  45.         # 2)生成订单号
  46.         # 3)支付用户:request.user
  47.         # 4)支付链接生成
  48.         # 5)入库(两个表)的信息准备
  49.         :param attrs:
  50.         :return:
  51.         """
  52.         # 1)订单总价校验
  53.         total_amount = self._check_price(attrs)
  54.         # 2)生成订单号
  55.         out_trade_no = self._gen_order()
  56.         # 3)支付用户:request.user
  57.         user = self._get_user()
  58.         # 4)支付链接生成
  59.         pay_url = self._get_pay_url(out_trade_no,total_amount,attrs.get('subject'))
  60.         # 5)入库(两个表)的信息准备
  61.         self._before_create(attrs,user,pay_url,out_trade_no)
  62.         return attrs
  63.     # 重写create方法
  64.     def create(self, validated_data):
  65.         course_list = validated_data.pop('course')
  66.         order = models.Order.objects.create(**validated_data)
  67.         for course in course_list:
  68.             models.OrderDetail.objects.create(order=order,course=course,price=course.price,real_price=course.price)
  69.         return order
复制代码
views.py
  1. from django.shortcuts import render
  2. from rest_framework.viewsets import ViewSet,GenericViewSet
  3. from rest_framework.mixins import CreateModelMixin
  4. from rest_framework.decorators import action
  5. from luffyapi.utils.response import APIResponse
  6. from . import models
  7. from . import serializers
  8. from rest_framework_jwt.authentication import JSONWebTokenAuthentication
  9. from rest_framework.permissions import IsAuthenticated
  10. from rest_framework.response import Response
  11. class PayView(GenericViewSet,CreateModelMixin):
  12.     queryset = models.Order.objects.all()
  13.     serializer_class = serializers.OrderSerializer
  14.     # 下面这两句话是为了给立即购买接口增加认证和权限:只有登录用户才能点击购买
  15.     authentication_classes = [JSONWebTokenAuthentication,]
  16.     permission_classes = [IsAuthenticated,]
  17.     # 重写CreateModelMixin里的create方法,返回自定义的response和添加context
  18.     def create(self, request, *args, **kwargs):
  19.         serializer = self.get_serializer(data=request.data,context={'request':request})
  20.         serializer.is_valid(raise_exception=True)
  21.         self.perform_create(serializer)
  22.         return Response(serializer.context.get('pay_url'))
复制代码
前端在立即购买按钮下定义一个方法
  1.     // 点击立即购买时,定义一个方法并把课程信息传入
  2.     buy_now(course) {
  3.       // 先获取一个token,获取不到则提示需要先登录
  4.       let token = this.$cookies.get('token')
  5.       if (!token) {
  6.         this.$message({
  7.           message: "请先登录!",
  8.         })
  9.         return false
  10.       }
  11.       // 有token,则想后端发送post请求,拿到支付链接
  12.       this.$axios(
  13.           {
  14.             method: 'post',
  15.             url: this.$settings.base_url + '/order/pay/',
  16.             data: {
  17.               "subject": course.name,
  18.               "total_amount":course.price,
  19.               "pay_type": 1,
  20.               "course": [
  21.                 course.id,
  22.               ]
  23.             },
  24.             headers: {Authorization: 'jwt ' + token},
  25.           }
  26.       )
  27.           .then(response => {
  28.             // 成功拿到支付链接之后,采用open方法,指定页面跳转支付页面
  29.             let pay_url = response.data
  30.             // _self指定当前页面直接跳,而不是在新窗口跳转
  31.             open(pay_url,'_self')
  32.           }).catch(error => {
  33.       })
  34.     },
复制代码
支付成功前端页面
  1. <template>
  2.    
  3.         
  4.         <Header/>
  5.         
  6.             
  7.                
  8.                     <p >您已成功购买 1 门课程!</p>
  9.                
  10.             
  11.             
  12.                 <p ><b>订单号:</b>{{ result.out_trade_no }}</p>
  13.                 <p ><b>交易号:</b>{{ result.trade_no }}</p>
  14.                 <p ><b>付款时间:</b>{{ result.timestamp }}</p>
  15.             
  16.             
  17.                 立即学习
  18.             
  19.         
  20.    
  21. </template>
复制代码
  1.     {
  2.         path: '/pay/success/',
  3.         name: 'PaySuccess',
  4.         component: PaySuccess
  5.     },
复制代码
支付宝同步异步回调

支付成功之后,支付宝会给前端发送一个post回调,我们也需要在用户支付成功之后,在前端添加一个同步的get回调,以此来反馈给前端,用户是否支付成功!!
  1.     path('success/',views.SuccessView.as_view()),
复制代码
  1. class SuccessView(APIView):
  2.     def get(self,request,*args,**kwargs):
  3.         out_trade_no=request.query_params.get('out_trade_no')
  4.         order=models.Order.objects.filter(out_trade_no=out_trade_no).first()
  5.         if order.order_status==1:
  6.             return Response(True)
  7.         else:
  8.             return Response(False)
  9.     def post(self,request,*args,**kwargs):
  10.         '''
  11.         支付宝回调接口
  12.         '''
  13.         from luffyapi.libs.al_pay import alipay
  14.         from luffyapi.utils.logger import log
  15.         data = request.data
  16.         out_trade_no=data.get('out_trade_no',None)
  17.         gmt_payment=data.get('gmt_payment',None)
  18.         signature = data.pop("sign")
  19.         # 验证签名
  20.         success = alipay.verify(data, signature)
  21.         if success and data["trade_status"] in ("TRADE_SUCCESS", "TRADE_FINISHED"):
  22.             models.Order.objects.filter(out_trade_no=out_trade_no).update(order_status=1,pay_time=gmt_payment)
  23.             log.info('%s订单支付成功'%out_trade_no)
  24.             return Response('success')  # 这里必须按支付宝规范返回一个success
  25.         else:
  26.             log.error('%s订单有问题' % out_trade_no)
  27.             return Response('error')
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

涛声依旧在

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表