1. 接口规则
星驿支付文档中心
  • 文档说明
    • 阅读对象
    • 版本说明
  • 术语
    • 支付模式
    • 名词解释
    • 支付产品
      • 付款码支付
      • 聚合码支付
      • PC支付
      • 小程序支付
        • 微信小程序支付
        • 支付宝小程序支付
      • APP支付
        • 微信支付
        • 支付宝支付
      • H5支付
        • 微信H5支付
        • 支付宝H5支付
        • 云闪付H5支付
        • 手机浏览器H5支付
      • Native支付
        • 支付宝支付
        • 云闪付支付
      • 终端支付
        • 智能云MIS
        • 智能APP
  • 接口规则
    • 协议规则
    • 安全规范
  • 支付交易
    • 交易前指引
      • 微信认证操作流程
      • 支付宝认证操作流程
      • H5页面微信支付对接前准备
      • H5页面支付宝支付对接前准备
      • 支付流程
      • 支付接入注意事项
      • 消费者IP获取指引
    • 基础支付
      • 付款码支付
      • JSAPI支付
      • 订单查询
      • 扫码支付-星驿码
      • 扫码支付-官方码
      • 扫码支付-场景码
      • 关闭订单
      • 撤销订单
      • 退款
      • 退款查询
      • 授权码查询openid
      • 获取银联用户标识
      • 获取微信刷脸凭证
      • 成品油(现金/储值)开票
    • 小程序支付
      • 使用说明
      • 小程序半屏-星驿付
      • 小程序半屏-场景
    • 扫码预授权
      • 扫码预授权
      • 预授权查询
      • 预授权撤销
      • 预授权撤销查询
      • 预授权完成
      • 预授权完成查询
      • 预授权完成撤销
      • 预授权完成撤销查询
    • 订单推送
      • 推送终端交易
      • 关闭推送终端交易
      • 碰一下推单状态查询
      • 碰一下订单取消
      • 碰一下订单推送
    • 智能终端SDK
      • 智能终端SDK
    • 交易通知及对账
      • 交易对账文件-场景应用
      • 交易对账文件-ISV
      • 交易和退款结果通知
      • 交易手续费通知
    • 刷卡预授权
      • 预授权撤销
      • 预授权完成撤销
  • 商户管理
    • 前件前指引
      • 进件说明
      • 图片示例
      • 微信认证操作流程
      • 支付宝认证操作流程
      • 附件下载
    • 商户入网
      • 商户进件上传图片
      • 普通商户进件
      • 普通商户信息修改
      • 小微商户进件
      • 小微商户信息修改
      • 审核结果查询
      • 商户审核结果通知
      • 待审核商户撤回
      • 商户信息查询
      • 结算卡修改
      • 商户快捷修改
      • 受益人信息查询
      • 商户报备查询
      • 商户报备结果通知
      • 商户状态修改
      • 终端报备查询
      • 商户阶梯费率配置
      • 商户阶梯费率查询
    • 商户协议签约
      • 入网协议签约接口调用流程
      • 普通商户用户开户
      • 小微商户用户开户
      • 发短信验证码
      • 获取签约链接
      • 校验验证码
      • 校验验证码-补签
      • 签约结果通知
    • 微信支付宝认证
      • 微信申请单提交
      • 微信申请单撤销
      • 微信申请单状态查询
      • 微信商户授权状态查询
      • 支付宝申请单提交
      • 支付宝申请单撤销
      • 支付宝申请单状态查询
      • 支付宝商户授权状态查询
    • 微信开发配置
      • 微信APPID配置
      • 微信支付目录配置
      • 微信公众号查询
      • 微信开发配置查询
      • 指定渠道报备
      • 微信小程序冻结状态查询
      • 微信小程序冻结通知(非接口)
    • 商户限额调整
      • 商户限额查询
      • 商户提额申请
      • 商户提额记录查询
    • 商户风险处置
      • 商户风险处置通知
      • 风险商户图片上传
      • 微信官方风险商户文件
      • 支付宝官方风险商户文件
      • 风险商户异常流水查询
      • 风险商户调查列表
      • 风险商户调查详情
      • 风险调查单历史查询
      • 风险调查材料下载
      • 风险调查单处理
    • 商户手续费开票
      • 开具发票
      • 开票详情
      • 待开发票信息查询
      • 合并开票商户信息查询
      • 电子普票重发邮箱
      • 开票历史查询
    • 无界收款
      • 无界收款图片上传
      • 无界收款开通
      • 无界收款审核查询
      • 无界收款审核结果通知
  • 资金结算
    • D0结算业务
      • 产品介绍
      • 获取人脸识别链接
      • 人脸认证结果查询
      • D0开通
      • D0信息修改
      • D0状态查询
      • D0开通失败申诉
      • D0申诉结果查询
      • D0申诉结果通知
    • 特殊结算业务
      • 产品介绍
      • 结算业务申请
      • 结算业务变更
      • 结算业务结果查询
      • 结算业务结果通知
    • 提现查账
      • 手工提现
      • 到账记录查询
      • 到账记录批量查询
      • 秒到提现记录查询
      • 账户余额查询
      • 挂账记录查询
      • 补付申请
      • 提现结果通知
      • 账户余额变动通知
      • 提现对账文件
      • 批量结算提现通知
      • 到账附言配置
      • 查询到账附言
  • 星账云管家
    • 星账云管家
    • 功能开通
      • 功能开通(XZY001)
      • 编辑(XZY003)
      • 编辑上传付款凭证(XZY002)
      • 修改生效状态(XZY004)
      • 查询详情信息(XZY005)
      • 产品开通审核通知(XZY006)
    • 公共接口
      • 上传文件(XZY013)
    • 分账
      • 交易
        • 分账结算电子回单下载(XZY037)
        • 余额查询接口(XZY041)
        • 分账合并入账电子回单下载(XZY039)
        • 订单查询(XZY015)
        • 分账(XZY014)
        • 分账撤销退回(XZY017)
        • 分账结果回调(XZY016)
      • 客户管理
        • 新增客户(XZY018)
        • 分账授权申请接口-收单统一结算(XZY043)
        • 批量分账授权申请接口-收单统一结算(XZY044)
        • 合作状态调整(XZY020)
        • 解约/重启签约(XZY-001-002)
        • 编辑客户(XZY023)
        • 替换客户结算卡(XZY022)
        • 审核通知(XZY019)
        • 查询分账客户信息(XZY021)
    • 归集
      • 开户/授权
        • 归集授权申请接口-收单统一结算(XZY037)
        • 审核通知(XZY025)
        • 合作状态调整(XZY026)
        • 查询归集客户信息(XZY027)
        • 替换结算卡(XZY028)
        • 编辑信息(XZY029)
      • 交易
        • 归集合并入账电子回单下载(XZY040)
        • 归集结算电子回单下载(XZY038)
        • 余额查询接口(XZY042)
        • 订单查询(XZY031)
        • 归集-支持自定义服务费(XZY030)
        • 归集(XZY030)
        • 归集结果回调(XZY032)
        • 归集撤销退回(XZY033)
    • 提现
      • 提现订单查询(XZY035)
      • 结算回调通知(XZY050)
      • 提现电子回单下载(XZY040)
      • 提现(XZY036)
    • 账户
      • 结算资金流水查询(XZY047)
      • 结算卡资金流水查询(XZY049)
  • 设备管理与推送
    • 设备管理
      • 收款设备绑定
      • 收款设备解绑
      • 收款设备绑定查询
      • 设备绑定/解绑结果通知
      • 音箱绑定
      • 音箱绑定查询
      • 音箱解绑
    • 设备推送
      • 音箱播报
      • 交易打印
  • 营销活动
    • 银行活动
      • 产品介绍
      • 图片上传
      • 商户补贴列表查询
      • 银行活动报名
      • 银行活动报名结果查询
      • 银行活动报名结果通知
      • 银行活动列表查询
      • 银行活动退出
    • 特殊行业活动
      • 产品介绍
      • 特殊行业活动报名申请
      • 特殊活动报名结果通知
      • 特殊行业商户活动查询
      • 特殊行业活动图片上传
    • 综合账户
      • 营销增资-订单
      • 营销增资-金额
      • 营销增资查询
      • 综合账户余额查询
      • 综合账户流水查询
    • 增值服务
      • 获取增值营销产品列表
      • 获取增值营销产品明细
      • 获取分享链接
      • 推广结果列表查询
      • 推广结果详情
  • 慧徕店开放平台
    • 慧+SPI
      • 对接前准备
      • 商户同步
        • 开发配置
          • 请求域名
          • 加签&验签
        • 商户同步
        • 商户同步查询
        • 商户同步回调
      • 应用模块
        • 产品介绍
        • 开发配置
          • 请求域名
          • 加签&验签
        • 应用及增值服务查询
        • 商户应用注册及增值服务状态查询
        • 应用注册
        • 应用注册状态查询
        • 应用注册回调通知
        • 增值服务激活
        • 增值服务激活状态查询
        • 增值服务激活回调通知
      • 聚合支付SPI
        • 产品介绍
        • 开发配置
          • 签名方式
          • 验签方式
          • 公共参数
        • 支付API
        • 付款码支付
        • 扫码支付-官方码
        • JSAPI支付
        • 扫码支付-聚合码
        • 获取小程序支付信息
        • 订单查询
        • 交易结果通知
        • 退款
        • 退款查询
        • 关闭订单
        • 获取银联用户标识
    • 应用开发API
      • 验签说明
      • 应用支付
      • 门店信息同步
      • 门店增值服务激活
  • 支付广告
    • 微信小程序广告
    • 支付宝小程序广告
    • 支付宝H5广告
    • SDK资源文件
  • 数据字典
    • MCC
      • 搜索查询二级mcc
      • 获取一级mcc目录
      • 获取二级mcc目录
    • 地区码
      • 获取省
      • 获取市
      • 获取区县
    • 落地银行
      • 落地银行查询
      • 落地银行下级机构查询
    • 银行信息
      • 获取银行省信息-联行号
      • 获取银行市信息-联行号
      • 银行信息查询
      • 查询联行号信息
      • 根据银行卡号获取银行信息
    • 连锁品牌
      • 获取连锁品牌信息
  • 分账服务
    • 订单分账
      • 订单分账
      • 订单分账结果查询
      • 订单分账撤销
      • 订单分账对帐文件
    • 余额分账
      • 余额分账
      • 余额分账结果查询
      • 可分账余额查询
    • 分账配置
      • 分账图片上传
      • 分账商户配置
      • 分账商户配置结果查询
      • 分账商户配置结果通知
      • 分账商户移除
      • 快捷创建资金接收方
      • 修改分账比例与金额
      • 商圈信息查询
  • 数据模型
    • FileHeader
    • FileBody
    • StandardResponse
  1. 接口规则

