python调用企业支付宝转账到个人账户的代码-2023年1月18日亲测能用

发布时间:2024年01月18日

本文代码是另一位老哥写的,
我在github上找到并修改部分后成功使用,感谢前者,
记录下来以在用,也分享给大家
代码内的注释是之前老哥的

中文部分由CHATGPT解释

总共三个文件

1、RSAUtil.py

# -*- coding: utf-8 -*-
# @Time    : 2021-01-24 14:08
# @Author  : makun 15902051493
# @FileName: RSAUtil.py
# @Describe: 创建RSA签名
# pip install rsa -i https://pypi.douban.com/simple


__pem_begin = '-----BEGIN RSA PRIVATE KEY-----\n'
__pem_end = '\n-----END RSA PRIVATE KEY-----'


def RSASign(content, private_key, sign_type):
    if sign_type.upper() == 'RSA':
        return rsa_sign(content, private_key, 'SHA-1')
    elif sign_type.upper() == 'RSA2':
        return rsa_sign(content, private_key, 'SHA-256')
    else:
        raise Exception('sign_type错误')




from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding


def load_private_key(pem_private_key):
    private_key = serialization.load_pem_private_key(
        pem_private_key.encode(),
        password=None,
        backend=default_backend()
    )
    return private_key


def rsa_sign(content, pem_private_key, hash_algorithm):
    private_key = load_private_key(pem_private_key)
    if hash_algorithm == 'SHA-256':
        algorithm = hashes.SHA256()
    elif hash_algorithm == 'SHA-1':
        algorithm = hashes.SHA1()
    else:
        raise ValueError("Unsupported hash algorithm")

    signature = private_key.sign(
        content.encode(),
        padding.PKCS1v15(),
        algorithm
    )
    return signature


def _format_private_key(private_key):
    if not private_key.startswith(__pem_begin):
        private_key = __pem_begin + private_key
    if not private_key.endswith(__pem_end):
        private_key = private_key + __pem_end
    return private_key

首先,定义了两个常量 __pem_begin__pem_end,它们包含了RSA私钥的开始和结束标志。

接下来,有以下几个函数:

  1. RSASign(content, private_key, sign_type): 这是一个用于进行RSA签名的主要函数。它接收三个参数:content 是要签名的内容,private_key 是RSA私钥(以PEM格式表示),sign_type 是签名类型,可以是 ‘RSA’ 或 ‘RSA2’。根据 sign_type 的不同,它会调用 rsa_sign 函数来进行具体的签名操作。

  2. load_private_key(pem_private_key): 这个函数用于加载PEM格式的RSA私钥,并返回一个私钥对象。它接收一个参数 pem_private_key,这是PEM格式的私钥字符串。函数内部使用 serialization.load_pem_private_key 方法来加载私钥。

  3. rsa_sign(content, pem_private_key, hash_algorithm): 这个函数用于对内容进行RSA数字签名。它接收三个参数:content 是要签名的内容,pem_private_key 是PEM格式的RSA私钥字符串,hash_algorithm 是哈希算法,可以是 ‘SHA-1’ 或 ‘SHA-256’。函数内部首先调用 load_private_key 函数加载私钥,然后根据指定的哈希算法创建一个哈希对象,最后使用私钥对内容进行签名,返回签名结果。

  4. _format_private_key(private_key): 这个函数用于格式化RSA私钥,确保它以 __pem_begin 开始且以 __pem_end 结束。如果私钥字符串没有以这些标志开始或结束,它会在私钥的前面或后面添加相应的标志。

2、GetCertSN.py

# -*- coding: utf-8 -*-
# @Time    : 2021-01-24 13:58
# @Author  : makun 15902051493
# @FileName: GetCertSN.py
# @Describe: 获取证书序列号
import OpenSSL
import hashlib
import re


def md5(string):
    print('')
    return hashlib.md5(string.encode('utf-8')).hexdigest()


# 应用公钥证书序列号
def get_app_cert_cn(cert_str=None, cert_file=None):
    cert_str = cert_str or open(cert_file).read()
    cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_str)
    try:
        res = cert.get_signature_algorithm()
        if not re.match(b'sha.+WithRSAEncryption', res):
            return None
    except:
        return None
    cert_issue = cert.get_issuer()
    op = ''
    b = list(cert_issue.get_components())
    for i in range(len(b)):
        a = list(b[len(b) - 1 - i])
        opp = "{}={}".format(a[0].decode(), a[1].decode())
        op = op + opp + ','
    return md5(op[:-1] + str(cert.get_serial_number()))


