1、設置沙箱帳戶css
一、經過支付寶登錄到螞蟻金服開放平臺(https://openhome.alipay.com/platform/appDaily.htm?tab=info)html
註釋:只要登錄上後沙箱應用上面就有本身的APPID和支付寶網關,支付寶網關就是支付寶給咱們的api接口,而RSA2(SHA256)密鑰是須要本身設置德。python
二、設置密鑰django
下載支付寶自動生成密鑰軟件,將本身的應用私鑰保存在本地,應用公鑰發送給支付寶點擊確認後就會獲得支付寶發送給咱們的支付寶公鑰json
三、下載支付寶錢包bootstrap
經過手機掃碼下載支付寶沙箱錢包,沙箱錢包的用戶名和密碼在在沙箱帳戶中查找api
2、設置Alipay服務器
一、因爲支付寶不提供python版本的sdk,因此須要手動編寫python版本sdk。app
二、編寫python版本sdk框架
一、項目目錄結構
二、settings配置(主要寫一些必須的配置項)
import os BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) class AliPayConfig(object): # 商戶app_id,即沙箱的APPID app_id = "2016081500252288" # 商戶私鑰路徑 merchant_private_key_path = os.path.join(BASE_DIR, "keys", "app_private_2048.txt") # 支付寶公鑰路徑 alipay_public_key_path = os.path.join(BASE_DIR, "keys", "alipay_public_2048.txt") # 服務器異步通知頁面路徑 需http: // 格式的完整路徑,不能加?id = 123 這類自定義參數,必須外網能夠正常訪問 # 向支付寶支付成功後,支付寶會執行回調函數來通知服務器的支付狀態,即支付寶發送post請求是支付寶支付成功後調用服務器的驗證函數,服務器來判斷是不是真的支付成功 notify_url = "http://47.94.172.250:8804/api/v1/trade/alipay/" # 頁面跳轉同步通知頁面路徑 需http: // 格式的完整路徑,不能加?id = 123 這類自定義參數,必須外網能夠正常訪問 # 向支付寶支付成功後,支付寶會執行回調函數來通知服務器的支付狀態,即支付寶發送get請求是支付寶支付成功後調用服務器的支付成功的頁面信息 return_url = "http://47.94.172.250:8804/api/v1/trade/alipay/" # 簽名方式(當前只支持RSA和RSA2) sign_type = "RSA2" # 字符編碼格式 charset = "utf-8" # 支付寶網關(若是是線上環境的話, dev 這三個字去掉便可) gateway_url = "https://openapi.alipaydev.com/gateway.do" # 異步通知參數DOC(支付寶會主動發起POST請求) notify_doc = "https://docs.open.alipay.com/270/105902/"
三、支付寶公鑰和應用私鑰配置
註釋:一、主要是存放在keys文件中,alipay_public_2048.txt中保存的是支付寶公鑰,app_private_2048.txt中保存的是應用私鑰。
二、不管是支付寶公鑰仍是應用私鑰在文件中的寫法必須遵循以下格式:
-----BEGIN RSA PRIVATE KEY-----
中間寫支付寶公鑰或是應用私鑰
-----END RSA PRIVATE KEY-----
四、alipay文件配置
註釋:配置以前必須先安裝Crypto和pycryptodome倆模塊
from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from base64 import decodebytes, encodebytes from config.settings import AliPayConfig import json # 文檔連接(支付成功回調通知參數等相關, 支付寶交易狀態是 TRADE_SUCCESS 會主動發起異步通知) # https://docs.open.alipay.com/270/105902/ # 沙箱測試環境可以使用沙箱支付寶支付 # 帳號: rjoegs7144@sandbox.com # 密碼: 111111 # 支付密碼: 111111 class AliPay(object): """ 支付寶支付接口(PC端支付接口,已經寫好的支付接口,什麼都不用修改直接調用便可) """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.importKey(fp.read()) if debug is True: #alipaydev是測試環境,alipay是生產環境 self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): data.pop("sign", None) # 排序後的字符串 unsigned_items = self.ordered_data(data) unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) sign = self.sign(unsigned_string.encode("utf-8")) # ordered_items = self.ordered_data(data) quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 得到最終的訂單信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def ordered_data(self, data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 將字典類型的數據dump出來 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 開始計算簽名 key = self.app_private_key signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new(unsigned_string)) # base64 編碼,轉換爲unicode表示並移除回車 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 開始計算簽名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序後的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature) #上面的類就是支付寶的api接口,如下只須要調實例化產生api接口類對象後 if __name__ == "__main__": #支付寶的api接口類實例化 alipay = AliPay( appid=AliPayConfig.app_id, #APPDI app_notify_url=AliPayConfig.notify_url, #支付成功後的post請求 return_url=AliPayConfig.return_url, #支付成功後的get請求 app_private_key_path=AliPayConfig.merchant_private_key_path, #應用私鑰 alipay_public_key_path=AliPayConfig.alipay_public_key_path, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你本身的公鑰 debug=True, # 默認False,是不是測試環境仍是生產環境 ) #調用實例化對象下的direct_pay方法,生成支付的url import time query_params = alipay.direct_pay( subject="luffycity", # 商品簡單描述,可自定義哦 out_trade_no="%s"time.time(), # 商戶訂單號,可自定義可是商品訂單號必須是惟一的,不然發送給支付寶不會被處理 total_amount=100.00, # 交易金額(單位: 元 保留倆位小數) ) pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params)#這就是生成的支付寶api接口 print(pay_url) # 這一部分是驗證url的簽名,即支付成功後支付寶經過post請求返回給服務器的驗證數據 return_url = 'http://47.92.87.172:8000/?total_amount=100.00×tamp=2017-08-15+23%3A53%3A34&sign=e9E9UE0AxR84NK8TP1CicX6aZL8VQj68ylugWGHnM79zA7BKTIuxxkf%2Fvhd DYz4XOLzNf9pTJxTDt8tTAAx%2FfUAJln4WAeZbacf1Gp4IzodcqU%2FsIc4z93xlfIZ7OLBoWW0kpKQ8AdOxrWBMXZck%2F1cffy4Ya2dWOYM6Pcdpd94CLNRPlH6kFsMCJCbhqvyJTflxdp VQ9kpH%2B%2Fhpqrqvm678vLwM%2B29LgqsLq0lojFWLe5ZGS1iFBdKiQI6wZiisBff%2BdAKT9Wcao3XeBUGigzUmVyEoVIcWJBH0Q8KTwz6IRC0S74FtfDWTafplUHlL%2Fnf6j%2FQd1y6 Wcr2A5Kl6BQ%3D%3D&trade_no=2017081521001004340200204115&sign_type=RSA2&auth_app_id=2016080600180695&charset=utf-8&seller_id=2088102170208070&meth od=alipay.trade.page.pay.return&app_id=2016080600180695&out_trade_no=20170202185&version=1.0' #這一部分就是支付寶返回給服務器的數據 o = urlparse(return_url) #路徑解析,就至關因而取request.body中的數據 query = parse_qs(o.query) #將數據轉換成字典 processed_query = {} ali_sign = query.pop("sign")[0] for key, value in query.items(): processed_query[key] = value[0] # 驗證簽名校驗的結果,就是支付寶發的post請求驗證 print(alipay.verify(processed_query, ali_sign)) #調用alipay對象下的verify認證方法,若是返回True就證實支付成功,不然失敗
三、根據步驟2中的講解,本身編寫支付寶支付框架
一、如圖所示:
二、pay.py文件(該文件內就只寫了個類,該類就是支付寶api的接口類)
from datetime import datetime from Crypto.PublicKey import RSA from Crypto.Signature import PKCS1_v1_5 from Crypto.Hash import SHA256 from urllib.parse import quote_plus from urllib.parse import urlparse, parse_qs from base64 import decodebytes, encodebytes import json class AliPay(object): """ 支付寶支付接口(PC端支付接口) """ def __init__(self, appid, app_notify_url, app_private_key_path, alipay_public_key_path, return_url, debug=False): self.appid = appid self.app_notify_url = app_notify_url self.app_private_key_path = app_private_key_path self.app_private_key = None self.return_url = return_url with open(self.app_private_key_path) as fp: self.app_private_key = RSA.importKey(fp.read()) self.alipay_public_key_path = alipay_public_key_path with open(self.alipay_public_key_path) as fp: self.alipay_public_key = RSA.importKey(fp.read()) if debug is True: self.__gateway = "https://openapi.alipaydev.com/gateway.do" else: self.__gateway = "https://openapi.alipay.com/gateway.do" def direct_pay(self, subject, out_trade_no, total_amount, return_url=None, **kwargs): biz_content = { "subject": subject, "out_trade_no": out_trade_no, "total_amount": total_amount, "product_code": "FAST_INSTANT_TRADE_PAY", # "qr_pay_mode":4 } biz_content.update(kwargs) data = self.build_body("alipay.trade.page.pay", biz_content, self.return_url) return self.sign_data(data) def build_body(self, method, biz_content, return_url=None): data = { "app_id": self.appid, "method": method, "charset": "utf-8", "sign_type": "RSA2", "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "version": "1.0", "biz_content": biz_content } if return_url is not None: data["notify_url"] = self.app_notify_url data["return_url"] = self.return_url return data def sign_data(self, data): data.pop("sign", None) # 排序後的字符串 unsigned_items = self.ordered_data(data) unsigned_string = "&".join("{0}={1}".format(k, v) for k, v in unsigned_items) sign = self.sign(unsigned_string.encode("utf-8")) # ordered_items = self.ordered_data(data) quoted_string = "&".join("{0}={1}".format(k, quote_plus(v)) for k, v in unsigned_items) # 得到最終的訂單信息字符串 signed_string = quoted_string + "&sign=" + quote_plus(sign) return signed_string def ordered_data(self, data): complex_keys = [] for key, value in data.items(): if isinstance(value, dict): complex_keys.append(key) # 將字典類型的數據dump出來 for key in complex_keys: data[key] = json.dumps(data[key], separators=(',', ':')) return sorted([(k, v) for k, v in data.items()]) def sign(self, unsigned_string): # 開始計算簽名 key = self.app_private_key signer = PKCS1_v1_5.new(key) signature = signer.sign(SHA256.new(unsigned_string)) # base64 編碼,轉換爲unicode表示並移除回車 sign = encodebytes(signature).decode("utf8").replace("\n", "") return sign def _verify(self, raw_content, signature): # 開始計算簽名 key = self.alipay_public_key signer = PKCS1_v1_5.new(key) digest = SHA256.new() digest.update(raw_content.encode("utf8")) if signer.verify(digest, decodebytes(signature.encode("utf8"))): return True return False def verify(self, data, signature): if "sign_type" in data: sign_type = data.pop("sign_type") # 排序後的字符串 unsigned_items = self.ordered_data(data) message = "&".join(u"{}={}".format(k, v) for k, v in unsigned_items) return self._verify(message, signature)
三、page1.html文件
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="dist/css/bootstrap.css"> </head> <body> <form method="POST"> {% csrf_token %} <input type="text" name="money"> <input type="submit" value="去支付" /> </form> <script></script> </body> </html>
四、url.py文件
from django.conf.urls import url from django.contrib import admin from app01 import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^page1/', views.page1), url(r'^page2/', views.page2), ]
五、key文件夾
一、pri文件存放的是應用私鑰
二、pub文件存放的是支付寶公鑰
六、views.py文件
from django.shortcuts import render, redirect, HttpResponse from utils.pay import AliPay import json import time #將實例化的api接口和參數存放子啊函數中 def ali(): #APPID app_id = "2016082500309412" # POST請求,支付寶回調函數,用於支付成功後的驗證 notify_url = "http://www.dabo.com:8009/page2/" # GET請求,支付寶回調函數,用於支付成功後返回給用戶的頁面信息 return_url = "http://www.dabo.com:8009/page2/" merchant_private_key_path = "keys/pri" #應用私鑰 alipay_public_key_path = "keys/pub" #支付寶公鑰 #實例化api接口類 alipay = AliPay( appid=app_id, app_notify_url=notify_url, return_url=return_url, app_private_key_path=merchant_private_key_path, alipay_public_key_path=alipay_public_key_path, debug=True, # 默認False,若是是True則爲測試環境,若是爲Flase則爲生成環境 ) return alipay def page1(request): if request.method == "GET": return render(request, 'page1.html') else: money = float(request.POST.get('money')) alipay = ali() # 生成支付的url query_params = alipay.direct_pay( #調用支付寶對象下的direct_pay方法 subject="充氣式韓紅", # 商品簡單描述 out_trade_no="x2" + str(time.time()), # 商戶訂單號,有切必須惟一 total_amount=money, # 交易金額(單位: 元 保留倆位小數) ) #支付寶api完整路徑 pay_url = "https://openapi.alipaydev.com/gateway.do?{}".format(query_params) return redirect(pay_url) def page2(request): alipay = ali() if request.method == "POST": # 檢測是否支付成功 # 去請求體中獲取全部返回的數據:狀態/訂單號 from urllib.parse import parse_qs """ # request.body => 字節類型 # request.body.decode('utf-8') => 字符串類型 {"k1":["v1"],"k2":["v1"]} k1=[v1]&k2=[v2] """ body_str = request.body.decode('utf-8') post_data = parse_qs(body_str)#parse_qs是將字符串直接json成字典的形式 # {k1:[v1,],k2:[v2,]},這是支付寶返回給咱們的json數據,可是不是咱們想要的數據, post_dict = {} for k, v in post_data.items():#因此經過這種方式能夠轉換成咱們想要的{k1:v1}格式的數據 post_dict[k] = v[0] print(post_dict) """ {'gmt_create': '2017-11-24 14:53:41', 'charset': 'utf-8', 'gmt_payment': '2017-11-24 14:53:48', 'notify_time': '2017-11-24 14:57:05', 'subject': '充氣式韓紅', 'sign': 'YwkPI9BObXZyhq4LM8//MixPdsVDcZu4BGPjB0qnq2zQj0SutGVU0guneuONfBoTsj4XUMRlQsPTHvETerjvrudGdsFoA9ZxIp/FsZDNgqn9i20IPaNTXOtQGhy5QUetMO11Lo10lnK15VYhraHkQTohho2R4q2U6xR/N4SB1OovKlUQ5arbiknUxR+3cXmRi090db9aWSq4+wLuqhpVOhnDTY83yKD9Ky8KDC9dQDgh4p0Ut6c+PpD2sbabooJBrDnOHqmE02TIRiipULVrRcAAtB72NBgVBebd4VTtxSZTxGvlnS/VCRbpN8lSr5p1Ou72I2nFhfrCuqmGRILwqw==', 'buyer_id': '2088102174924590', 'invoice_amount': '666.00', 'version': '1.0', 'notify_id': '11aab5323df78d1b3dba3e5aaf7636dkjy', 'fund_bill_list': '[{"amount":"666.00","fundChannel":"ALIPAYACCOUNT"}]', 'notify_type': 'trade_status_sync', 'out_trade_no': 'x21511506412.4733646', 'total_amount': '666.00', 'trade_status': 'TRADE_SUCCESS', 'trade_no': '2017112421001004590200343962', 'auth_app_id': '2016082500309412', 'receipt_amount': '666.00', 'point_amount': '0.00', 'app_id': '2016082500309412', 'buyer_pay_amount': '666.00', 'sign_type': 'RSA2', 'seller_id': '2088102172939262'} {'stade_status': "trade_success",'order':'x2123123123123'} """ sign = post_dict.pop('sign', None) status = alipay.verify(post_dict, sign)#把數據和簽名放在一塊兒進行進行校驗,驗證是否支付成功 if status: #print(post_dict['stade_status'])#支付寶系統的交易狀態,stade_status這個寫的有點問題,單詞好像寫錯了 print(post_dict['out_trade_no'])#商品網站惟一訂單號 return HttpResponse('POST返回') else: # QueryDict = {'k':[1],'k1':[11,22,3]} params = request.GET.dict() sign = params.pop('sign', None) status = alipay.verify(params, sign) print('GET驗證', status) return HttpResponse('支付成功')