Luffy之支付寶支付開發API

發起支付

接入支付寶

支付的大體流程以下圖:前端

                                                    

 

部分節點詳解:vue

 

沙箱環境

  • 是支付寶提供給開發者的模擬支付的環境python

  • 沙箱環境跟真實環境是分開的,項目上線時必須切換對應的配置服務器地址和開發者ID和密鑰。ios

  • 沙箱應用https://docs.open.alipay.comgit

  • 沙箱帳號https://openhome.alipay.com/platform/appDaily.htm?tab=accountgithub

     真實的支付寶網關:   https://openapi.alipay.com/gateway.do
        
    沙箱的支付寶網關:   https://openapi.alipaydev.com/gateway.do

 

支付寶開發者文檔

 

開發支付功能

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

 

配置祕鑰

1. 生成應用的私鑰和公鑰

下載對應系統的祕鑰生成工具: https://doc.open.alipay.com/docs/doc.htm?treeId=291&articleId=105971&docType=1json

應用公鑰複製粘貼到支付寶網站頁面中.axios

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;
    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 .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{
    width: 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{
    width: 16px;
    height: 16px;
    margin-right: 10px;
}
.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-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;
  width: 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;
    width: 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{
    width: 175px;
    height: 115px;
    margin-right: 35px;
    vertical-align: middle;
}
.alipay{
  display: block;
  height: 48px;
}
.alipay img{
  height: 100%;
  width: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
相關文章
相關標籤/搜索