安全规范

1、域名与账号#

测试环境域名:https://xyf-server-test.postar.cn
uat环境域名:https://xyzscxm.postar.cn
生产环境域名:https://yyfsvxm.postar.cn
机构号,商户号对接时由国通对接人员提供。

2、测试注意(重要重要!!!必看!!!!!)#

注意:测试环境提供的银行间连生产测试账号是生产环境用来进行支付业务体验的账号,交易中产生的一切信息均为生产环境数据。用户支付需使用正式版微信、支付宝登陆用户的支付账号,使用用户的账号余额或绑定的银行卡等支付渠道进行支付。由于测试账号业务特点,不会产生清算资金划拨。所以使用测试账号时请务必使用小于0.1元的小金额,并请务必在支付当日完成退款。否则因隔日退款失败造成的测试资金损失由测试机构承担,并视为放弃资金。由于该账号为测试体验账号,所以禁止将该账号用于生产业务用途,禁止在商户真实业务中使用。对于将测试账号误用在商户真实业务造成的商户、用户等资金损失,由测试机构承担。 使用测试环境则视为同意上述使用协议。

3、压测说明#

生产环境、测试环境均严禁私自压测。如有压测需求,请先联系技术协调员,在【测试环境】进行!【生产环境】因风控及通道限制不提供压测支持!!
风控服务会自动关闭压测商户,因此请不要在生产环境进行压测。

