Python实现支付宝二维码支付(沙箱环境)

Python实现支付宝二维码支付

一.沙箱环境配置

1.登录蚂蚁金融开放平台:https://openhome.alipay.comjava

2.选择进入个人开放平台。寻找开发中心的研发服务。git

3.点击沙箱环境—沙箱应用github

4.这里博主已经配置好密钥了,因此在RSA2(SHA256)密钥(推荐)这边跟没有注册的不太同样。express

   若是没有配置过密钥请继续向下看,密钥配置完毕跳到代码处json

5.下载RSA密钥生成工具:https://docs.open.alipay.com/291/105971api

   根据网页中的使用步骤配置便可app

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适用)的密钥,本文代码依旧可使用。

#---------------------------------------------------------------------------------------------------------------------------#

能够用本身的沙箱支付宝测试,是否能够支付。

附加成功示例: