Java-微信支付(jsapi)V2及V3版本
Tips:本文没有支付下单及退款等相关业务的具体代码实现。只提供签名、解密等操作的代码实现。
参考文档:
前置准备
SDK
主要使用到了微信支付官方提供的两个SDK
1 2 3 4 5 6
| <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version>0.0.3</version> </dependency>
|
1 2 3 4 5 6
| <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version>0.2.1</version> </dependency>
|
微信商户参数
- 微信APPID (appid)
- 微信支付商户ID (mchid)
- 微信支付APIV3秘钥 (APIV3Key)
- 微信支付秘钥 (privateKey)
- 微信支付P12证书 (xxxxxxxx.p12)
以上参数均在微信商户平台获取
实现代码
常量信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
private static final String KEY_ALIAS = "Tenpay Certificate";
private static final String ALGORITHM = "AES";
private static final String ALGORITHM_MODE_PADDING_V2 = "AES/ECB/PKCS7Padding";
private static final String ALGORITHM_MODE_PADDING_V3 = "AES/GCM/NoPadding";
private static final int APIV3_KEY_LENGTH_BYTE = 32;
private static final int TAG_LENGTH_BIT = 128;
|
微信支付V3版本
读取P12证书
P12证书文件中包含有证书的公钥、私钥、序列号信息。
证书密码为微信支付商户号。(mch_id)
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
|
public PKCS12Result createPKCS12(String keyPath, String keyPass) { try { File cert = ResourceUtils.getFile(keyPath); char[] pem = keyPass.toCharArray(); KeyStore store = KeyStore.getInstance("PKCS12"); store.load(new FileInputStream(cert), pem); X509Certificate certificate = (X509Certificate) store.getCertificate(KEY_ALIAS); certificate.checkValidity(); String serialNumber = certificate.getSerialNumber().toString(16).toUpperCase(); PublicKey publicKey = certificate.getPublicKey(); PrivateKey storeKey = (PrivateKey) store.getKey(KEY_ALIAS, pem);
KeyPair keyPair = new KeyPair(publicKey, storeKey); return new PKCS12Result(keyPair, serialNumber);
} catch (Exception e) { LoggerUtils.error("createPKCS12", "微信支付 - 获取公私钥失败", e.getMessage()); throw new IllegalStateException("Cannot load keys from store: " + keyPath, e); } }
|
构造HttpClient,用于请求V3接口
V3请求时不需要手动签名,签名程序已封装至CloseableHttpClient中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
private CloseableHttpClient httpClientBuilder(String mchId, String apiV3Key, String mchSerialNo, PrivateKey privateKey) { AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier( new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, privateKey)), apiV3Key.getBytes(StandardCharsets.UTF_8));
return WechatPayHttpClientBuilder.create() .withMerchant(mchId, mchSerialNo, privateKey) .withValidator(new WechatPay2Validator(verifier)) .build(); }
|
支付签名
前端在调起支付时需要对参数进行签名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public String paySign(String msg, PrivateKey privateKey) { try { Signature sign = Signature.getInstance("SHA256withRSA"); sign.initSign(privateKey); sign.update(msg.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(sign.sign()); } catch (NoSuchAlgorithmException e) { LoggerUtils.error("paySign", "微信支付 - 支付签名失败", "当前Java环境不支持SHA256withRSA"); throw new WechatPayRuntimeException("当前Java环境不支持SHA256withRSA", e); } catch (SignatureException e) { LoggerUtils.error("paySign", "微信支付 - 支付签名失败", "签名计算失败"); throw new WechatPayRuntimeException("签名计算失败", e); } catch (InvalidKeyException e) { LoggerUtils.error("paySign", "微信支付 - 支付签名失败", "无效的私钥"); throw new WechatPayRuntimeException("无效的私钥", e); } }
|
解密回调数据
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
|
public String decryptNotifyV3(String associatedData, String nonce, String ciphertext, String apiV3Key) { byte[] nonceBytes = nonce.getBytes(StandardCharsets.UTF_8); byte[] apiV3KeyBytes = apiV3Key.getBytes(StandardCharsets.UTF_8); if (apiV3KeyBytes.length != APIV3_KEY_LENGTH_BYTE) { throw new IllegalArgumentException("无效的ApiV3Key,长度必须为32个字节"); } try { Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING_V3); SecretKeySpec key = new SecretKeySpec(apiV3KeyBytes, ALGORITHM); GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonceBytes); cipher.init(Cipher.DECRYPT_MODE, key, spec); cipher.updateAAD(associatedData.getBytes()); return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), StandardCharsets.UTF_8); } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { LoggerUtils.error("decryptNotifyV3", "微信支付 - 解密V3回调数据失败", e.getMessage()); throw new IllegalStateException(e); } catch (InvalidKeyException | InvalidAlgorithmParameterException | BadPaddingException | IllegalBlockSizeException e) { LoggerUtils.error("decryptNotifyV3", "微信支付 - 解密V3回调数据失败", e.getMessage()); throw new IllegalArgumentException(e); } }
|
微信支付V2版本
创建微信支付配置类
V2版本需新建配置类并实现SDK中的WXPayConfig接口。
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
| public class WechatPayConfig implements WXPayConfig {
private final String appid;
private final String mchid;
private final String privateKey;
private final String certPath;
private final Integer httpConnectTimeoutMs;
private final Integer httpReadTimeoutMs;
public WechatPayConfig(String appid, String mchid, String privateKey, String certPath) { this.appid = appid; this.mchid = mchid; this.privateKey = privateKey; this.certPath = certPath; this.httpConnectTimeoutMs = 8000; this.httpReadTimeoutMs = 10000; }
public WechatPayConfig(String appid, String mchid, String privateKey, String certPath, Integer httpConnectTimeoutMs, Integer httpReadTimeoutMs) { this.appid = appid; this.mchid = mchid; this.privateKey = privateKey; this.certPath = certPath; this.httpConnectTimeoutMs = httpConnectTimeoutMs; this.httpReadTimeoutMs = httpReadTimeoutMs; }
@Override public String getAppID() { return this.appid; }
@Override public String getMchID() { return this.mchid; }
@Override public String getKey() { return this.privateKey; }
@Override public InputStream getCertStream() { try { File file = ResourceUtils.getFile(this.certPath); return new FileInputStream(file); } catch (IOException e) { throw new WechatPayRuntimeException("路径:" + this.certPath + " 下找不到证书", e); } }
@Override public int getHttpConnectTimeoutMs() { return this.httpConnectTimeoutMs; }
@Override public int getHttpReadTimeoutMs() { return this.httpReadTimeoutMs; } }
|
构造微信支付SDK对象
1 2 3 4 5 6 7 8 9 10 11 12
|
private WXPay WXPayBuilder(String appid, String mchid, String privateKey, String certPath) { return new WXPay(new WechatPayConfig(appid, mchid, privateKey, certPath)); }
|
请求签名
V2版本请求时需携带签名,SDK中已封装好签名方法。
1 2 3 4 5 6 7 8 9 10 11 12
|
public String v2Sign(Map<String, String> reqdata, String privateKey) throws Exception { Map<String, String> data = new TreeMap<>(); reqdata.forEach(data::put); return WXPayUtil.generateSignature(data, privateKey, WXPayConstants.SignType.MD5); }
|
解密回调数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public String decryptNotifyV2(String reqInfo, String privateKey) { byte[] reqInfoBytes = Base64.getDecoder().decode(reqInfo); byte[] privateKeyBytes = DigestUtil.md5Hex(privateKey).toLowerCase().getBytes(StandardCharsets.UTF_8);
try { Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING_V2); SecretKeySpec spec = new SecretKeySpec(privateKeyBytes, ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, spec); return new String(cipher.doFinal(reqInfoBytes), StandardCharsets.UTF_8); } catch (NoSuchPaddingException | NoSuchAlgorithmException e) { LoggerUtils.error("decryptNotifyV2", "微信支付 - 解密V2回调数据失败", e.getMessage()); throw new IllegalStateException(e); } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) { LoggerUtils.error("decryptNotifyV2", "微信支付 - 解密V2回调数据失败", e.getMessage()); throw new IllegalArgumentException(e); } }
|
请求微信示例
V3版本示例
1 2 3 4 5 6 7 8 9 10
| CloseableHttpClient httpClient = httpClientBuilder(mchid, apiV3Key, serialNumber, privateKey); HttpPost post = new HttpPost(PaymentRemoteApi.WECHAT_PAY_PRE_ORDER); post.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); post.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); HttpEntity entity = new StringEntity(jsonEntity); post.setEntity(entity);
CloseableHttpResponse response = httpClient.execute(post); String stringEntity = EntityUtils.toString(response.getEntity());
|
V2版本示例
1 2 3 4 5 6 7 8 9 10 11
| WXPay wxPay = WXPayBuilder(appid, mchid, privateKey, certPath);
Map<String, String> reqdata = new TreeMap<>(); reqdata.put("xxxx", "xxxx");
String sign = v2Sign(reqdata, privateKey); reqdata.put("sign", sign);
Map<String, String> result = wxPay.refund(reqdata);
|