4、签名算法#

说明:返回参数及请求参数说明:邮驿付&星驿付后期会对返回参数保留扩展的权力,扩展方式为新增参数但不会删除参数,请商户在解析邮驿付&星驿付返回参数时要支持邮驿付&星驿付可能扩展参数这种情况
1.
将所有传的参数(包括timeStamp和version,不含sign)拼接成一个字符串,拼接格式: key1=value1&key2=value2&key3=value3,其中 key1,key2,key3按字母ASCII值大小升序排序,如果value是一个JSON,则将value转为json字符串再拼接,比如key1=value1&key2=value2&key3={"key4":"value4","key5":"value5"},key3中value的key可以不排序,但要保证顺序和传参时的顺序一致(若参数值为null,则不要拼接,如果是空字符串,则还是需要拼接)
2.
将上面拼接的字符串用sha256算法hash(注意:结果为小写字母,若是大写的,需转为小写),以交易查询接口为例,拼接后的字符串为:
String str = "agetId=xxx&custId=xxx&custLogin=xxx&orderNo=xxx&timeStamp=xxx&version=1.0.0",
之后用sha256算法:String sha256Str = SHA256.getSHA256(str);
3.
对上面hash后的字符串进行rsa签名操作,如:
String sign = encrypt(publicKey, sha256Str);
sign即为参数表中的sign参数,与其它参数一起构成最终的参数,其中: publicKey为公钥,对接时会提供。

5、签名demo#

1.
JAVA
展开查看完整demo
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import sun.misc.BASE64Decoder;
import javax.crypto.Cipher;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.net.SocketTimeoutException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.TreeMap;

@Slf4j
public class Demo {

    private static String publicKey = "xxx";

    public static void main(String[] args) {
        TreeMap<String, Object> params = new TreeMap<>();
        // 放入参数
        params.put("agetId", "xx");
        params.put("custId", "xx");
        params.put("orderNo", "xx");
        // ...
        params.put("version", "1.0.0");
        // 拼接参数,生成sign
        StringBuilder sb = new StringBuilder();
        for (String key : params.keySet()) {
            if (params.get(key) == null) {
                continue;
            }
            String valueStr;
            Object value = params.get(key);
            // 判断是否为可直接 toString() 的简单类型
            if (value instanceof CharSequence      // 包含 String、StringBuilder 等
                    || value instanceof Number      // 所有数值类型包装类
                    || value instanceof Boolean
                    || value instanceof Character
                    || value instanceof Enum) {     // 枚举类型,通常使用枚举名即可
                valueStr = value.toString();
            } else {
                // 复杂对象(如 Map、List、数组、自定义 POJO 等)转为 JSON 字符串
                valueStr = JSON.toJSONString(value);
            }
            sb.append(key).append("=").append(valueStr).append("&");
        }
        String res = sb.substring(0, sb.lastIndexOf("&"));
        log.info("拼接的待签名串:{}", res);
        String sha256 = SHA256.getSHA256(res);
        String sign = RSAUtil.encrypt(publicKey, sha256);
        params.put("sign", sign);
        String paramJson = JSONObject.toJSONString(params);
        String url = "xxx";
        // 发送请求
        String response = HttpUtil.postData(url, paramJson);
    }


    public static class SHA256 {


        /**
         * 利用java原生的类实现SHA256加密
         *
         * @param str 加密后的报文
         * @return
         */
        public static String getSHA256(String str) {
            MessageDigest messageDigest;
            String encodestr = "";
            try {
                messageDigest = MessageDigest.getInstance("SHA-256");
                messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
                encodestr = byte2Hex(messageDigest.digest());
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
            return encodestr;
        }


        /**
         * 将byte转为16进制
         *
         * @param bytes
         * @return
         */
        private static String byte2Hex(byte[] bytes) {
            StringBuilder stringBuffer = new StringBuilder();
            String temp;
            for (byte aByte : bytes) {
                temp = Integer.toHexString(aByte & 0xFF);
                if (temp.length() == 1) {
                    stringBuffer.append("0");
                }
                stringBuffer.append(temp);
            }
            return stringBuffer.toString();
        }
    }


    public static class RSAUtil {
        /**
         * 使用公钥对明文进行签名
         *
         * @param publicKey 公钥
         * @param plainText 明文
         * @return
         */
        public static String encrypt(String publicKey, String plainText) {
            try {
                Cipher cipher = Cipher.getInstance("RSA");
                cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(publicKey));
                byte[] bytes = plainText.getBytes();
                ByteArrayInputStream read = new ByteArrayInputStream(bytes);
                ByteArrayOutputStream write = new ByteArrayOutputStream();
                byte[] buf = new byte[117];
                int len = 0;
                while ((len = read.read(buf)) != -1) {
                    byte[] buf1 = null;
                    if (buf.length == len) {
                        buf1 = buf;
                    } else {
                        buf1 = new byte[len];
                        for (int i = 0; i < len; i++) {
                            buf1[i] = buf[i];
                        }
                    }
                    byte[] bytes1 = cipher.doFinal(buf1);
                    write.write(bytes1);
                }
                return Base64Util.encode(write.toByteArray());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }


        /**
         * 得到公钥
         *
         * @param key 密钥字符串(经过base64编码)
         * @throws Exception
         */
        public static PublicKey getPublicKey(String key) throws Exception {
            byte[] keyBytes;
            keyBytes = new BASE64Decoder().decodeBuffer(key);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(keySpec);
            return publicKey;
        }
    }

    public static class Base64Util {
        private static final char[] base64EncodeChars = new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };

        private static byte[] base64DecodeChars = new byte[] { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 };

        private Base64Util() {}

