微信商家转账到零钱,企业付款升级避坑

微信商家转账到零钱,企业付款升级避坑

1,概述

原微信企业付款升级成为了商家转账到零钱接口,之前的商户开通了企业付款接口的还能继续用(但是微信在升级指引中说后续可能下架企业付款),新的商户只能开通商家转账到零钱接口了,商家转账到零钱官方文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_3_1.shtml

接口说明

**适用对象:**直连商户

**请求URL:**https://api.mch.weixin.qq.com/v3/transfer/batches

**请求方式:**POST

接口限频: 单个商户 50QPS,如果超过频率限制,会报错FREQUENCY_LIMITED,请降低频率请求。

**是否需要证书:**是

2.发起商家转账到零钱

网上有很多代码java代码示例,官方文档也有demo,所以本文就不贴代码了,本文只是想说一些开发过程中需要注意的点和一些坑。

1.证书问题

商家转账到零钱是微信支付V3版本,不同于之前的企业付款(v2版本),微信支付v3版本用到了两个证书,一个是微信支付平台证书,一个是商户api证书,(原来企业付款只需要商户api证书)。

先说两种证书区别,微信支付平台证书是用来给敏感字段加密的(比如姓名字段),商户api证书是用来给参数加签名用的,因为v3支付需要在请求头上加Authorization​字段,官方文档都有,接下来具体说明:

微信支付平台证书

目前微信支付平台证书只能通过api接口下载,官方文档地址先送上:https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml

可以通过postman请求,获取微信返回的参数,

{
	"original_type": "transaction", // 加密前的对象类型
	"algorithm": "AEAD_AES_256_GCM", // 加密算法

	// Base64编码后的密文
	"ciphertext": "...",
	// 加密使用的随机串初始化向量)
	"nonce": "...",
	// 附加数据包(可能为空)
	"associated_data": ""
}

其中微信返回的内容中,ciphertext​字段就是微信支付平台证书的内容,此时这个内容是加密过的,需要解密才行,解密需要用到微信支付apiV3密钥,设置密钥的方法:在【商户平台】->【API安全】的页面设置该密钥,还要说明下,apiv3密钥一定要设置,不然接口调不了,而且设置完了密钥还要设置ip白名单,加入了白名单的ip才能调用接口。

平台证书解密,java demo
public static String decryptResponseBody(String apiV3Key,String associatedData, String nonce, String ciphertext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

            SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES");
            GCMParameterSpec spec = new GCMParameterSpec(128, nonce.getBytes(StandardCharsets.UTF_8));

            cipher.init(Cipher.DECRYPT_MODE, key, spec);
            cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8));

            byte[] bytes;
            try {
                bytes = cipher.doFinal(Base64Utils.decodeFromString(ciphertext));
            } catch (GeneralSecurityException e) {
                throw new IllegalArgumentException(e);
            }
            return new String(bytes, StandardCharsets.UTF_8);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new IllegalStateException(e);
        } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
            throw new IllegalArgumentException(e);
        }
    }


       String apiV3="aaa";//apiV3密钥
       String associatedData="certificate";//微信平台证书下载接口返回的附加数据包(可能为空)
       String nonce="bbbb";//微信平台证书下载接口返回的随机数
       String ciphertext="aaa";//微信平台证书下载接口返回的加密串
       String pubKey=AesUtil.decryptResponseBody(apiV3,associatedData,nonce,ciphertext);
       System.out.println(pubKey);

这时打印出来的是

-----BEGIN CERTIFICATE-----
aaaaaaa
-----END CERTIFICATE-----

的证书密钥字符串,我们可以复制到记事本保存为PublicKey.pem文件,此时这个pem文件就是微信支付平台证书pem文件,如果需要加密,

//其中message是要加密的内容,keyPath是微信支付平台证书pem文件路径
public static String rsaEncryptOAEP(String message,String keyPath)
            throws IllegalBlockSizeException, IOException, CertificateException {
        ClassPathResource classPathResource = new ClassPathResource(keyPath);
        InputStream inputStream = classPathResource.getInputStream();
        X509Certificate certificate = getCertificate(inputStream);
        PublicKey publicKey = certificate.getPublicKey();
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);

            byte[] data = message.getBytes("utf-8");
            byte[] cipherdata = cipher.doFinal(data);
            return Base64.getEncoder().encodeToString(cipherdata);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }
商户api证书

说到商户证书真的踩了很多坑,原来我做企业付款用的是.p12商户证书,而商家转账用的是.pem格式的商户证书,这块花费了我很多时间。

微信商户证书总共有三种格式,证书pkcs12格式、证书pem格式、证书密钥pem格式,官方文档说明:https://kf.qq.com/faq/180824JvUZ3i180824YvMNJj.html,

商家转账用的是证书密钥pem格式,那我原来如果只有.p12证书也不要紧(有pem证书的跳过),可以使用p12证书生成签名使用的api证书私钥文件apiclient_key.pem

方法:使用openssl命令,openssl可以百度安装下

openssl pkcs12 -in 商户证书.p12 -out apiclient_key.pem -nodes -nocerts

上面的命令可以生成商户证书私钥pem文件,可以用文本编辑器打开,可以看到内容是

Bag Attributes
localKeyID: ******
friendlyName: Tenpay Certificate
Key Attributes: <No Attributes>
-----BEGIN PRIVATE KEY-----
*****abcdxxxxxx-----END PRIVATE KEY-----

这样的内容,这边把开头的bag内容去掉,只保留

-----BEGIN PRIVATE KEY-----
*****abcdxxxxxx-----END PRIVATE KEY-----

的内容,然后用此私钥证书pem文件来给参数签名。

说到签名,微信支付v3需要签名的参数,签名串一共有五行,每一行为一个参数。行尾以 \n​(换行符,ASCII编码值为0x0A)结束,包括最后一行。如果参数本身以\n​结束,也需要附加一个\n​,官方文档:https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_0.shtml

 HTTP请求方法\n
 URL\n
 请求时间戳\n
 请求随机串\n
 请求报文主体\n

签名生成后,就需要在请求头放Authorization ​​信息了,Authorization: 认证类型 签名信息

认证类型,目前为WECHATPAY2-SHA256-RSA2048

特别说明,Authorization的时间戳和随机串要和生成签名用到的时间戳和随机串一样

签名信息:

  • 发起请求的商户(包括直连商户、服务商或渠道商)的商户号​ mchid
  • 商户API证书序列号serial_no​,用于声明所使用的证书
  • 请求随机串nonce_str
  • 时间戳timestamp
  • 签名值signature

商户api证书序列号可以通过商户后台查看,也可以通过商户证书pem查看,命令:openssl x509 -in 商户证书.pem -noout -serial

转账时需要加请求头:

            HttpPost httpPost = new HttpPost(url);
            httpPost.addHeader("content-type", "application/json;charset=UTF-8");
            httpPost.addHeader("Accept", "application/json");
            httpPost.addHeader("Authorization", "WECHATPAY2-SHA256-RSA2048"+" "+token);//token为签名
            httpPost.addHeader("Wechatpay-Serial", "aaa");//如果要加密姓名字段,需要多送个Wechatpay-Serial,不加密可以不送
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值