最近在做支付通道开发,需要对接微信和支付宝,银联,云闪付,各大银行等。
微信支付的开放接口对于开发者来说,相对支付宝并不那么友好,主要是有些小坑,开放文档描述的不够一致。
例如,同一个属性,返回的字段名与调起支付的字段名不一致,对开发者来说容易搞混。微信支付的 SDK 中定义为抽象的方法却使用 private 控制修饰符,导致外部不能重写。
微信退款通知
微信退款通知中的退款业务数据是一个加密信息字段 req_info
,需要使用商户秘钥进行解密。
备注:官方文档没有说明的一个坑,加密信息字段req_info
的 BASE64 字符串使用的是 ISO_8859_1
编码,而不是通常默认的 UTF-8
,否则会解密失败。
解密步骤如下
- 对加密串 A 做 base64 解码,得到加密串 B。
- 对商户 key 做md5,得到 32 位 小写 key。
- 用 key 对加密串 B 做 AES-256-ECB 解密(PKCS7Padding)。
加解密工具类
添加依赖
JDK 8 默认支持的是 PKCS5Padding,要支持 PKCS7Padding 需要引入对应的 JCE 包。
支付宝SDK 和 spring-cloud-start 包含了 bcprov-jdk15on
依赖。
1 2 3 4 5
| <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.62</version> </dependency>
|
加解密示例
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 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 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| package com.clearofchina.pay.test;
import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.springframework.util.DigestUtils;
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.Security; import java.util.Base64;
public class AESUtil {
private static final String ALGORITHM = "AES";
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
public static String decryptData(String data, String key) throws Exception { Base64.Decoder decoder = Base64.getDecoder(); String lowMD5 = DigestUtils.md5DigestAsHex(key.getBytes(Charset.defaultCharset())).toLowerCase(); SecretKeySpec secretKey = new SecretKeySpec(lowMD5.getBytes(), ALGORITHM); Security.addProvider(new BouncyCastleProvider()); Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC"); cipher.init(Cipher.DECRYPT_MODE, secretKey); String info = new String(decoder.decode(data), StandardCharsets.ISO_8859_1); byte[] bytes = cipher.doFinal(info.getBytes(StandardCharsets.ISO_8859_1)); return new String(bytes, Charset.defaultCharset()); }
public static String encryptData(String data, String key) throws Exception { String lowMD5 = DigestUtils.md5DigestAsHex(key.getBytes(Charset.defaultCharset())).toLowerCase(); SecretKeySpec secretKey = new SecretKeySpec(lowMD5.getBytes(), ALGORITHM); Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC"); cipher.init(Cipher.ENCRYPT_MODE, secretKey); Base64.Encoder encoder = Base64.getEncoder(); byte[] bytes = cipher.doFinal(data.getBytes()); return encoder.encodeToString(bytes); }
public static void main(String[] args) throws Exception { String apiKey = "4567qwer53321ty444rewq87656uyt"; String data = "<root>\n" + "<out_refund_no><![CDATA[131811191610442717309]]></out_refund_no>\n" + "<out_trade_no><![CDATA[71106718111915575302817]]></out_trade_no>\n" + "<refund_account><![CDATA[REFUND_SOURCE_RECHARGE_FUNDS]]></refund_account>\n" + "<refund_fee><![CDATA[3960]]></refund_fee>\n" + "<refund_id><![CDATA[50000408942018111907145868882]]></refund_id>\n" + "<refund_recv_accout><![CDATA[支付用户零钱]]></refund_recv_accout>\n" + "<refund_request_source><![CDATA[API]]></refund_request_source>\n" + "<refund_status><![CDATA[SUCCESS]]></refund_status>\n" + "<settlement_refund_fee><![CDATA[3960]]></settlement_refund_fee>\n" + "<settlement_total_fee><![CDATA[3960]]></settlement_total_fee>\n" + "<success_time><![CDATA[2018-11-19 16:24:13]]></success_time>\n" + "<total_fee><![CDATA[3960]]></total_fee>\n" + "<transaction_id><![CDATA[4200000215201811190261405420]]></transaction_id>\n" + "</root>"; String encode = encryptData(data, apiKey); String decode = decryptData(encode, apiKey); System.out.println(decode); } }
|
相关参考