        /**
         * 将字节数组编码为字符串
         *
         * @param data
         */
        public static String encode(byte[] data) {
            StringBuilder sb = new StringBuilder();
            int len = data.length;
            int i = 0;
            int b1, b2, b3;


            while (i < len) {
                b1 = data[i++] & 0xff;
                if (i == len) {
                    sb.append(base64EncodeChars[b1 >>> 2]);
                    sb.append(base64EncodeChars[(b1 & 0x3) << 4]);
                    sb.append("==");
                    break;
                }
                b2 = data[i++] & 0xff;
                if (i == len) {
                    sb.append(base64EncodeChars[b1 >>> 2]);
                    sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
                    sb.append(base64EncodeChars[(b2 & 0x0f) << 2]);
                    sb.append("=");
                    break;
                }
                b3 = data[i++] & 0xff;
                sb.append(base64EncodeChars[b1 >>> 2]);
                sb.append(base64EncodeChars[((b1 & 0x03) << 4) | ((b2 & 0xf0) >>> 4)]);
                sb.append(base64EncodeChars[((b2 & 0x0f) << 2) | ((b3 & 0xc0) >>> 6)]);
                sb.append(base64EncodeChars[b3 & 0x3f]);
            }
            return sb.toString();
        }

        /**
         * 将base64字符串解码为字节数组
         *
         * @param str
         */
        public static byte[] decode(String str) throws Exception{
            byte[] data = str.getBytes("GBK");
            int len = data.length;
            ByteArrayOutputStream buf = new ByteArrayOutputStream(len);
            int i = 0;
            int b1, b2, b3, b4;
            while (i < len) {
                /* b1 */
                do {
                    b1 = base64DecodeChars[data[i++]];
                } while (i < len && b1 == -1);
                if (b1 == -1) {
                    break;
                }

                /* b2 */
                do {
                    b2 = base64DecodeChars[data[i++]];
                } while (i < len && b2 == -1);
                if (b2 == -1) {
                    break;
                }
                buf.write((b1 << 2) | ((b2 & 0x30) >>> 4));

                /* b3 */
                do {
                    b3 = data[i++];
                    if (b3 == 61) {
                        return buf.toByteArray();
                    }
                    b3 = base64DecodeChars[b3];
                } while (i < len && b3 == -1);
                if (b3 == -1) {
                    break;
                }
                buf.write((int) (((b2 & 0x0f) << 4) | ((b3 & 0x3c) >>> 2)));

                /* b4 */
                do {
                    b4 = data[i++];
                    if (b4 == 61) {
                        return buf.toByteArray();
                    }
                    b4 = base64DecodeChars[b4];
                } while (i < len && b4 == -1);
                if (b4 == -1) {
                    break;
                }
                buf.write(((b3 & 0x03) << 6) | b4);
            }
            return buf.toByteArray();
        }
    }

    public static class HttpUtil {
        public static String postData(String url, String json) {
            return postData(url, json, StandardCharsets.UTF_8);
        }

        public static String postData(String url, String json, Charset responseCharset) {
            log.info("请求地址:" + url);
            HttpPost httpPost = new HttpPost(url);
            setTimeOut(httpPost);
            log.info("连接成功");
            StringEntity requestEntity = new StringEntity(json, "UTF-8");
            requestEntity.setContentEncoding("UTF-8");
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setEntity(requestEntity);
            log.info("请求报文:" + json);
            return execute(httpPost, responseCharset);
        }

        private static void setTimeOut(HttpRequestBase httpRequest) {
            //设置超时时间 请求超时时间 12s
            RequestConfig config = RequestConfig.custom()
                    // 设置连接超时时间(单位毫秒)
                    .setConnectTimeout(12000)
                    // 设置请求超时时间(单位毫秒)
                    .setConnectionRequestTimeout(2000)
                    // socket读写超时时间(单位毫秒)
                    .setSocketTimeout(2000).build();
            httpRequest.setConfig(config);
        }

        private static String execute(HttpRequestBase httpRequest, Charset responseCharset) {
            log.info("开始请求————————");
            log.info("请求中------------------");
            long start = System.currentTimeMillis();
            String result = "";
            try (CloseableHttpClient httpClient = HttpClientBuilder.create().build();
                 CloseableHttpResponse response = httpClient.execute(httpRequest)) {
                long end = System.currentTimeMillis();
                log.info("请求结束————————");
                log.info("响应时间:" + (end - start));
                if (response != null) {
                    // 从响应模型中获取响应实体
                    HttpEntity responseEntity = response.getEntity();
                    log.info("响应状态为:" + response.getStatusLine());
                    if (responseEntity != null) {
                        log.info("响应内容长度为:" + responseEntity.getContentLength());
                        result = EntityUtils.toString(responseEntity, responseCharset);
                        log.info("响应内容为:" + result);
                    }
                }
            } catch (SocketTimeoutException s) {
                log.error("请求超时", s);
            } catch (Exception e) {
                log.error("请求处理异常", e);
            }
            return result;
        }
    }
}
2.
C#
展开查看完整demo
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using Newtonsoft.Json.Linq;

namespace Api.XYFPay.Helper.Test
{
public class RSA2Helper
{
    //公钥,对接时候使用提供的公钥
    private static string pubKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAg+xmmMsM04/q7yP+ykr8cagYhMls1612W5Mpq5WlofErS1ksfAH5GvW3qTwgwmgACOL8EcQE6xgmwCrHLe0jt6PfihZkTuNcBA88DEcSyBJeQhPwi1WeUcZF14AjUdLiLmdDGb7eQmiN/w8lMac8IwbjSiXfw4kju1DuISSIbcYFw+BpFIirTzPiyL1hNludAm15ecnLY79ONiZQP1BrkmV/lvwfkXCQkrrATtiCzCZdiZ2XLBhZm8R7GcVtPk5IB5bYKCg7HH6pdRPdul0futAow2mht4kBVeaZeQdA1xkumDPYaoLez9wQBOT5nGubc6NmgAA1AY70I9csXASnBwIDAQAB";

