工行有java、python、C#的SDK,项目是用python写的,遇到的坑先列举起来
1.工行给的私钥是PKCS8格式,这个是java专用的,其他语言需要转为PKCS1格式,推荐网上是链接: 在线PKCS8转PKCS1
2.工行的签名是需要加上接口的URL,不仅仅是对参数进行加密
3.参数名与参数值都要做urlEncode,sign签名也是需要的
签名生成
签名原文构造
①筛选获取所有请求参数,不包括字节型参数,如文件、字节流,剔除sign字段。
②排序
将筛选的参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。
③拼接
将排序后的参数与其对.值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接来,此时生成的字符串为待签名字符串。
④调用签名函数
现将拼接后的参数,按照编码类型处理为byte数组,使用各自语言对应的RSA签名函数利用商户私钥对待签名字符串进行签名,并进行Base64编码。
# -*- coding: utf-8 -*- SignatureUtils.py
import base64
import json
import rsa
def get_sign_content(all_params):
sign_content = ""
for (k, v) in sorted(all_params.items()):
value = v
if not isinstance(value, str):
value = json.dumps(value, ensure_ascii=False)
sign_content += ("&" + k + "=" + value)
sign_content = sign_content[1:]
return sign_content
def fill_private_key_marker(private_key):
return add_start_end(private_key, "-----BEGIN RSA PRIVATE KEY-----\n", "\n-----END RSA PRIVATE KEY-----")
def fill_public_key_marker(public_key):
return add_start_end(public_key, "-----BEGIN PUBLIC KEY-----\n", "\n-----END PUBLIC KEY-----")
def sign_with_rsa(private_key, sign_content, charset):
sign_content = sign_content.encode(charset)
private_key = fill_private_key_marker(private_key)
signature = rsa.sign(sign_content, rsa.PrivateKey.load_pkcs1(private_key, format='PEM'), 'SHA-1')
sign = base64.b64encode(signature)
sign = str(sign, encoding=charset)
return sign
def sign_with_rsa2(private_key, sign_content, charset):
sign_content = sign_content.encode(charset)
private_key = fill_private_key_marker(private_key)
signature = rsa.sign(sign_content, rsa.PrivateKey.load_pkcs1(private_key, format='PEM'), 'SHA-256')
sign = base64.b64encode(signature)
sign = str(sign, encoding=charset)
return sign
def verify_with_rsa(public_key, message, sign):
public_key = fill_public_key_marker(public_key)
sign = base64.b64decode(sign)
return bool(rsa.verify(message, sign, rsa.PublicKey.load_pkcs1_openssl_pem(public_key)))
def add_start_end(key, startMarker, endMarker):
if key.find(startMarker) < 0:
key = startMarker + key
if key.find(endMarker) < 0:
key = key + endMarker
return key
使用
from urllib.parse import urlencode, quote_plus, quote
import json
import requests
import random
import time
from SignatureUtils import get_sign_content, sign_with_rsa2
ICBC_PAY_URL = "https://apipcs3.dccnet.com.cn/api/mybank/pay/cpay/cppayapply/V2" # 工银e企付支付申请服务
ICBC_CLOSE_PAY_URL = "https://api.weixin.qq.com/pay/orderquery" # 工银e企付订单关闭
ICBC_Config = {
"AgreeCode": "", # 合作方协议编号 正式环境由工行提供给贵司
"PayeeAccno1": "", # 收款账号 平台的工行账号,可作为收款账户上送
"PayeeCompanyName1": "", # 收款账户名称 平台的工行账号,可作为收款账户上送
"PayeeAccno2": "", # 收款账号2 商户的工行账号,可作为收款账户上送
"PayeeCompanyName2": "", # 收款账户名称2 商户的工行账号,可作为收款账户上送
"APP_ID": "",
# 网关公钥 正式环境由工行提供给贵司
"APIGW_PUBLIC_KEY": "",
# 签名公钥 正式环境请贵司提供给工行,生成方法请参考《9-联测指引(客户)》公私钥生成方法
"MY_PUBLIC_KEY": "",
# 签名私钥 正式环境请贵司保存好,切勿给工行或他人,生成方法请参考《9-联测指引(客户)》公私钥生成方法需要https://www.ssleye.com/ssltool/pkcs.html将这个pkcs8转换格式转为pkcs1
# "MY_PRIVATE_KEY": "",
"MY_PRIVATE_KEY": ",
# 登陆用户名 对账文件服务器登陆用户名
"username": "eQiFu_2022",
# 登录公钥
"publicKey": "",
# 登录私钥
"privateKey": """""",
}
def createNoncestr(length=32):
"""产生随机字符串,不长于32位"""
chars = "abcdefghijklmnopqrstuvwxyz0123456789"
strs = []
for x in range(length):
strs.append(chars[random.randrange(0, len(chars))])
return "".join(strs)
def pay():
body = {'app_id': '工行提供',
'format': 'json',
'charset': 'UTF-8',
'sign_type': 'RSA2',
'biz_content': json.dumps(
{"agreeCode": "0020000033060410006041000000001464", "asynFlag": "0", "callbackUrl": "www.baidu.com",
"goodsList": [{"goodsAmt": "100", "goodsName": "倚天剑", "goodsNumber": "1", "goodsSubId": "1",
"goodsUnit": "个", "payeeCompanyName": "饮鹿投茵技拿褪辛乓忘嘴易"}],
"internationalFlag": "1", "orderAmount": "100", "orderCode": "2019062730023", "orderCurr": "001",
"orderRemark": "订单备注-直接支付-境内", "partnerSeq": "98a2d815bb134a118fdb4513",
"payChannel": "1", "payEntitys": "1", "payMemno": "19173980", "payMode": "1", "payeeList": [
{"mallCode": "PIPWF2202207158", "mallName": "华为技术有限公司", "payAmount": "100",
"payeeAccno": "0200023309200006066", "payeeCompanyName": "饮鹿投茵技拿褪辛乓忘嘴易",
"payeeSysflag": "1"}], "rceiptRemark": "回单补充信息备注", "returnUrl": "www.baidu.com",
"submitTime": "工行提供有专门的测试时间", "sumPayamt": "100"}),
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()), # 交易发生时间戳,yyyy-MM-dd HH:mm:ss格式
'msg_id': createNoncestr(), # 消息通讯唯一编号,每次调用独立生成,APP级唯一
}
# 筛选并排序拼接
sign_content = '/api/mybank/pay/cpay/cppayapply/V2?' + get_sign_content(body)
sign = sign_with_rsa2(ICBC_Config["MY_PRIVATE_KEY"], sign_content, "UTF-8")
body['sign'] = sign
payload = urlencode(body)
headers = {
'Content-Type': 'application/x-www-form-urlencoded'
}
response = requests.request("POST", ICBC_PAY_URL, headers=headers, data=payload)
print("res_data:", response.json())
if __name__ == "__main__":
pay()