# 根证书序列号
def get_root_cn_sn(cert_file):
    root_cert = open(cert_file).read()
    cert_list = root_cert.split('-----BEGIN CERTIFICATE-----')
    root_cert_sn = ''
    for i in cert_list:
        if not len(i):
            continue
        cert_sn = get_app_cert_cn('-----BEGIN CERTIFICATE-----' + i)
        if cert_sn is not None:
            root_cert_sn = root_cert_sn + cert_sn + '_'
    return root_cert_sn[:-1]

  1. md5(string): 这是一个简单的MD5哈希函数,接收一个字符串 string 作为输入,对其进行MD5哈希,并返回哈希结果的十六进制表示。

  2. get_app_cert_cn(cert_str=None, cert_file=None): 这个函数用于获取应用公钥证书的序列号。它可以接收两个参数,cert_str 是证书的字符串表示,cert_file 是证书文件的路径。首先,它会加载证书并进行一些验证操作,检查证书的签名算法是否以 “sha” 开头,并以 “WithRSAEncryption” 结尾。如果验证失败,返回 None。然后,它获取证书的颁发者信息,将其格式化为字符串,计算这个字符串的MD5哈希值,然后将MD5哈希值与证书的序列号组合在一起,返回最终的序列号信息。

  3. get_root_cn_sn(cert_file): 这个函数用于获取根证书的序列号。它接收一个参数 cert_file,是根证书文件的路径。首先,它读取根证书文件,然后将文件内容按 “-----BEGIN CERTIFICATE-----” 分割成多个证书块。接着,它通过调用 get_app_cert_cn 函数获取每个证书块的序列号,然后将这些序列号以下划线连接在一起,返回根证书的序列号字符串。

3、HttpZfbPay.py

# -*- coding: utf-8 -*-
# @Time    : 2021-01-24 13:35
# @Author  : makun 15902051493
# @FileName: HttpZfbPay.py
# @Describe: 使用http协议直接进行支付宝的支付操作
# https://opendocs.alipay.com/apis/api_28/alipay.fund.trans.uni.transfer
import base64

import requests, uuid, hashlib
import json
from datetime import datetime

from urllib.parse import urlencode

import RSAUtil, GetCertSN
from models import XAliPay


# '''md5 hash'''
def md5Hash(tmp):
    try:
        tmp = tmp.encode("utf-8")
        return hashlib.md5(tmp).hexdigest()
    except:
        return hashlib.md5(tmp).hexdigest()


def connectQuerys(querys):
    keyList = []
    for each in querys:
        keyList.append(each)
    keyList = sorted(keyList)
    tmpList = []
    for i in range(0, len(keyList)):
        tmpList.append("{}={}".format(keyList[i], querys.get(keyList[i])))
    result = "&".join(tmpList)
    return result


def getSign(querys):
    tmpStr = connectQuerys(querys)
    alipay_info = XAliPay.query.filter_by(id=1).first()
    # TODO   请补全自己创建应用时创建的私钥
    pri = '此处填写您的私钥application_private_key'
    result = RSAUtil.RSASign(content=tmpStr, private_key=pri, sign_type="RSA2")

    return result


def zfbRequest(zfb_name, identity, trans_amount):
    import os

    # 获取当前脚本的绝对路径
    base_dir = os.path.dirname(os.path.abspath(__file__))

    # 构建证书文件的绝对路径
    root_cert_path = os.path.join(base_dir, 'alipayRootCert.crt')
    app_cert_path = os.path.join(base_dir, 'appCertPublicKey_2016060801495267.crt')

    alipay_info = XAliPay.query.filter_by(id=1).first()
    appId = alipay_info.appid
    url = "https://openapi.alipay.com/gateway.do"

    # TODO 请补全自己的密钥地址
    rootCertSN = GetCertSN.get_root_cn_sn(root_cert_path)
    appCertSN = GetCertSN.get_app_cert_cn(
        cert_file=app_cert_path)

    bizContent = {
        "out_biz_no": md5Hash("{}".format(uuid.uuid4()).encode("UTF-8")),
        "trans_amount": trans_amount,  # 这里是金额
        "product_code": "TRANS_ACCOUNT_NO_PWD",
        "biz_scene": "DIRECT_TRANSFER",
        "payee_info": {
            # "identity": "18140208525",
            "identity": identity,
            "identity_type": "ALIPAY_LOGON_ID",
            # "name": "陈**",
            "name": zfb_name

        }
    }

    querys = {
        'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
        'method': 'alipay.fund.trans.uni.transfer',
        'app_id': appId,
        'sign_type': 'RSA2',
        'version': '1.0',
        'charset': 'utf-8',
        'biz_content': json.dumps(bizContent, separators=(',', ':'), ensure_ascii=False),
        'alipay_root_cert_sn': rootCertSN,
        'app_cert_sn': appCertSN
    }

    sign = getSign(querys)
    sign_base64 = base64.b64encode(sign).decode('utf-8')
    querys['sign'] = sign_base64

    url = "{}?{}".format(url, urlencode(querys))

    req = requests.post(url=url)
    content = req.json()
    req.close()
    print(content['alipay_fund_trans_uni_transfer_response'])
    return content['alipay_fund_trans_uni_transfer_response']

  1. 导入模块:代码中导入了一些需要使用的Python模块,包括 base64requestsuuidhashlibjsondatetimeurlencode,以及自定义的模块 RSAUtilGetCertSN

  2. md5Hash(tmp): 这是一个用于计算MD5哈希的函数,接收一个字符串 tmp 作为输入,将其编码为UTF-8,然后计算其MD5哈希值并返回。

  3. connectQuerys(querys): 这个函数接收一个字典 querys,将字典中的键按字母顺序排序,然后将键值对按照格式连接成字符串并返回。

  4. getSign(querys): 这个函数用于生成签名。首先,它调用 connectQuerys 函数将传入的 querys 字典连接成字符串。然后,它获取私钥并使用 RSAUtil.RSASign 函数对连接后的字符串进行RSA签名,签名算法根据 sign_type 参数来确定(支持RSA和RSA2两种算法)。最后,返回生成的签名。

  5. zfbRequest(zfb_name, identity, trans_amount): 这个函数是主要的请求支付宝转账的部分。它接收三个参数:zfb_name(支付宝账户名称)、identity(身份标识)、trans_amount(转账金额)。在函数内部,首先获取一些配置信息,包括应用ID、证书信息等。然后,构建了一个包含转账信息的 bizContent 字典。接着,构建了一个包含请求参数的 querys 字典,其中包括时间戳、方法、应用ID、签名类型、版本、字符集、业务内容、根证书序列号、应用证书序列号等信息。然后,调用 getSign 函数生成签名,并将签名添加到 querys 中。最后,将请求参数拼接成URL,发送POST请求到支付宝接口,并返回响应结果。

使用方法

1、 修改HttpZfbPay.py 44行的 pri参数
2、修改为自己的证书
root_cert_path = os.path.join(base_dir, ‘alipayRootCert.crt’)
app_cert_path = os.path.join(base_dir, ‘appCertPublicKey_2016060801495267.crt’)

3、调用zfbRequest(zfb_name, identity, trans_amount)
传入真实姓名、支付宝账号、打款金额

zfbRequest方法返回值可能如下,各位可以参考来写后续逻辑:

{'msg': 'Business Failed', 'code': '40004', 'sub_msg': '请求金额不能低于0.1元', 'sub_code': 'EXCEED_LIMIT_SM_MIN_AMOUNT'}


{'msg': 'Business Failed', 'code': '40004', 'sub_msg': '余额不足,建议尽快充值。后续可登录电脑端支付宝,自主设置余额预警提醒功能。', 'sub_code': 'BALANCE_IS_NOT_ENOUGH'}


{'msg': 'Business Failed', 'code': '40004', 'sub_msg': '收款账号不存在或姓名有误,建议核实账号和姓名是否准确', 'sub_code': 'PAYEE_NOT_EXIST'}


{'code': '10000', 'msg': 'Success', 'order_id': '20240117020070011500500094030081', 'out_biz_no': '1d90d3bc2af0665dce63a72d90da769a', 'pay_fund_order_id': '20240117020070011500500094030081', 'status': 'SUCCESS', 'trans_date': '2024-01-17 16:02:29'}
 

code=='10000’就是转账成功

文章来源:https://blog.csdn.net/weixin_40541330/article/details/135663520
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。