    /// <summary>
    /// 公钥加密demo(提交参数sign)
    /// </summary>
    /// <returns></returns>
    public static object PaTest()
    {
        try
        {
            var parameters = new Dictionary<string, object>();

            var jsonArray = JsonConvert.DeserializeObject("[{\"id\":\"123\" },{\"name\":\"123\"},{\"name12\":\"12333\"},{\"aa\":\"12333\"}]");

            parameters.Add("agetId", "FWH000018501");
            parameters.Add("orderNo", "test112121121212121");
            parameters.Add("orderno2", "方法");
            parameters.Add("orderno3", null);
            parameters.Add("orderNo4", jsonArray);

            // 按键排序字典,包括下划线
            var sortedParameters = parameters.OrderBy(p => p.Key, StringComparer.Ordinal)
             .Where(x=>x.Value!=null)
             .Select(p => p.Value is string ? $"{p.Key}={p.Value}" : $"{p.Key}={JsonConvert.SerializeObject(p.Value)}");

            // 拼接排序后的参数
            var parameterString = string.Join("&", sortedParameters);

            // 计算 SHA256 哈希
            string sha256Str = SHA256Encrypt(parameterString);


            string sign = EncryptByPublicKey(pubKey, sha256Str);

            parameters.Add("sign", sign);


            return parameters;
        }
        catch (Exception ex)
        {
            return "fail";
        }
    }

    /// <summary>
    /// 公钥解密demo,(异步通知 参数需要动态的获取,后面可能会有新增字段情况)
    /// </summary>
    /// <returns></returns>
    public static object ReceiveMessageTest()
    {
        try
        {
            //异步通知内容
            string content = "{\"REPAYMENT_TRANSACTION_ID\":\"\",\"ORDER_STATUS\":\"1\",\"THREE_ORDER_NO\":\"1718613332163578\",\"T_PAY_NO\":\"4200002164202406174443831052\",\"ORDER_TIME\":\"20240617163542\",\"BUSI_DATA\":\"\",\"MEDIATYPE\":\"\",\"REMARK\":\"\",\"CODE_AGE_AMT\":\"0\",\"POS_PAY_WAY\":\"\",\"USTLDAT\":\"20240617\",\"NETR_AMT\":\"1\",\"DISCOUNT_FLAG\":\"0\",\"DISCOUNT_FEE\":\"0\",\"TXAMT\":\"1\",\"CUST_ID\":\"60000007011491\",\"DEVICE_NO\":\"\",\"SREF_NO\":\"\",\"ORD_TYPE\":\"\",\"INVESTMENT_TYPE\":\"\",\"ORDER_CREATE_TIME\":\"\",\"MERCID\":\"\",\"LIMIT_FEE\":\"0\",\"CARD_TYPE\":\"03\",\"MERCH_CONTRIBUTE\":\"\",\"ORDER_NO\":\"20240617TTTardpq\",\"BAT_NO\":\"\",\"PAY_CHANNEL\":\"2\",\"T_ORDER_NO\":\"A0240617163533442648\",\"TXN_RSV1\":\"\",\"TXN_RSV2\":\"\",\"BANK_NAME\":\"零钱通或零钱\",\"PROMOTION_DETAIL\":\"\",\"OLD_ORDER_NO\":\"\",\"sign\":\"DQGWWVppvT0tn8YZP+N63hA1BFsig3d03Ghab6PUcATd71tLWqzPnQM4KtePeZUWLq7RxgwHmkwYi6EUdFSZbM2+9Y9GlcWGbJsC1oyZtWkIaIoyyO9IqoePutr8/Xk6asidkfFu4DcJQf9LsnFx6sj2wWOCAOMJHRPXMIdlqq+Tqasn8QUHotgtFbFEiHHaq4WmLVKrwpXrXb76hD4fUTpFrHTafD8DXZeOV/uodckxGX7jCyrmVBOi4xwlLsOGMPwqI3vyq01R9Af4ZzW8TgMZ+poWlXwGQFBO4WYaBqXbG2XsCggMlc1JDysUkFqnahYgR2gWO1RfBUnQQS+WBw==\",\"DEBT_STATE\":\"\",\"CUST_FEE\":\"0\",\"TRAN_TYPE_SER\":\"01\",\"PAY_TYPE\":\"\",\"AGET_ID\":\"FWH000018501\",\"BANK_CODE\":\"OTHERS\",\"OPEN_ID\":\"oBY7i5bBtwn9oyFyOjnTxhUcKOzk\",\"PAY_WAY\":\"8\",\"TRADING_IP\":\"120.33.197.108\",\"CUST_AMT\":\"0\"}";


            object notify = JsonConvert.DeserializeObject(content);

            //var json = JsonConvert.SerializeObject(notify, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });
            var parameters = JsonConvert.DeserializeObject<Dictionary<string, object>>(content);

            string sign = parameters.FirstOrDefault(x => x.Key == "sign").Value.ToString();
            parameters.Remove("sign");

            // 按字典排序,包括下划线
            var sortedParameters = parameters.OrderBy(p => p.Key, StringComparer.Ordinal)
             .Select(p => p.Value is string ? $"{p.Key}={p.Value}" : $"{p.Key}={JsonConvert.SerializeObject(p.Value)}");

            // 拼接排序后的参数
            var parameterString = string.Join("&", sortedParameters);

            // 计算 SHA256 哈希
            string sha256Str = SHA256Encrypt(parameterString);


            string strRst = Encoding.UTF8.GetString(DecryptByPublicKey(sign, pubKey));

            if (strRst == sha256Str)
            {
                return JsonConvert.DeserializeObject<JObject>("{\"rspCod\":\"\",\"rspMsg\":\"success\"}");
            }

            return JsonConvert.DeserializeObject<JObject>("{\"rspCod\":\"\",\"rspMsg\":\"fail\"}");
        }
        catch (Exception ex)
        {
            return JsonConvert.DeserializeObject<JObject>("{\"rspCod\":\"\",\"rspMsg\":\"fail\"}");
        }
    }

    /// <summary>
    /// RSA公钥加密
    /// </summary>
    /// <param name="xmlPublicKey">加密公钥;为空则默认系统公钥</param>
    /// <param name="enptStr">需要加密的明文字符串</param>
    /// <param name="encoding">编码格式;默认:UTF-8</param>
    /// <returns>RSA公钥加密的密文</returns>
    public static string EncryptByPublicKey(string publicKey, string enptStr, string encoding = "UTF-8")
    {
        String pubKey = publicKey.Trim();
        String xmlPublicKey = RSAPublicKeyJava2DotNet(pubKey);

        using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
        {
            byte[] cipherbytes;
            rsa.FromXmlString(xmlPublicKey);
            cipherbytes = rsa.Encrypt(Encoding.GetEncoding(encoding).GetBytes(enptStr), false);
            return Convert.ToBase64String(cipherbytes);
        }
    }

