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适用)的密钥,本文代码依旧可以使用。
#---------------------------------------------------------------------------------------------------------------------------#
可以用自己的沙箱支付宝测试,是否可以支付。
附加成功示例: