Django對接支付寶Alipay支付介面

2022-05-30 15:02:00

最新部落格更新見我的個人主頁: https://xzajyjs.cn

我們在使用Django構建網站時常需要對接第三方支付平臺的支付介面,這裡就以支付寶為例(其他平臺大同小異),使用支付寶開放平臺的沙箱環境進行實驗。

我們這裡使用一個第三方的AliPay Python SDK(github)

下面看一下它的基本使用


呼叫流程

事實上需要我們網站伺服器端做的事並不多,只需要生成一個訂單向支付寶發出支付請求,等使用者支付完畢後向支付寶(通過同步和非同步的方式)查詢訂單、交易資訊即可。

在實際生產環境中,需要注意如下各種安全性問題:

  • 由於同步返回的不可靠性,支付結果必須以非同步通知或查詢介面返回為準,不能依賴同步跳轉。

  • 商戶系統接收到非同步通知以後,必須通過驗籤(驗證通知中的 sign 引數)來確保支付通知是由支付寶傳送的。

  • 接收到非同步通知並驗籤通過後,請務必核對通知中的 app_id、out_trade_no、total_amount 等引數值是否與請求中的一致,並根據 trade_status 進行後續業務處理。

  • 在支付寶端,partnerId 與 out_trade_no 唯一對應一筆單據,商戶端保證不同次支付 out_trade_no 不可重複;若重複,支付寶會關聯到原單據,基本資訊一致的情況下會以原單據為準進行支付。


具體實踐

1.準備工作

由於使用真實環境需要商戶支付寶賬號、上線應用需要審批等流程,我們這裡使用支付寶開放平臺的沙箱環境

沙箱環境中提供了後面需要的引數如APPIDAPP_PRIVATE_KEYALIPAY_PUBLIC_KEY支付寶閘道器等。

接下來安裝AliPay Python SDK

pip3 install python-alipay-sdk --upgrade

由於是沙箱環境,平臺已經提供給我們需要的公鑰和私鑰,如果是生產環境,則需要通過openssl生成

openssl
OpenSSL> genrsa -out app_private_key.pem   2048  # 私鑰
OpenSSL> rsa -in app_private_key.pem -pubout -out app_public_key.pem # 匯出公鑰
OpenSSL> exit

在支付寶上下載的公鑰是一個字串,你需要在文字的首尾新增標記位:

-----BEGIN PUBLIC KEY----- 和 -----END PUBLIC KEY-----

2.建立訂單

先在settings.py中設定一些關鍵引數

# 讀取公鑰和私鑰為字串
app_private_key_string = open("/path/to/your/private/key.pem").read()
alipay_public_key_string = open("/path/to/alipay/public/key.pem").read()
# 沙箱環境提供的APPID
ALIPAY_APP_ID = "2021000120607609"
# 同步回撥url(這裡需要一個公網ip)
RETURN_URL = "http://xxx.xxx.xxx.xxx/"
# 支付寶閘道器地址。注意:正式環境和沙箱環境的閘道器地址不同
GATEWAY = "https://openapi.alipaydev.com/gateway.do?"
from alipay import AliPay, AliPayConfig
from .settings import APP_PRIVATE_KEY, ALIPAY_PUBLIC_KEY, ALIPAY_APP_ID, RETURN_URL, GATEWAY

def create_alipay():
  	# 使用應用公鑰進行報文驗籤
    alipay = AliPay(
        appid=ALIPAY_APP_ID,
        app_notify_url=None,  # 預設非同步回撥 url
        app_private_key_string=APP_PRIVATE_KEY,
        alipay_public_key_string=ALIPAY_PUBLIC_KEY,
        sign_type="RSA2",
        debug=False,  # 預設 False
        verbose=False,  # 輸出偵錯資料
        config=AliPayConfig(timeout=15)  # 可選,請求超時時間
    )
    return alipay

下面可以建立支付訂單了(官方檔案)

# 向支付寶提交訂單資訊
def alipay_pay(subject, total_amount, out_trade_no, return_url_view):
    alipay = create_alipay()	# 先範例化alipay
    return_url = RETURN_URL + return_url_view	# 同步回撥url,用於支付完後跳轉回網站並對支付狀態進行即時檢驗。這裡的return_url_view是用於接收支付寶回撥的狀態檢驗的檢視函數
    order_string = alipay.api_alipay_trade_page_pay(
        out_trade_no=out_trade_no,	# 商戶訂單號,這個需要商戶自定義
        total_amount=total_amount,	# 支付總金額
        subject=subject,	# 訂單標題
        return_url=return_url,	# 同步回撥url,用於支付完後跳轉回網站並對支付狀態進行即時檢驗
        notify_url="https://example.com/notify"  # 可選,不填則使用預設 notify url
    )
    return order_string		# 返回訂單字串

呼叫

import string
import random
from .settings import GATEWAY

# 隨機生成32位元商戶交易號
out_trade_no = "".join(random.sample(string.ascii_letters+string.digits, 32))

# 在檢視函數對alipay_return進行繫結
# 同步回撥url為:  http://xxx.xxx.xxx.xxx/alipay_return
order_string = alipay_pay(subject="測試商品",total_amount=100,out_trade_no=out_trade_no,return_url_view='alipay_return')
	return HttpResponseRedirect(GATEWAY+order_string)

