• Luffy之支付宝支付开发API


    发起支付

    接入支付宝

    支付的大致流程如下图:

                                                        

    部分节点详解:

    沙箱环境

     

    支付宝开发者文档

     

    开发支付功能

    cd luffy/apps
    python ../../manage.py startapp payments

    配置秘钥

    1. 生成应用的私钥和公钥

    下载对应系统的秘钥生成工具: https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1

    应用公钥复制粘贴到支付宝网站页面中.

    2. 保存应用私钥文件

    在payments应用中新建keys目录,用来保存秘钥文件。

    将应用私钥文件app_private_key.pem复制到payment/keys目录下。

    -----BEGIN RSA PRIVATE KEY-----
    私钥
    -----END RSA PRIVATE KEY-----

    3. 保存支付宝公钥

    在payment/keys目录下新建alipay_public_key.pem文件,用于保存支付宝的公钥文件。

    将支付宝的公钥内容复制到alipay_public_key.pem文件中

     -----BEGIN PUBLIC KEY-----
    公钥
    -----END PUBLIC KEY-----

     

    4. 使用支付宝的sdk开发支付接口

    SDK:https://docs.open.alipay.com/270/106291/

    python版本的支付宝SDK文档:https://github.com/fzlee/alipay/blob/master/README.zh-hans.md

    安装命令:

    pip install python-alipay-sdk --upgrade

     

    流程思路:

      1.在order页面用户点击支付宝支付时,前端需要向后端发送带有订单ID值得请求,让后端通过上面安装的支付宝sdk构造链接返回来,然后再接受响应的地方跳转到支付宝提供的支付界面

    <template>
     .....
           <el-col :span="4" class="cart-pay" ><span @click="payhandler" >支付宝支付</span></el-col>
    ....
    </template>
    
    <script>
      export default {
        name:"Order",
       
        methods:{
          payhandler(){
             // 判断用户是否已经登陆了。
          if( !this.token){
            this.$router.push("/login");
          }
    
           let _this = this;
          // 发起请求获取购物车中的商品信息
          _this.$axios.get("http://127.0.0.1:8000/pay/"+_this.order_id,{
              headers: {
                  'Authorization': 'JWT ' + _this.token
              },
              responseType: 'json',
            }).then(response=>{
              console.log("response.data",response.data.url)
                //跳转页面到支付界面
               window.location.href=response.data.url;
    
            })
          }
        },
      }
    </script>

      2.后端接收到前端支付的请求,实现发起支付接口(生成支付链接返回),其中有许多配置项都需要注意,详细可参考开发者文档

    class PaymentAPIView(APIView):
        """支付宝"""
        permission_classes = (IsAuthenticated,)
    
        def get(self, request, order_id):
            """获取支付链接"""
            # 判断订单信息是否正确
            try:
                order = Order.objects.get(order_id=order_id, user=request.user,
                                              order_status=0,)
            except Order.DoesNotExist:
                return Response({'message': '订单信息有误'}, status=status.HTTP_400_BAD_REQUEST)
    
            # 构造支付宝支付链接地址
            alipay = AliPay(
                appid=settings.ALIPAY_APPID,
                app_notify_url=None,  # 默认回调url
                app_private_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/app_private_key.pem"),
                alipay_public_key_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), "keys/alipay_public_key.pem"),  # 支付宝的公钥,验证支付宝回传消息使用,不是你自己的公钥,
                sign_type="RSA2",  # RSA 或者 RSA2
                debug=settings.ALIPAY_DEBUG
            )
    
            order_string = alipay.api_alipay_trade_page_pay(
                out_trade_no=order.id,
                total_amount=str(order.total_price),
                subject=order.order_desc,
                return_url="http://127.0.0.1:8080/pay_success",
            )
            alipay_url = settings.ALIPAY_URL + "?" + order_string
            return Response({'alipay_url': alipay_url}, status=status.HTTP_201_CREATED)

    在配置文件中编辑支付宝的配置信息[实际的值根据自己的账号而定]

    # 支付宝
    ALIPAY_APP_ID="2016091600523592" # 应用ID
    APLIPAY_APP_NOTIFY_URL = None      # 应用回调地址[支付成功以后,支付宝返回结果到哪一个地址下面]
    APP_PRIVATE_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/app_private_key.pem")
    ALIPAY_PUBLIC_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/alipay_public_key.pem")
    ALIPAY_DEBUG = True
    # APIPAY_GATEWAY="https://openapi.alipay.com/gateway.do"   #上线使用
    APIPAY_GATEWAY="https://openapi.alipaydev.com/gateway.do"  #开发环境使用
    ALIPAY_RETURN_URL = "http://127.0.0.1:8080/success"
    ALIPAY_NOTIFY_URL = "http://127.0.0.1:8080/success"

      3.当购买人支付成功后,支付宝会跳转链接到之前我们配置好的页面中,("http://127.0.0.1:8080/success"),所以需要提前写好购买成功的界面,并在该页面向后端发送购买成功的信息,此时后端会验证更新订单状态,包括写入支付日期,以及订单状态的更改等

    success.vue 前端发起请求页面:..

    <template>
    .....
    </template>
    
    <script>
      import Header from "./common/Header"
      import Footer from "./common/Footer"
      export default{
        created(){
          // 页面刷新时,最开始时候要把支付宝服务器的返回get参数结果提交给后端
          // 后端需要根据这结果修改订单的状态
          this.$axios.get("http://127.0.0.1:8000/pay/result"+location.search,{
              headers: {
                  'Authorization': 'JWT ' + this.token
              },
              responseType: 'json',
            }).then(response=>{
              this.result = response.data
          }).catch(error=>{
              console.log(error.response);
          })
    </script>
    
    

    注意,此时后端发送的数据,必须要携带支付宝在表头返回的数据,以用于后端验证

    支付宝会返回的参数如下列表:(前面是域名,后面才是参数)

    http://127.0.0.1:8080/success?charset=utf-8&
    out_trade_no=2019040217080000000010976&
    method=alipay.trade.page.pay.return&
    total_amount=1206.44&
    sign=XKJG5826fH%2F9%2B3jCWw2ODjlc%2FuGLfqmr5RnimSAqrh%2B5bFkWcbLDh5V6VYtMqCpwnYp3FuGPqEeUeRO6WK62Qz0Q5nQGOA394IdxPfTOzry7PXuwYf41PCbDq53yg7vCYrobz4Tt8uajeADJLJwIsL%2F%2B88vbDEISUDUujL4442kl3oLh3EDD8DxZc2LLsv1Z%2FEFGJMfcTA47A4T7qmjB%2BbLKJetZZBISdt9RDL0q8A%2BAfb8B3Ux1nq%2F0EiNGiwIlWC1pvUCHK2UXMJW3kmgU9P9Zoujrj4ER28oieQt6Rt4gQXeah5uYtAMkftWfZpiyu%2FjUkr6iRx%2B4mP5IFz4Uew%3D%3D&
    trade_no=2019040222001439881000005802&
    auth_app_id=2016091600523592&
    version=1.0&
    app_id=2016091600523592&
    sign_type=RSA2&
    seller_id=2088102175868026&
    timestamp=2019-04-02%2017%3A13%3A15

    4.后端对发送过来的请求,会进行订单是否成功的验证,若验证成功,则会修改相应的订单状态,并返回相应的订单的信息内容,以供前端渲染数据

    后端代码如下:

     1 class PaymentAPIView(APIView):
     2   .......
     3 
     4 
     5 class PayResultAPIView(APIView):
     6     def get(self,request):
     7         """处理get返回结果的数据"""
     8         # 接受数据
     9         data = request.query_params.dict()
    10         print(data)
    11         # sign 不能参与签名验证
    12         signature = data.pop("sign")
    13         # print(json.dumps(data))
    14         # print(signature)
    15         alipay = AliPay(
    16             appid=settings.ALIPAY_APP_ID,
    17             app_notify_url=None,  # 默认回调url
    18             # 应用私钥
    19             app_private_key_path=settings.APP_PRIVATE_KEY_PATH,
    20             # 支付宝的公钥,
    21             alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH,
    22             sign_type="RSA2",  # 密码加密的算法
    23             # 开发时属于调试模式
    24             debug = settings.ALIPAY_DEBUG  # 默认False
    25         )
    26 
    27         # verify验证支付结果,布尔值
    28         success = alipay.verify(data, signature)
    29 
    30         if success:
    31             # 支付成功
    32             order = Order.objects.get(order_number=data.get("out_trade_no"))
    33             order.order_status = 1 # 修改订单状态
    34             order.pay_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    35             order.save()
    36 
    37 
    38             return Response({
    39                 "length":order.order_course.count(),
    40                 "paytime": order.pay_time,
    41                 "price":order.total_price,
    42                 "info":order.order_desc,
    43             },status=status.HTTP_200_OK)
    44 
    45         return Response({"message":"订单没有变化"})
    46     def post(self,request):
    47         """提供给支付宝发送post数据"""
    48         # 参考上面的代码实现
    49         # 接受数据
    50         data = request.data.dict()
    51 
    52         # sign 不能参与签名验证
    53         signature = data.pop("sign")
    54         # print(json.dumps(data))
    55         # print(signature)
    56         alipay = AliPay(
    57             appid=settings.ALIPAY_APP_ID,
    58             app_notify_url=None,  # 默认回调url
    59             # 应用私钥
    60             app_private_key_path=settings.APP_PRIVATE_KEY_PATH,
    61             # 支付宝的公钥,
    62             alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH,
    63             sign_type="RSA2",  # 密码加密的算法
    64             # 开发时属于调试模式
    65             debug=settings.ALIPAY_DEBUG  # 默认False
    66         )
    67 
    68         # verify验证支付结果,布尔值
    69         success = alipay.verify(data, signature)
    70 
    71         if success:
    72             # 支付成功
    73             order = Order.objects.get(order_number=data.get("out_trade_no"))
    74             order.order_status = 1  # 修改订单状态
    75             order.pay_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    76             order.save()
    77 
    78             return Response({
    79                 "length": order.order_course.count(),
    80                 "paytime": order.pay_time,
    81                 "price": order.total_price,
    82                 "info": order.order_desc,
    83             }, status=status.HTTP_200_OK)
    84 
    85         return Response({"message": "订单没有变化"})

    完整代码,可详细看:

    前端代码:

    success.vue

    <template>
      <div class="success">
        <Header :current_page="current_page"/>
        <div class="main">
            <div class="title">
              <img src="../../static/images/right.svg" alt="">
              <div class="success-tips">
                  <p class="tips1">您已成功购买 {{result.length}} 门课程!</p>
                  <p class="tips2">你还可以加入QQ群 <span>747556033</span> 学习交流</p>
              </div>
            </div>
            <div class="order-info">
                <p class="info1"><b>付款时间:</b><span>{{result.paytime}}</span></p>
                <p class="info2"><b>付款金额:</b><span >{{result.price}}</span></p>
                <p class="info3"><b>课程信息:</b><span><span>《{{result.info}}》</span></span></p>
            </div>
            <div class="wechat-code">
              <img src="../../static/images/server.cf99f78.png" alt="" class="er">
              <p><img src="../../static/images/tan.svg" alt="">重要!微信扫码关注获得学习通知&amp;课程更新提醒!否则将严重影响学习进度和课程体验!</p>
            </div>
            <div class="study">
              <span>立即学习</span>
            </div>
        </div>
        <Footer/>
      </div>
    </template>
    
    <script>
      import Header from "./common/Header"
      import Footer from "./common/Footer"
      export default{
        name:"Success",
        data(){
          return {
            token: sessionStorage.token || localStorage.token,
            current_page:0,
            result:{},
          };
        },
        components:{
          Header,
          Footer,
        },
        created(){
          // 页面刷新时,最开始时候要把支付宝服务器的返回get参数结果提交给后端
          // 后端需要根据这结果修改订单的状态
          this.$axios.get("http://127.0.0.1:8000/pay/result"+location.search,{
              headers: {
                  'Authorization': 'JWT ' + this.token
              },
              responseType: 'json',
            }).then(response=>{
              this.result = response.data
          }).catch(error=>{
              console.log(error.response);
          })
        }
      }
    </script>
    
    <style scoped>
    .success{
      padding-top: 80px;
    }
    .main{
        height: 100%;
        padding-top: 25px;
        padding-bottom: 25px;
        margin: 0 auto;
         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;
         60px;
        height: 60px;
        margin-right: 40px;
    }
    .title .success-tips{
        box-sizing: border-box;
    }
    .title .tips1{
        font-size: 22px;
        color: #000;
    }
    .title .tips2{
        font-size: 16px;
        color: #4a4a4a;
        letter-spacing: 0;
        text-align: center;
        margin-top: 10px;
    }
    .title .tips2 span{
        color: #ec6730;
    }
    .order-info{
        padding: 25px 48px;
        padding-bottom: 15px;
        border-bottom: 1px solid #f2f2f2;
    }
    .order-info p{
      font-family: PingFangSC-Regular;
        display: -ms-flexbox;
        display: flex;
        margin-bottom: 10px;
        font-size: 16px;
    }
    .order-info p b{
      font-weight: 400;
      color: #9d9d9d;
      white-space: nowrap;
    }
    .wechat-code{
        display: flex;
        -ms-flex-align: center;
        align-items: center;
        padding: 25px 40px;
        border-bottom: 1px solid #f2f2f2;
    }
    .wechat-code>img{
         100px;
        height: 100px;
        margin-right: 15px;
    }
    .wechat-code p{
        font-family: PingFangSC-Regular;
        font-size: 14px;
        color: #d0021b;
        display: -ms-flexbox;
        display: flex;
        -ms-flex-align: center;
        align-items: center;
    }
    .wechat-code p>img{
         16px;
        height: 16px;
        margin-right: 10px;
    }
    .study{
          padding: 25px 40px;
    }
    .study span{
      display: block;
       140px;
      height: 42px;
      text-align: center;
      line-height: 42px;
      cursor: pointer;
      background: #ffc210;
      border-radius: 6px;
      font-family: PingFangSC-Regular;
      font-size: 16px;
      color: #fff;
    }
    </style>
    View Code

    order.vue:

    <template>
      <div class="cart">
        <Header/>
        <div class="cart-info">
            <h3 class="cart-top">购物车结算 <span>共1门课程</span></h3>
            <div class="cart-title">
               <el-row>
                 <el-col :span="2">&nbsp;</el-col>
                 <el-col :span="10">课程</el-col>
                 <el-col :span="8">有效期</el-col>
                 <el-col :span="4">价格</el-col>
               </el-row>
            </div>
              <div class="cart-item" v-for="item in course_list" >
              <el-row>
                 <el-col :span="2" class="checkbox">&nbsp;&nbsp;</el-col>
                 <el-col :span="10" class="course-info">
                   <img :src="item.course.course_http_img" alt="">
                    <span>{{item.course.name}}</span>
                 </el-col>
                 <el-col :span="8"><span>永久有效</span></el-col>
                 <el-col :span="4" class="course-price">¥{{item.unit_price}}</el-col>
               </el-row>
            </div>
            <div class="calc">
                <el-row class="pay-row">
                  <el-col :span="4" class="pay-col"><span class="pay-text">支付方式:</span></el-col>
                  <el-col :span="4"><span class="alipay"><img src="../../static/images/1554167287107.png" alt=""></span></el-col>
                  <el-col :span="12" class="count">实付款: <span>¥{{total}}</span></el-col>
                  <el-col :span="4" class="cart-pay" ><span @click="payhandler" >支付宝支付</span></el-col>
                </el-row>
            </div>
        </div>
        <Footer/>
      </div>
    </template>
    
    <script>
      import Header from "./common/Header"
      import Footer from "./common/Footer"
    
      export default {
        name:"Order",
        data(){
          return {
            total:0,
            course_list:[],
            token: localStorage.token || sessionStorage.token,
            id : localStorage.id || sessionStorage.id,
            order_id:sessionStorage.order_id || null,
          }
        },
    
        components:{
          Header,
          Footer,
    
        },
        methods:{
          payhandler(){
             // 判断用户是否已经登陆了。
          if( !this.token){
            this.$router.push("/login");
          }
    
           let _this = this;
          // 发起请求获取购物车中的商品信息
          _this.$axios.get("http://127.0.0.1:8000/pay/"+_this.order_id,{
              headers: {
                  'Authorization': 'JWT ' + _this.token
              },
              responseType: 'json',
            }).then(response=>{
              console.log("response.data",response.data.url)
                //跳转页面到支付界面
               window.location.href=response.data.url;
    
            })
          }
        },
        created() {
           // 判断用户是否已经登陆了。
          if( !this.token){
            this.$router.push("/login");
          }
    
           let _this = this;
          // 发起请求获取购物车中的商品信息
          _this.$axios.get("http://127.0.0.1:8000/orders/detail/"+_this.order_id,{
              headers: {
                  'Authorization': 'JWT ' + _this.token
              },
              responseType: 'json',
            }).then(response=>{
              console.log("response.data",response.data)
              _this.course_list = response.data.order_course;
              _this.total = response.data.total_price
    
            })
        },
      }
    </script>
    
    
    <style scoped>
    .cart{
      margin-top: 80px;
    }
    .cart-info{
      overflow: hidden;
       1200px;
      margin: auto;
    }
    .cart-top{
      font-size: 18px;
      color: #666;
      margin: 25px 0;
      font-weight: normal;
    }
    .cart-top span{
        font-size: 12px;
        color: #d0d0d0;
        display: inline-block;
    }
    .cart-title{
        background: #F7F7F7;
        height: 70px;
    }
    .calc{
      margin-top: 25px;
      margin-bottom: 40px;
    }
    
    .calc .count{
      text-align: right;
      margin-right: 10px;
      vertical-align: middle;
    }
    .calc .count span{
        font-size: 36px;
        color: #333;
    }
    .calc .cart-pay{
        margin-top: 5px;
         110px;
        height: 38px;
        outline: none;
        border: none;
        color: #fff;
        line-height: 38px;
        background: #ffc210;
        border-radius: 4px;
        font-size: 16px;
        text-align: center;
        cursor: pointer;
    }
    .cart-item{
      height: 120px;
      line-height: 120px;
    }
    .course-info img{
         175px;
        height: 115px;
        margin-right: 35px;
        vertical-align: middle;
    }
    .alipay{
      display: block;
      height: 48px;
    }
    .alipay img{
      height: 100%;
      auto;
    }
    
    .pay-text{
      display: block;
      text-align: right;
      height: 100%;
      line-height: 100%;
      vertical-align: middle;
      margin-top: 20px;
    }
    </style>
    View Code

    后端代码:

    payment.view:

     1 from datetime import datetime
     2 
     3 from alipay import AliPay
     4 from django.conf import settings
     5 from django.shortcuts import render
     6 
     7 # Create your views here.
     8 from rest_framework import status
     9 from rest_framework.permissions import IsAuthenticated
    10 from rest_framework.response import Response
    11 from rest_framework.views import APIView
    12 
    13 from luffy.apps.orders.models import Order
    14 
    15 
    16 class PaymentAPIView(APIView):
    17     permission_classes = [IsAuthenticated]
    18 
    19     def get(self, request, pk):
    20         """生成支付链接的地址"""
    21         try:
    22             order = Order.objects.get(pk=pk)
    23         except Order.DoesNotExist():
    24             return Response({"message": "当前订单不存在!"}, status=status.HTTP_400_BAD_REQUEST)
    25 
    26         alipay = AliPay(
    27             appid=settings.ALIPAY_APP_ID,
    28             app_notify_url=None,  # 默认回调url
    29             # 应用私钥
    30             app_private_key_path=settings.APP_PRIVATE_KEY_PATH,
    31             # 支付宝的公钥,
    32             alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH,
    33             sign_type="RSA2",  # 密码加密的算法
    34             # 开发时属于调试模式
    35             debug=settings.ALIPAY_DEBUG  # 默认False
    36         )
    37 
    38         # 生成参数
    39         order_string = alipay.api_alipay_trade_page_pay(
    40             out_trade_no = order.order_number,
    41             total_amount = float(order.total_price),  # 订单价格,单位:元 / RMB
    42             subject = order.order_desc,  # 订单标题
    43             return_url = settings.ALIPAY_RETURN_URL,
    44             notify_url=settings.ALIPAY_NOTIFY_URL  # 可选, 不填则使用默认notify url
    45         )
    46         # 生成新地址
    47         url = settings.APIPAY_GATEWAY + "?" + order_string
    48 
    49         return Response({"url": url}, status=status.HTTP_200_OK)
    50 
    51 
    52 
    53 class PayResultAPIView(APIView):
    54     def get(self,request):
    55         """处理get返回结果的数据"""
    56         # 接受数据
    57         data = request.query_params.dict()
    58         # sign 不能参与签名验证
    59         signature = data.pop("sign")
    60 
    61         alipay = AliPay(
    62             appid=settings.ALIPAY_APP_ID,
    63             app_notify_url=None,  # 默认回调url
    64             # 应用私钥
    65             app_private_key_path=settings.APP_PRIVATE_KEY_PATH,
    66             # 支付宝的公钥,
    67             alipay_public_key_path=settings.ALIPAY_PUBLIC_KEY_PATH,
    68             sign_type="RSA2",  # 密码加密的算法
    69             # 开发时属于调试模式
    70             debug = settings.ALIPAY_DEBUG  # 默认False
    71         )
    72 
    73         # verify验证支付结果,布尔值
    74         success = alipay.verify(data, signature)
    75 
    76         # 修改状态,如更改订单支付时间,支付状态等
    77         if success:
    78             # 支付成功
    79             order = Order.objects.get(order_number=data.get("out_trade_no"))
    80             order.order_status = 1  # 修改订单状态
    81             order.pay_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    82             order.save()
    83 
    84             return Response({
    85                 "length": order.order_course.count(),
    86                 "paytime": order.pay_time,
    87                 "price": order.total_price,
    88                 "info": order.order_desc,
    89             }, status=status.HTTP_200_OK)
    90 
    91         return Response({"message": "订单没有变化"})
    View Code

    相关settings的配置信息:

     1 # 支付宝
     2 ALIPAY_APP_ID="20160********971" # 应用ID
     3 APLIPAY_APP_NOTIFY_URL = None      # 应用回调地址[支付成功以后,支付宝返回结果到哪一个地址下面]
     4 APP_PRIVATE_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/app_private_key.pem")
     5 ALIPAY_PUBLIC_KEY_PATH = os.path.join(BASE_DIR,"luffy/apps/payments/keys/alipay_public_key.pem")
     6 ALIPAY_DEBUG = False
     7 # APIPAY_GATEWAY="https://openapi.alipay.com/gateway.do"  #上线使用
     8 APIPAY_GATEWAY="https://openapi.alipaydev.com/gateway.do" #开发使用
     9 ALIPAY_RETURN_URL = "http://127.0.0.1:8080/success"
    10 ALIPAY_NOTIFY_URL = "http://127.0.0.1:8080/success"
    View Code
  • 相关阅读:
    mysql has gone away
    [置顶] 在Visual Studio 2008上调试C语言程序
    滚动加载更多内容
    【jeecg-mybatis版本】 mybatis+spring mvc 完美整合方案 查询,保存,更新,删除自动生成
    Android Developers:按钮
    java 从零开始,学习笔记之基础入门<集合>(十六)
    Ubuntu MYSQL环境搭建
    Android 如何预置APK M
    php基础系列:从用户登录处理程序学习mysql扩展基本操作
    CMUSphinx Learn
  • 原文地址:https://www.cnblogs.com/Mixtea/p/10645481.html
Copyright © 2020-2023  润新知