    /// <summary>
    /// 用公钥解密
    /// </summary>
    /// <param name="data"></param>
    /// <param name="publicKey">公钥</param>
    /// <returns></returns>
    public static byte[] DecryptByPublicKey(String data, String publicKey)
    {
        String pubKey = publicKey.Trim();
        String xmlPublicKey = RSAPublicKeyJava2DotNet(pubKey);

        RSACryptoServiceProvider publicRsa = new RSACryptoServiceProvider();
        publicRsa.FromXmlString(xmlPublicKey);

        AsymmetricKeyParameter keyPair = DotNetUtilities.GetRsaPublicKey(publicRsa);
        //转换密钥  
        // AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(publicRsa);
        IBufferedCipher c = CipherUtilities.GetCipher("RSA/ECB/PKCS1Padding");// 参数与Java中加密解密的参数一致       
        c.Init(false, keyPair); //第一个参数为true表示加密,为false表示解密;第二个参数表示密钥 
        byte[] DataToEncrypt = Convert.FromBase64String(data);
        byte[] outBytes = c.DoFinal(DataToEncrypt);//解密  
        return outBytes;
    }


    /// <summary>
    /// RSA公钥格式转换,java->.net
    /// </summary>
    /// <param name="publicKey"></param>
    /// <returns></returns>
    private static string RSAPublicKeyJava2DotNet(string publicKey)
    {
        RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey));
        return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>",
            Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()),
            Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned()));
    }



    public static string SHA256Encrypt(string data)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(data);
        byte[] hash = SHA256Managed.Create().ComputeHash(bytes);


        string ret = "";
        for (int i = 0; i < hash.Length; i++)
        {
            ret += Convert.ToString(hash[i], 16).PadLeft(2, '0');
        }

        return ret.PadLeft(32, '0').ToLower();

    }


}
}
3.
PHP
展开查看完整demo
<?php
namespace pay;

class Demo
{
private $agetId = "61xxxxx"; //机构号
private $custId = "600xxxx"; //商户号
private $APIURL = "https://xyf-server-test.postar.cn"; //测试环境

/**
 * 生成签名的通用方法
 * @param array $params 需要签名的参数数组
 * @return string 签名结果
 */
private function generateSign($params)
{
    // 根据key进行字母大小排序
    ksort($params);
    
    // 拼接排序后的参数字符串
    $str = '';
    foreach ($params as $key => $value) {
        if ($value !== null) {
            $str .= "&$key=$value";
        }
    }
    $str = substr($str, 1);

    $sha256 = hash("sha256", $str);
    $key_pem =
        "-----BEGIN PUBLIC KEY-----
" .
        chunk_split(
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxZ933I5F2uwSq3nLlhT7O8wNg8lSpAP4ATP/CHIcvt6QWWk6WLMoJVenJnKaF/nBnnXvb2Bp1GnlxW86a55uQLtbYgbuVZFm8qE3WhXVtEx9dqe3DOS/CqZXbrDEygclBaATZutKYvH3Z7Gl6uRfz63GKVhEJ0BbmrEP7ybQ3wTyXAD37WeiKgkYewKGuYEqnA6uV1/3H9dpiXauj5AcfVJ7ZV1QQHYm4fA4YJTiNB5oKGXiOvMO7lyfisqMrgjsQOwiTUHxxEmMk9HR+vCbVkA4GfynNBTjo7xG93lS0SBNIkf0R8D4PG9NsBVKwaEHyebja1Ak6q8kMBmc/L0azwIDAQAB",
            64,
            "
"
        ) .
        "-----END PUBLIC KEY-----";

    $privateKey = openssl_pkey_get_public($key_pem); //检测是否公钥
    openssl_public_encrypt($sha256, $sign, $privateKey); //公钥加密
    $sign = base64_encode($sign);
    return $sign;
}

/**
 * 获取格式化的公钥
 * @return string 格式化的公钥
 */
private function getPublicKey()
{
    return "-----BEGIN PUBLIC KEY-----
" .
        chunk_split(
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxZ933I5F2uwSq3nLlhT7O8wNg8lSpAP4ATP/CHIcvt6QWWk6WLMoJVenJnKaF/nBnnXvb2Bp1GnlxW86a55uQLtbYgbuVZFm8qE3WhXVtEx9dqe3DOS/CqZXbrDEygclBaATZutKYvH3Z7Gl6uRfz63GKVhEJ0BbmrEP7ybQ3wTyXAD37WeiKgkYewKGuYEqnA6uV1/3H9dpiXauj5AcfVJ7ZV1QQHYm4fA4YJTiNB5oKGXiOvMO7lyfisqMrgjsQOwiTUHxxEmMk9HR+vCbVkA4GfynNBTjo7xG93lS0SBNIkf0R8D4PG9NsBVKwaEHyebja1Ak6q8kMBmc/L0azwIDAQAB",
            64,
            "
"
        ) .
        "-----END PUBLIC KEY-----";
}

//签名demo - 使用通用方法生成签名
public function sign()
{
    // 创建参数字典
    $params = array(
        'agetId' => $this->agetId,    //机构号
        'custId' => $this->custId,    //商户号
        'timeStamp' => time(),        //时间戳
        'version' => '1.0.0'          //版本号
    );
    
    // 调用通用签名方法
    return $this->generateSign($params);
}

//公钥解密参考签名demo
public function decode()
{
    $RSA_DECRYPT_BLOCK_SIZE = 256;
    $sign =
        "k3qi6zUkK2vgj56p2Rvin801AK3JJIgowOVfp2BWyf+XSCAGFImG4X5DiyeFlQ1u02CTdEXdxV2OuaIumJdE7MtkkqYasqir8vFGLVVP1hxNhV0rSuNuhYxjFpjKUAIsGoOc9BgA/EYE3LjWXyt7ubFdkDc9yBJf45TndaylbsdcpPd2Blaq/hm10LAFGqOJhEPPhSOx+XD2R2ZWt6Lh2zNeoSB8Scg5zNaqNeX1xzMFIGhBktfkaehWvZaPGXGKa4Dluw95UJ73ZDib7Pq0c0yZ0PORc1QogZylMkgNFIpqH8W/HVU0ocfm8+UJGDSuEldNlYaY/na1nGRWcdhxuQ=="; // 签名
    $sign = base64_decode($sign);
    $data = str_split($sign, $RSA_DECRYPT_BLOCK_SIZE);
    // 获取公钥
    $key_pem = $this->getPublicKey();
    $pubKey = openssl_pkey_get_public($key_pem);
    $result = "";
    foreach ($data as $block) {
        openssl_public_decrypt($block, $dataDecrypt, $pubKey);
        $result .= $dataDecrypt;
    }
    return $result;
}
}