呼叫後會跳轉到支付寶平臺,使用沙箱環境提供的買家賬號即可完成支付

但是此時我們還不能回撥跳轉到我們自己的網站,也不能獲得訂單支付資訊,下面還有最後一步。

3.同步回撥

我們剛剛建立的訂單資訊中填寫了return_url,我們需要一個檢視函數來接收,並對其返回值進行分析

def alipay_return(request):
    processed_dict = {}
    # 回撥時alipay會把一些公用資訊通過GET方式傳參回來,這裡用字典去接收儲存
    for key, value in request.GET.items():
        processed_dict[key] = value
    """
    processed_dict = {
        'charset': 'utf-8', 
        'out_trade_no': 'xxxxxxx', 	# 這個是我們之前建立訂單時生成的商戶交易號
        'method': 'alipay.trade.page.pay.return', 
        'total_amount': '100.00', 	# 交易金額
        'trade_no': '20220xxxxxxxx24353', 	# 支付寶交易號
        'auth_app_id': '2021xxxxxx609', 		# 使用者appid
        'version': '1.0', 
        'app_id': '2021xxxxxx7609', 		# 沙箱提供的APPID 應用ID
        'sign_type': 'RSA2', 
        'seller_id': '2088xxxxx844', 		# 收款支付寶賬號對應的支付寶唯一使用者號。
以2088開頭的純16位元數位
        'timestamp': '2022-05-28 23:40:55'
    }
    """
    sign = processed_dict.pop("sign", None)

    new_alipay = create_alipay()
    verify_re = new_alipay.verify(processed_dict, sign)
    if verify_re is True:
      print("支付成功")
    else:
      print("支付失敗")

注意:同步回撥往往不可靠,因此需要增加一個非同步回撥檢驗

另外,在訂單建立後需要向資料庫儲存訂單資訊,包括訂單金額、商戶訂單號、appid等,等待回撥後與引數校驗一致無誤後再將訂單支付資訊進行更新。下面的完整範例不會包括該部分,請自行完成


完整範例

專案結構

# alipay.py
from alipay import AliPay, AliPayConfig
from .settings import APP_PRIVATE_KEY, ALIPAY_PUBLIC_KEY, ALIPAY_APP_ID, RETURN_URL


def create_alipay():
    alipay = AliPay(
        appid=ALIPAY_APP_ID,
        app_notify_url=None,  # 預設回撥 url
        app_private_key_string=APP_PRIVATE_KEY,
        # 支付寶的公鑰,驗證支付寶回傳訊息使用,不是你自己的公鑰,
        alipay_public_key_string=ALIPAY_PUBLIC_KEY,
        sign_type="RSA2",  # RSA 或者 RSA2
        debug=False,  # 預設 False
        verbose=False,  # 輸出偵錯資料
        config=AliPayConfig(timeout=15)  # 可選,請求超時時間
    )
    return alipay


def alipay_pay(subject, total_amount, out_trade_no, return_url_view):
    alipay = create_alipay()
    return_url = RETURN_URL + return_url_view
    order_string = alipay.api_alipay_trade_page_pay(
        out_trade_no=out_trade_no,
        total_amount=total_amount,
        subject=subject,
        return_url=return_url,
        notify_url="https://example.com/notify"  # 可選,不填則使用預設 notify url
    )
    return order_string
# settings.py
...
...

ALIPAY_APP_ID = "xxxxxx"
APP_PRIVATE_KEY = open(os.path.join(BASE_DIR, 'alipay/app_private_key.pem'), 'r').read()
ALIPAY_PUBLIC_KEY = open(os.path.join(BASE_DIR, 'alipay/alipay_public_key.pem'), 'r').read()
RETURN_URL = "http://xxxxxx/"
GATEWAY = "https://openapi.alipaydev.com/gateway.do?"
# urls.py
from django.contrib import admin
from django.urls import path
from . import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.index),
    path('alipay_return/', views.alipay_return)
]
# views.py
import random
import string
from django.http import HttpResponseRedirect
from django.shortcuts import render
from ali_django.alipay import alipay_pay, create_alipay
from django.conf import settings


def index(request):
    if request.method == "GET":
        return render(request, 'index.html')
    elif request.method == "POST":
        # 隨機生成32位元商戶交易號
        out_trade_no = "".join(random.sample(string.ascii_letters + string.digits, 32))

        order_string = alipay_pay(subject="測試商品", total_amount=100, out_trade_no=out_trade_no,return_url_view='alipay_return')
        return HttpResponseRedirect(settings.GATEWAY + order_string)


def alipay_return(request):
    processed_dict = {}
    # 回撥時alipay會把一些公用資訊通過GET方式傳參回來,這裡用字典去接收儲存
    for key, value in request.GET.items():
        processed_dict[key] = value
    sign = processed_dict.pop("sign", None)

    new_alipay = create_alipay()
    verify_re = new_alipay.verify(processed_dict, sign)
    if verify_re is True:
        print("支付成功")
    else:
        print("支付失敗")
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>支付寶支付介面測試</title>
</head>
<body>
<form action="" method="post">
    <input type="submit" value="提交">
</form>
</body>
</html>