Python實現支付寶二維碼支付
一.沙箱環境配置
1.登陸螞蟻金融開放平臺:https://openhome.alipay.com
2.選擇進入我的開放平臺。尋找開發中心的研發服務。
3.點擊沙箱環境—沙箱應用
4.這里博主已經配置好密鑰了,所以在RSA2(SHA256)密鑰(推薦)這邊跟沒有注冊的不太一樣。
? ?如果沒有配置過密鑰請繼續向下看,密鑰配置完畢跳到代碼處
5.下載RSA密鑰生成工具:https://docs.open.alipay.com/291/105971
? ?根據網頁中的使用步驟配置即可
6.因為是沙箱測試,需要下載沙箱支付寶以便測試,但是沙箱支付寶僅支持安卓系統,需要一部安卓手機。
二.Python代碼編寫
1.首先準備需要的庫:
如果是使用pycharm,博主在自己的github上已經配好了所需要的庫,
可以直接訪問博主的github:https://github.com/PythonStriker/Alipay_for_QR_code
省下很多步驟如果不是使用paycharm,需要自己配置所需要的庫:
?2.代碼如下所示:
pay.py
__author__ = 'PythonStriker'
from self_Alipay import *
import qrcode,time
APPID = 自己的appid號
private_key = '''-----BEGIN RSA PRIVATE KEY-----
自己的支付寶私鑰
-----END RSA PRIVATE KEY-----
'''
public_key = '''-----BEGIN PUBLIC KEY-----
自己的支付寶公鑰
-----END PUBLIC KEY-----
'''
class pay:
def __init__(self,out_trade_no,total_amount,subject,timeout_express):
self.out_trade_no = out_trade_no
self.total_amount = total_amount
self.subject = subject
self.timeout_express = timeout_express
def get_qr_code(self,code_url):
'''
生成二維碼
:return None
'''
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_H,
box_size=10,
border=1
)
qr.add_data(code_url) # 二維碼所含信息
img = qr.make_image() # 生成二維碼圖片
img.save(r'自己需要保存的路徑')
print('二維碼保存成功!')
def query_order(self,out_trade_no: int):
'''
:param out_trade_no: 商戶訂單號
:return: Nonem
'''
_time = 0
for i in range(600):
time.sleep(1)
result = alipay.init_alipay_cfg().api_alipay_trade_query(out_trade_no=out_trade_no)
if result.get("trade_status", "") == "TRADE_SUCCESS":
print('訂單已支付!')
print('訂單查詢返回值:', result)
return True
_time += 2
return False
if __name__ == '__main__':
alipay = alipay(APPID, private_key, public_key)
payer = pay(out_trade_no="訂單號",total_amount= 價格,subject = "商品名字",timeout_express='訂單超時取消時間 單位:s,m')
dict = alipay.trade_pre_create(out_trade_no=payer.out_trade_no,total_amount=payer.total_amount,subject =payer.subject,timeout_express=payer.timeout_express )
payer.get_qr_code(dict['qr_code'])
payer.query_order(payer.out_trade_no)
代碼有幾處需要注意的地方: 公鑰私鑰,appid,二維碼圖片保存地址,主函數中payer實例化訂單號,價格,商品名字,超市取消時間都是需要自己填寫的!
在主函數中,調用的方法已經寫出來了,可以在別的模塊中用相同的調用方法,博主會在之后的文章中演示,如何在別的模塊中,完成調用,并驗證是否付款成功。
self_Alipay.py
# -*- coding: UTF-8 -*-
import base64
import collections
import copy
import json
from datetime import datetime
from urllib import request, parse
import rsa
from alipay import AliPay
APP_ID = '需要填寫'
private_key = '''-----BEGIN RSA PRIVATE KEY-----
需要填寫
-----END RSA PRIVATE KEY-----
'''
public_key = '''-----BEGIN PUBLIC KEY-----
需要填寫
-----END PUBLIC KEY-----
'''
class alipay:
def __init__(self, app_id, private_key, public_key, notify_url=None, charset='gbk', sign_type='RSA2',
version='1.0', DEBUG=True):#需要注意,自己編碼類型是否是RSA2
self.requesturl = 'https://openapi.alipay.com/gateway.do' if DEBUG is False else "https://openapi.alipaydev.com/gateway.do"
self.private_key = private_key
self.public_key = public_key
self.params = dict(app_id=app_id, charset=charset, sign_type=sign_type, version=version,
biz_content={}, timestamp='', notify_url=notify_url)
def _sort(self, params):
#print(collections.OrderedDict(sorted(dict(params).items(), key=lambda x: x[0])))
return collections.OrderedDict(sorted(dict(params).items(), key=lambda x: x[0]))
@staticmethod
def make_goods_etail(goods_detail=None, alipay_goods_id=None, goods_name=None, quantity=None, price=None,
goods_category=None, body=None, show_url=None):
params = dict(goods_detail=goods_detail, alipay_goods_id=alipay_goods_id, goods_name=goods_name,
quantity=quantity, price=price, goods_category=goods_category, body=body, show_url=show_url)
return dict(filter(lambda x: x[1] is not None, params.items()))
def _make_sign(self, params, **kwargs):
private_key = rsa.PrivateKey.load_pkcs1(kwargs.get('private_key', None) or self.private_key)
sign = base64.b64encode(rsa.sign(params.encode(), private_key, "SHA-256")).decode('gbk')
return sign
def _check_sign(self, message, sign, **kwargs):
message = self._sort(message)
data = '{'
for key, value in message.items():
data += '"{}":"{}",'.format(key, value)
data = data[:-1] + '}'
sign = base64.b64decode(sign)
public_key = rsa.PublicKey.load_pkcs1_openssl_pem(kwargs.get('public_key', None) or self.public_key)
try:
rsa.verify(data.encode(), sign, public_key)
return True
except Exception:
return False
def _make_request(self, params, biz_content, **kwargs):
buf = ''
params['timestamp'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
params['biz_content'] = json.dumps(self._sort(biz_content))
for key, value in kwargs.items():
params[key] = value
params = self._sort(params)
for key in params:
buf += '{}={}&'.format(key, params[key])
params['sign'] = self._make_sign(buf[:-1], **kwargs)
#print(params)
# 發射http請求取回數據
data = request.urlopen(self.requesturl, data=parse.urlencode(params).encode('gbk')).read().decode('gbk')
#print(parse.urlencode(params).encode('gbk'))
return data
def parse_response(self, params, **kwargs):
sign = params['sign']
if self._check_sign(dict(filter(lambda x: 'sign' not in x[0], params.items())), sign, **kwargs):
return True
else:
return False
def trade_pre_create(self, out_trade_no, total_amount, subject, seller_id=None, discountable_amount=None,
undiscountable_amount=None, buyer_logon_id=None, body=None, goods_detail=None,
operator_id=None, store_id=None, terminal_id=None, timeout_express=None, alipay_store_id=None,
royalty_info=None, extend_params=None, **kwargs):
"""
:param out_trade_no: 商戶訂單號,64個字符以內、只能包含字母、數字、下劃線;需保證在商戶端不重復.
:param total_amount: 訂單總金額,單位為元,精確到小數點后兩位.
:param subject: 訂單標題.
:param seller_id: 賣家支付寶用戶ID。 如果該值為空,則默認為商戶簽約賬號對應的支付寶用戶ID.
:param discountable_amount:可打折金額. 參與優惠計算的金額,單位為元,精確到小數點后兩位,取值范圍[0.01,100000000]
:param undiscountable_amount:不可打折金額. 不參與優惠計算的金額,單位為元,精確到小數點后兩位,取值范圍[0.01,100000000]
:param buyer_logon_id: 買家支付寶賬號
:param body: 對交易或商品的描述
:param goods_detail: 訂單包含的商品列表信息.使用make_goods_etail生成. 其它說明詳見:“商品明細說明”
:param operator_id: 商戶操作員編號
:param store_id: 商戶門店編號
:param terminal_id: 商戶機具終端編號
:param timeout_express: 該筆訂單允許的最晚付款時間,逾期將關閉交易。取值范圍:1m~15d。m-分鐘,h-小時,d-天,1c-當天
:param alipay_store_id: 支付寶店鋪的門店ID
:param royalty_info: 描述分賬信息 暫時無效
:param extend_params: 業務擴展參數 暫時無效
:param kwargs: 公共參數可在此處暫時覆蓋
:return:
"""
params = copy.deepcopy(self.params)
params['method'] = 'alipay.trade.precreate'
total_amount = round(int(total_amount), 2)
if discountable_amount:
discountable_amount = round(int(discountable_amount), 2)
if undiscountable_amount:
undiscountable_amount = round(int(undiscountable_amount), 2)
if discountable_amount:
if undiscountable_amount is not None:
if discountable_amount + undiscountable_amount != total_amount:
return '傳入打折金額錯誤'
biz_content = dict(out_trade_no=out_trade_no[:64], total_amount=total_amount, seller_id=seller_id,
subject=subject,
discountable_amount=discountable_amount, undiscountable_amount=undiscountable_amount,
buyer_logon_id=buyer_logon_id, body=body, goods_detail=goods_detail, operator_id=operator_id,
store_id=store_id, terminal_id=terminal_id, timeout_express=timeout_express,
alipay_store_id=alipay_store_id, royalty_info=royalty_info, extend_params=extend_params)
#print(biz_content)
resp = self._make_request(params, dict(filter(lambda x: x[1] is not None, biz_content.items())), **kwargs)
#print(resp)
check = eval(resp)
resp = json.loads(resp)['alipay_trade_precreate_response']
if self._check_sign(check['alipay_trade_precreate_response'], check['sign']):
return resp
return False
def trade_refund(self, refund_amount, out_trade_no=None, trade_no=None,
refund_reason=None, out_request_no=None, operator_id=None, store_id=None,
terminal_id=None, **kwargs):
"""
:param refund_amount: 需要退款的金額,該金額不能大于訂單金額,單位為元,支持兩位小數
:param out_trade_no: 商戶訂單號,不可與支付寶交易號同時為空
:param trade_no: 支付寶交易號,和商戶訂單號不能同時為空
:param refund_reason: 退款的原因說明
:param out_request_no: 標識一次退款請求,同一筆交易多次退款需要保證唯一,如需部分退款,則此參數必傳。
:param operator_id: 商戶的操作員編號
:param store_id: 商戶的門店編號
:param terminal_id: 商戶的終端編號
:param kwargs: 公共參數可在此處臨時覆蓋
:return:
"""
params = copy.deepcopy(self.params)
params['method'] = 'alipay.trade.refund'
refund_amount = round(float(refund_amount), 2)
biz_content = dict(refund_amount=refund_amount, out_trade_no=out_trade_no, trade_no=trade_no,
refund_reason=refund_reason, out_request_no=out_request_no, operator_id=operator_id,
store_id=store_id, terminal_id=terminal_id)
resp = self._make_request(params, dict(filter(lambda x: x[1] is not None, biz_content.items())), **kwargs)
check = eval(resp)
resp = json.loads(resp)['alipay_trade_refund_response']
if self._check_sign(check['alipay_trade_refund_response'], check['sign']):
return int(resp['code']) == 10000
return False
def trade_query(self, out_trade_no, trade_no=None, **kwargs):
params = copy.deepcopy(self.params)
params['method'] = 'alipay.trade.query'
biz_content = dict(out_trade_no=out_trade_no, trade_no=trade_no)
resp = self._make_request(params, dict(filter(lambda x: x[1] is not None, biz_content.items())), **kwargs)
check = eval(resp)
resp = json.loads(resp)['alipay_trade_query_response']
if self._check_sign(check['alipay_trade_query_response'], check['sign']) and resp['code'] == 10000:
return resp
return False
def init_alipay_cfg(self):
alipay = AliPay(
appid=APP_ID,
app_notify_url=None, # 默認回調url
app_private_key_string=private_key,
alipay_public_key_string=public_key, # 支付寶的公鑰,驗證支付寶回傳消息使用,不是你自己的公鑰,
sign_type="RSA2", # RSA 或者 RSA2
debug=True # 默認False ,若開啟則使用沙盒環境的支付寶公鑰
)
return alipay
該代碼也有幾處需要注意的地方:公鑰密鑰,appid, 特別注意自己加密形式RSA 或者 RSA2之前博主吃過大虧。
#---------------------------------------------------------------------------------------------------------------------------#
特別注意事項:
1.這個支付寶端口支持的是非java端口,使用PKCS1加密方式密匙。
2.支付寶應用公鑰,和支付寶公鑰要分清楚。本代碼中需要填寫的是支付寶公鑰!
3.其實PKCS8的小伙伴也不用悲傷,支付寶自帶格式轉換,如下圖:
只需要將自己的PKCS8(JAVA適用)轉換PKCS1(非JAVA適用)的密鑰,本文代碼依舊可以使用。
#---------------------------------------------------------------------------------------------------------------------------#
可以用自己的沙箱支付寶測試,是否可以支付。
附加成功示例:
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
