今日内容, 生成订单和支付宝支付接口
-
支付宝支付介绍
咱们项目以支付宝支付为例
API,sdk
sdk:第三方sdk,基于API封装的
官方sdk:https://opendocs.alipay.com/open/02no41
支付宝支付介绍
申请条件很严苛
沙箱环境:Sandbox:程序的虚拟执行环境,不需要申请各种认证,直接写程序,后期只需要换成只是的秘钥即可
支付流程图解:
# 使用沙箱环境 # 第三方的sdk:https://github.com/fzlee/alipay -基于支付宝的API接口封装的,开源软件, -pip3 install python-alipay-sdk # 官方sdk # 生成公钥私钥:支付宝提供咱们一个生成的工具 -https://opendocs.alipay.com/open/02np9g -生成公钥私钥,在本地(私钥好好保管) -非对称加密: -公钥,私钥 -加密使用公钥,解密使用私钥 -对称加密:加密和解密使用同一个秘钥 :AES,DES -我能保证秘钥不丢失 -解密也要同样的秘钥,解密的人万一把秘钥丢失了 # 把公钥填在支付宝网站上(沙箱环境,正式环境) -把咱么的公钥填进网站,会生成一个支付宝公钥,以后项目中咱们使用支付宝公钥来做
基本使用
from alipay import AliPay from alipay.utils import AliPayConfig # 初始化得到对象,传入一堆参数 alipay = AliPay( appid="2016092000554611", # app的id号 app_notify_url=None, # 默认回调 url app_private_key_string=open('./app_private_key.pem').read(), # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, alipay_public_key_string=open('./alipay_public_key.pem').read(), sign_type="RSA2", # RSA 或者 RSA2 debug=False, # 默认 False verbose=False, # 输出调试数据 config=AliPayConfig(timeout=15) # 可选,请求超时时间 ) # 生成支付链接 order_string = alipay.api_alipay_trade_page_pay( out_trade_no="asdfasdfasdfasdfasdfasdf", total_amount=99999, subject='精品内衣', return_url="https://example.com", # get回调地址 notify_url="https://example.com/notify" # post回调地址 ) # print('https://openapi.alipay.com/gateway.do?'+order_string) print('https://openapi.alipaydev.com/gateway.do?'+order_string)
-
支付宝支付二次封装
alipay_common # 包名 pem # 放公钥私钥 alipay_public_key.pem app_private_key.pem __init__.py pay.py # 核心文件 settings.py #配置文件 # 保证公钥私钥的安全: 1 环境变量 2 专门写一个公钥私钥管理的服务(项目),发送请求去获取,接口中带很多认证
init
from .pay import alipay from .settings import GATEWAY
pay.py
from alipay import AliPay from alipay.utils import AliPayConfig from . import settings # 初始化得到对象,传入一堆参数 alipay = AliPay( appid=settings.APPID, # app的id号 app_notify_url=None, # 默认回调 url app_private_key_string=settings.APP_PRIVATE_KEY_STRING, # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥, alipay_public_key_string=settings.ALIPAY_PUBLIC_KEY_STRING, sign_type=settings.SIGN, # RSA 或者 RSA2 debug=settings.DEBUG, # 默认 False verbose=False, # 输出调试数据 config=AliPayConfig(timeout=15) # 可选,请求超时时间 )
settings.py
import os APPID = '2016092000554611' # 应用私钥 APP_PRIVATE_KEY_STRING = open( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'app_private_key.pem')).read() # 支付宝公钥 ALIPAY_PUBLIC_KEY_STRING = open( os.path.join(os.path.dirname(os.path.abspath(__file__)), 'pem', 'alipay_public_key.pem')).read() # 加密方式 SIGN = 'RSA2' # 是否是支付宝测试环境(沙箱环境),如果采用真是支付宝环境,配置False DEBUG = True # 支付网关 GATEWAY = 'https://openapi.alipaydev.com/gateway.do' if DEBUG else 'https://openapi.alipay.com/gateway.do'
-
订单相关表设计
#1 创建订单app #2 设计表 -订单表 -订单详情表 :订单表和订单详情是一对多的关系 from django.db import models from user.models import UserInfo from course.models import Course # Create your models here. # 订单表 class Order(models.Model): """订单模型""" status_choices = ( (0, '未支付'), (1, '已支付'), (2, '已取消'), (3, '超时取消'), ) pay_choices = ( (1, '支付宝'), (2, '微信支付'), ) subject = models.CharField(max_length=150, verbose_name="订单标题") total_amount = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="订单总价", default=0) # 唯一的,咱们生成的, uuid out_trade_no = models.CharField(max_length=64, verbose_name="订单号", unique=True) # 支付宝 成功后,会有个流水号 trade_no = models.CharField(max_length=64, null=True, verbose_name="流水号") # 订单状态 order_status = models.SmallIntegerField(choices=status_choices, default=0, verbose_name="订单状态") pay_type = models.SmallIntegerField(choices=pay_choices, default=1, verbose_name="支付方式") # 支付时间:支付宝支付完成返回的数据中有这个字段 pay_time = models.DateTimeField(null=True, verbose_name="支付时间") # 订单创建时间,不一定支付 created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间') user = models.ForeignKey(UserInfo, related_name='order_user', on_delete=models.DO_NOTHING, db_constraint=False, verbose_name="下单用户") class Meta: db_table = "luffy_order" verbose_name = "订单记录" verbose_name_plural = "订单记录" def __str__(self): return "%s - ¥%s" % (self.subject, self.total_amount) @property def courses(self): data_list = [] for item in self.order_courses.all(): data_list.append({ "id": item.id, "course_name": item.course.name, "real_price": item.real_price, }) return data_list # 订单详情表 class OrderDetail(models.Model): """订单详情""" order = models.ForeignKey(Order, related_name='order_courses', on_delete=models.CASCADE, db_constraint=False, verbose_name="订单") course = models.ForeignKey(Course, related_name='course_orders', on_delete=models.CASCADE, db_constraint=False, verbose_name="课程") price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程原价") real_price = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="课程实价") class Meta: db_table = "luffy_order_detail" verbose_name = "订单详情" verbose_name_plural = "订单详情" def __str__(self): try: return "%s的订单:%s" % (self.course.name, self.order.out_trade_no) except: return super().__str__() # 3 迁移数据
-
生成订单接口
# 前端:点击购买按钮----》向后端发送请求【登录认证】,携带数据{courses:[1,2,3],total_amount:999,subject:订单名,pay_type:1}---》 # 后端: -两个重要的事:生成支付链接,在订单表中插入数据 -视图类:create,序列化类:数据校验,生成支付链接
路由
from django.contrib import admin from django.urls import path, re_path from home import views from django.views.static import serve from django.conf import settings from . import views from rest_framework.routers import SimpleRouter router = SimpleRouter() # 127.0.0.1:8000/api/v1/order/alipay/---->post 请求 router.register('alipay',views.OrderView,'alipay') urlpatterns = [ ] urlpatterns += router.urls
视图类
from django.shortcuts import render # Create your views here. from .models import Order # 自动生成路由,新增功能:订单表存数据 from rest_framework.viewsets import GenericViewSet from rest_framework.mixins import CreateModelMixin from .serializer import OrderSerializer from utils.response import APIResponse from rest_framework_jwt.authentication import JSONWebTokenAuthentication from rest_framework.permissions import IsAuthenticated class OrderView(GenericViewSet): queryset = Order.objects.all() serializer_class = OrderSerializer # 登录后才能访问 authentication_classes = [JSONWebTokenAuthentication] permission_classes = [IsAuthenticated] # 这个新增接口,既要新增,又要返回支付链接,冲写create方法 def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data,context={'request':request}) serializer.is_valid(raise_exception=True) serializer.save() pay_url = serializer.context.get('pay_url') return APIResponse(pay_url=pay_url)
序列化类
from rest_framework import serializers from .models import Order, OrderDetail from course.models import Course from rest_framework.exceptions import APIException from django.conf import settings class OrderSerializer(serializers.ModelSerializer): # courses不是order表的字段,一定要重写 # 前端传入的是 courses:[1,3,4]----->转换成课程对象[课程对象1,课程对象3,课程对象4] courses = serializers.PrimaryKeyRelatedField(queryset=Course.objects.all(), write_only=True, many=True) class Meta: model = Order fields = ['courses', 'total_amount', 'subject', 'pay_type'] # 只用来做数据校验和反序列化 def _check_price(self, attrs): total_amount = attrs.get('total_amount') # 循环所有课程,取出价格累加得到总价格 real_total = 0 for course in attrs.get('courses'): real_total += course.price if not total_amount == real_total: # 不正常,抛异常 raise APIException('价格不一致') else: return real_total def _get_trade_no(self): import uuid return str(uuid.uuid4()) def _get_user(self): # 当前登录用户,request.user request = self.context.get('request') return request.user # 必须通过了登录认证,才有当前登录用户 def _get_pay_url(self, total_amount, subject, trade_no): from libs.alipay_common import GATEWAY, alipay pay_str = alipay.api_alipay_trade_page_pay( out_trade_no=trade_no, total_amount=float(total_amount), subject=subject, return_url=settings.RETURN_URL, # get回调地址 notify_url=settings.NOTIFY_URL # post回调地址 ) return GATEWAY + pay_str def _before_create(self, pay_url, attrs, user, trade_no): self.context['pay_url'] = pay_url attrs['user'] = user attrs['out_trade_no'] = trade_no # 要不要courses剔除? attrs:{courses:[对象1,对象2],total_amount:11,subject:xx,pay_type:1,user:user,out_trade_no:3333} def validate(self, attrs): # 1 校验价格:计算一下总价格和后端算出来的总价格是否一致 total_amount = self._check_price(attrs) # 2)生成订单号:唯一的, trade_no = self._get_trade_no() # 3)支付用户:request.user user = self._get_user() # 4)支付链接生成,放入到self.context中 pay_url = self._get_pay_url(total_amount, attrs.get('subject'), trade_no) # 5)入库(两个表)的信息准备:重写create方法 self._before_create(pay_url, attrs, user, trade_no) return attrs def create(self, validated_data): # {courses:[对象1,对象2],total_amount:11,subject:xx,pay_type:1,user:user,out_trade_no:3333} courses = validated_data.pop('courses') order = Order.objects.create(**validated_data) # 存订单详情表 for course in courses: OrderDetail.objects.create(order=order, course=course, price=course.price, real_price=course.price) return order
-
支付前端
# 在点击支付按钮上添加 go_buy(price, subject) { let token = this.$cookies.get('token') if (token) { // 发送ajax请求 this.$axios.post(this.$settings.BASE_URL + 'order/alipay/', { courses: [this.course_id], total_amount: price, subject: subject, pay_type: 1 }, { headers: { 'Authorization': 'jwt ' + token } }).then(res => { if (res.data.code == 100) { // 打开支付链接 open(res.data.pay_url, '_self') } else { this.$message({ message: res.data.msg, type: 'error' }); } }) } else { this.$message({ message: '您没有登录,请先登录', type: 'error' }); } }
支付成功页面PaySucess.vue
<template> <div class="pay-success"> <!--如果是单独的页面,就没必要展示导航栏(带有登录的用户)--> <Header/> <div class="main"> <div class="title"> <div class="success-tips"> <p class="tips">您已成功购买 1 门课程!</p> </div> </div> <div class="order-info"> <p class="info"><b>订单号:</b><span>{{ result.out_trade_no }}</span></p> <p class="info"><b>交易号:</b><span>{{ result.trade_no }}</span></p> <p class="info"><b>付款时间:</b><span><span>{{ result.timestamp }}</span></span></p> </div> <div class="study"> <span>立即学习</span> </div> </div> </div> </template> <script> import Header from "@/components/Header" export default { name: "Success", data() { return { result: {}, }; }, created() { // url后拼接的参数:?及后面的所有参数 => ?a=1&b=2 // console.log(location.search); // 解析支付宝回调的url参数 let params = location.search.substring(1); // 去除? => a=1&b=2 let items = params.length ? params.split('&') : []; // ['a=1', 'b=2'] //逐个将每一项添加到args对象中 for (let i = 0; i < items.length; i++) { // 第一次循环a=1,第二次b=2 let k_v = items[i].split('='); // ['a', '1'] //解码操作,因为查询字符串经过编码的 if (k_v.length >= 2) { // url编码反解 let k = decodeURIComponent(k_v[0]); this.result[k] = decodeURIComponent(k_v[1]); // 没有url编码反解 // this.result[k_v[0]] = k_v[1]; } } // 解析后的结果 // console.log(this.result); // 把地址栏上面的支付结果,再get请求转发给后端 this.$axios({ url: this.$settings.base_url + '/order/success/' + location.search, method: 'get', }).then(response => { console.log(response.data); }).catch(() => { console.log('支付结果同步失败'); }) }, components: { Header, } } </script> <style scoped> .main { padding: 60px 0; margin: 0 auto; width: 1200px; background: #fff; } .main .title { display: flex; -ms-flex-align: center; align-items: center; padding: 25px 40px; border-bottom: 1px solid #f2f2f2; } .main .title .success-tips { box-sizing: border-box; } .title img { vertical-align: middle; width: 60px; height: 60px; margin-right: 40px; } .title .success-tips { box-sizing: border-box; } .title .tips { font-size: 26px; color: #000; } .info span { color: #ec6730; } .order-info { padding: 25px 48px; padding-bottom: 15px; border-bottom: 1px solid #f2f2f2; } .order-info p { display: -ms-flexbox; display: flex; margin-bottom: 10px; font-size: 16px; } .order-info p b { font-weight: 400; color: #9d9d9d; white-space: nowrap; } .study { padding: 25px 40px; } .study span { display: block; width: 140px; height: 42px; text-align: center; line-height: 42px; cursor: pointer; background: #ffc210; border-radius: 6px; font-size: 16px; color: #fff; } </style>
-
支付宝回调接口
# 支付成功,支付宝会有俩回调 -get 回调,调前端 -为了保证准确性,支付宝回调会前端后,我们自己向后端发送一个请求,查询一下这个订单是否支付成功 -post 回调,调后端接口 -后端接口,接受支付宝的回调,修改订单状态 -这个接口需要登录吗?不需要任何的认证和权限 -如果用户点了支付----》跳转到了支付宝页面---》你的服务挂机了---》会出现什么情况 -支付宝在24小时内,会有8次回调, # 两个接口: -post回调,给支付宝用 -get回调,给我们前端做二次校验使用
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通