最新博客更新見我的個人主頁: https://xzajyjs.cn 我們在使用Django構建網站時常需要對接第三方支付平臺的支付介面,這裡就以支付寶為例(其他平臺大同小異),使用支付寶開放平臺的沙箱環境進行實驗。 我們這裡使用一個第三方的AliPay Python SDK(github) 下麵看一 ...
最新博客更新見我的個人主頁: 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.準備工作
由於使用真實環境需要商戶支付寶賬號、上線應用需要審批等流程,我們這裡使用支付寶開放平臺的沙箱環境
沙箱環境中提供了後面需要的參數如APPID
、APP_PRIVATE_KEY
、ALIPAY_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>