// 主函数,模拟main方法执行
function main() {
echo "===== Pay类演示程序开始 =====

";

// 创建Pay类实例
$pay = new payPay();

try {
    // 签名生成
    echo "1. 执行sign方法(签名生成):
";
    $signResult = $pay->sign();
    echo "签名结果: " . $signResult . "

";
    
    // 2. 异步通知 - 公钥解密
    echo "2. 执行decode方法(异步通知公钥解密):
";
    $decodeResult = $pay->decode();
    echo "解密结果: " . $decodeResult . "

";
    
} catch (Exception $e) {
    echo "执行过程中发生错误:" . $e->getMessage() . "
";
    echo $e->getTraceAsString() . "
";
}

echo "===== 程序结束 =====
";
}

// 执行主函数
main();
?>
4.
PYTHON
展开查看完整demo
# coding=utf-8
import base64
import hashlib
import time
from collections import OrderedDict
import rsa
from rsa import common, transform, core
from rsa.pkcs1 import _pad_for_encryption as pad_for_encryption


class SignDemo:
def init(self, pubKey):
    '''
    初始化签名验签所需参数
    :param pubKey:公钥
    '''
    self.coding = 'utf-8'
    self.pubKey = pubKey
    # 在演示环境中,使用一个简化的模拟处理,避免实际的RSA密钥加载错误
    try:
        self.publicKey = rsa.PublicKey.load_pkcs1_openssl_der(base64.b64decode(self.pubKey))
    except:
        # 演示环境下使用一个占位符
        self.publicKey = None


def rsa_encrypt(self, plain_text: str):
    '''
    RSA公钥加密
    :param plain_text:字符串
    :return:加密后的字符串
    '''
    # 演示环境下的简化实现
    if self.publicKey is None:
        # 返回一个模拟的加密结果
        return base64.b64encode(f"ENCRYPTED_{plain_text}".encode(self.coding)).decode(self.coding)
        
    plain_text_bytes = plain_text.encode(self.coding)
    block_size = common.byte_size(self.publicKey.n) - 11
    encrypt_bytes_list = []
    for i in range(0, len(plain_text_bytes), block_size):
        block = plain_text_bytes[i:i + block_size]
        key_length = common.byte_size(self.publicKey.n)
        padded = pad_for_encryption(block, key_length)
        num = transform.bytes2int(padded)
        encrypt = core.encrypt_int(num, self.publicKey.e, self.publicKey.n)
        out = transform.int2bytes(encrypt)
        encrypt_bytes_list.append(out)
    return base64.b64encode(b''.join(encrypt_bytes_list)).decode(self.coding)


def rsa_decrypt(self, rsa_str: str):
    '''
    RSA公钥解密
    :param rsa_str:加密字符串
    :return:解密后的字符串
    '''
    if self.publicKey is None:
        # 模拟解密逻辑
        try:
            decoded = base64.b64decode(rsa_str).decode(self.coding)
            if decoded.startswith("ENCRYPTED_"):
                return decoded[10:]  # 返回ENCRYPTED_后面的内容
        except:
            pass
        # 如果不是模拟的加密结果,返回原始内容
        return "DECRYPTED_CONTENT"
        
    rsa_str_bytes = base64.b64decode(rsa_str)
    decrypt_bytes_list = []
    block_size = common.byte_size(self.publicKey.n)
    for i in range(0, len(rsa_str_bytes), block_size):
        block = rsa_str_bytes[i:i + block_size]
        num = transform.bytes2int(block)
        decrypt = core.decrypt_int(num, self.publicKey.e, self.publicKey.n)
        out = transform.int2bytes(decrypt)
        sep_idx = out.index(b"\x00", 2)
        out = out[sep_idx + 1:]
        decrypt_bytes_list.append(out)
    return b''.join(decrypt_bytes_list).decode(self.coding)


def sha_hex(self, plaintext: str):
    '''
    SHA256哈希计算
    :param plaintext:原始字符串
    :return:SHA256哈希值
    '''
    sha = hashlib.sha256()
    sha.update(plaintext.encode(self.coding))
    return sha.hexdigest()


@staticmethod
def get_timestamp():
    '''
    获取当前时间戳
    :return:当前时间戳字符串
    '''
    return str(int(time.time()))


@staticmethod
def join_params(params: dict, exclude_sign: bool = False):
    '''
    参数字典拼接(空字段不参与拼接)
    :param params:参数字典
    :param exclude_sign:是否排除sign字段
    :return:拼接后的参数字符串
    '''
    if exclude_sign:
        params = params.copy()
        params.pop('sign', None)
    # 参数排序
    _sorted = OrderedDict(sorted(params.items()))
    _sign_list = []
    for k, v in _sorted.items():
        if v:
            _sign_list.append(f"{k}={v}")
    join_msg = '&'.join(_sign_list)
    return join_msg


def sign(self, data: dict):
    '''
    生成签名
    :param data:待签名数据
    :return:签名结果
    '''
    join = self.join_params(data)
    hex_data = self.sha_hex(join)
    return self.rsa_encrypt(hex_data)


def sign_check(self, data: dict):
    '''
    验证签名
    :param data:包含签名的数据
    :return:签名是否有效
    '''
    req_sign = data.get('sign')
    if not req_sign:
        return False
    
    # 1. 用公钥解密签名,获取原始sha256哈希值
    try:
        decrypted_sign = self.rsa_decrypt(req_sign)
    except Exception as e:
        return False
    
    # 2. 用参数重新计算sha256哈希值
    params_without_sign = self.join_params(data, True)  # 排除sign字段
    calculated_hash = self.sha_hex(params_without_sign)
    
    # 3. 比较两个哈希值是否相同
    return decrypted_sign == calculated_hash


# 主函数,用于演示加签验签方法
def main():
print("===== 加签验签演示程序开始 =====
")

try:
    # 1. 创建SignDemo实例并初始化
    print("1. 创建SignDemo实例并初始化")
    # 使用模拟的公钥进行初始化
    sign_demo = SignDemo()
    mock_pub_key = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAglcOjys1Rn5zlWbPFZbNqlO6a+AePnTc0v0N7kNEk9JH0ou+j2SombKjjxA5v4HTx3WMKt5tIR5xegheQc8Jde1RO//re1YJM5N49rO23AnYxEVhs+7B9CE8P096c1vgRx9f2AjzbgX5BOIabqgP64BovCwjgRTaqmyC2j5W8hlqnsBWyT7FKhGm9n6IgZGVo6wJiWYWe0kEdeVpiz4v3NMs3a3K5ScMrUeRtkbbyU8csKdpfFBTRAZuLYSPPb4lqs9aH41eviGe1zRyyAXkpGcnkwqMtGMy7qvZ0cO/YzbzM3PH6chmT+Xk2bMNSokD1Jvtm3+RSkYYV/GNo770xQIDAQAB"
    sign_demo.init(pubKey=mock_pub_key)
    print("初始化完成
")
    
    # 2. 获取时间戳
    print("2. 获取时间戳")
    timestamp = sign_demo.get_timestamp()
    print(f"当前时间戳: {timestamp}
")
    
    # 3. 演示参数排序和拼接
    print("3. 演示参数排序和拼接")
    test_params = {
        "c": "value_c",
        "a": "value_a",
        "b": "value_b",
        "empty": "",
        "sign": "test_sign"
    }
    joined_params = sign_demo.join_params(test_params)
    print(f"原始参数: {test_params}")
    print(f"排序并拼接后的参数: {joined_params}
")
    
    # 4. 演示签名生成
    print("4. 演示签名生成")
    data_to_sign = {"vaaaaa":"value1","vaaOrder1":"vaaOrder1","timestamp":"timestamp"}
    signature = sign_demo.sign(data_to_sign)
    print(f"待签名数据: {data_to_sign}")
    print(f"生成的签名: {signature}
")
    
    # 5. 演示签名验证-异步通知
    print("5. 演示签名验证")
    # 添加签名到数据中进行验证
    data_with_sign = data_to_sign.copy()
    # 获取的sign值
    data_with_sign['sign'] = "NKTenOMX86uXUkD+tJu0X4te4JXj1m4y6cXv2Lv/PM6DxAzmZSMbUKFk7ONrzec18peP69Jcu3MuNLZB8+b+KQ76Wy9b5UHvkweNJKnF0WS2gF31Yhkic9Y/FhqRQzR8uqN4wekFoqc+JUQy9HmoSNly4B/Umx4nDlZSaBoexxuxz9WYE+Qu9yMNABq9oK9/MZw17m4Hy6ZXmxBULz/oWLxV3Zq5nEvufgXENoUE7eZPfXmS4vjO3jd2hmm21F6sRiKE9h1iLzDDZ1hQ3msNj8qg3ai+uoOmZdmZ+uL4PCv//07F2LQDVXKF/iXFQRFVU/4lLAVe7wiBaW8QEBgJ1A=="
    is_valid = sign_demo.sign_check(data_with_sign)
    print(f"待验证数据: {data_with_sign}")
    print(f"签名验证结果: {'通过' if is_valid else '失败'}
")
    
except Exception as e:
    print(f"演示过程中发生错误: {str(e)}")
    import traceback
    traceback.print_exc()

print("===== 加签验签演示程序结束 =====")


# 执行主函数
if __name__ == "__main__":
main()

6、sign验证失败排查方案#

检查下公钥是否正确,是否有把测试环境的公钥拿到生产环境来用;机构号和公钥一一对应,是否有把其他机构的公钥拿到当前机构来用,最好在实际加载公钥的位置打印下日志看看。
检查下参数字典序拼接的有没有问题,区分大小写,比如,tOrderNo是排在reOrderNo的前面的;null的参数不要拼接,空字符串的参数需要拼接;值是对象的参数需要将对象转为json字符串后在拼接在key=后面。比如:
{
    "orderNo": "600889899046188256",
    "code": "134469628577908003",
    "latitude": "22.988558",
    "type": "P",
    "title": "授权码销售",
    "version": "1.0.0",
    "timeStamp": "20251119140829",
    "agetId": "61000000339134",
    "custId": "60000001038669",
    "tradingIp": "183.1.65.73",
    "detail": {
        "goods_detail": [
            {
                "goods_name": "星群50g口袋鸡胸肉奥尔良味",
                "quantity": 1,
                "price": 350,
                "goods_id": "6939733903026"
            }
        ]
    },
    "txamt": "350",
    "outTime": "15",
    "longitude": "113.269323",
    "subAppid": "",
    "remark": null
}
拼接后是如下字符串
agetId=61000000339134&code=134469628577908003&custId=60000001038669&detail={"goods_detail":[{"goods_name":"星群50g口袋鸡胸肉奥尔良味","quantity":1,"price":350,"goods_id":"6939733903026"}]}&latitude=22.988558&longitude=113.269323&orderNo=600889899046188256&outTime=15&subAppid=&timeStamp=20251119140829&title=授权码销售&tradingIp=183.1.65.73&txamt=350&type=P&version=1.0.0
注意上面detail,subAppid和remark3个特殊值的拼接规则。
修改于 2026-03-30 05:47:26
上一页
协议规则
下一页
微信认证操作流程
Built with