🆕 #2627【微信支付】增加V3分账动账通知解析方法

This commit is contained in:
Wyman 2022-04-29 09:48:22 +08:00 committed by GitHub
parent 40f10de24b
commit 42c57b6de9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 382 additions and 0 deletions

View File

@ -0,0 +1,142 @@
package com.github.binarywang.wxpay.bean.profitsharingV3;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
*
* 微信V3接口
* 通用通知实体
*
* @author yuanbo
* @create 2022-04-26-21:33 PM
*/
@Data
@NoArgsConstructor
public class ProfitSharingNotifyData implements Serializable{
private static final long serialVersionUID = 4242383310854692441L;
/**
* <pre>
* 字段名通知ID
* 是否必填
* 描述通知的唯一ID
* </pre>
*/
@SerializedName("id")
private String id;
/**
* <pre>
* 字段名通知创建时间
* 是否必填
* 描述通知创建的时间Rfc3339标准
* </pre>
*/
@SerializedName("create_time")
private String createTime;
/**
* <pre>
* 字段名通知数据类型
* 是否必填
* 描述通知的资源数据类型
* </pre>
*/
@SerializedName("resource_type")
private String resourceType;
/**
* <pre>
* 字段名通知类型
* 是否必填
* 描述通知的类型
* </pre>
*/
@SerializedName("event_type")
private String eventType;
/**
* <pre>
* 字段名通知数据
* 是否必填
* 描述通知资源数据
* </pre>
*/
@SerializedName("resource")
private Resource resource;
/**
* <pre>
* 字段名通知简要说明
* 是否必填
* 描述通知简要说明
* </pre>
*/
@SerializedName("summary")
private String summary;
@Data
@NoArgsConstructor
public static class Resource implements Serializable {
private static final long serialVersionUID = 8530711804335261449L;
/**
* <pre>
* 字段名加密算法类型
* 是否必填
* 描述对分账结果数据进行加密的加密算法目前只支持AEAD_AES_256_GCM
* </pre>
*/
@SerializedName("algorithm")
private String algorithm;
/**
* <pre>
* 字段名加密前的对象类型
* 是否必填
* 描述加密前的对象类型分账动账通知的类型为profitsharing
* </pre>
*/
@SerializedName("original_type")
private String originalType;
/**
* <pre>
* 字段名数据密文
* 是否必填
* 描述Base64编码后的分账结果数据密文
* </pre>
*/
@SerializedName("ciphertext")
private String cipherText;
/**
* <pre>
* 字段名随机串
* 是否必填
* 描述加密使用的随机串
* </pre>
*/
@SerializedName("nonce")
private String nonce;
/**
* <pre>
* 字段名附加数据
* 是否必填
* 描述附加数据
* </pre>
*/
@SerializedName("associated_data")
private String associatedData;
}
}

View File

@ -0,0 +1,130 @@
package com.github.binarywang.wxpay.bean.profitsharingV3;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
*
* 微信V3接口
* 分账动账通知解密后数据实体
*
* @author yuanbo
* @create 2022-04-26-21:08 PM
*/
@Data
@NoArgsConstructor
public class ProfitSharingNotifyResult implements Serializable {
private static final long serialVersionUID = -2875006651351414624L;
/**
* <pre>
* 字段名直连商户号
* 是否必填
* 描述直连模式分账发起和出资商户
* </pre>
*/
@SerializedName("mchid")
private String mchId;
/**
* <pre>
* 字段名微信订单号
* 是否必填
* 描述微信支付订单号
* </pre>
*/
@SerializedName("transaction_id")
private String transactionId;
/**
* <pre>
* 字段名微信分账/回退单号
* 是否必填
* 描述微信分账/回退单号
* </pre>
*/
@SerializedName("order_id")
private String orderId;
/**
* <pre>
* 字段名商户分账/回退单号
* 是否必填
* 描述分账方系统内部的分账/回退单号
* </pre>
*/
@SerializedName("out_order_no")
private String outOrderNo;
/**
* <pre>
* 字段名分账接收方
* 是否必填
* 描述分账接收方对象
* </pre>
*/
@SerializedName("receiver")
private Receiver receiver;
/**
* <pre>
* 字段名成功时间
* 是否必填
* 描述成功时间Rfc3339标准
* </pre>
*/
@SerializedName("success_time")
private String successTime;
@Data
@NoArgsConstructor
public static class Receiver implements Serializable {
private static final long serialVersionUID = -931070141604645363L;
/**
* <pre>
* 字段名分账接收方类型
* 是否必填
* 描述MERCHANT_ID商户号mch_id或者sub_mch_id
* </pre>
*/
@SerializedName("type")
private String type;
/**
* <pre>
* 字段名分账接收方账号
* 是否必填
* 描述申请本功能商户号
* </pre>
*/
@SerializedName("account")
private String account;
/**
* <pre>
* 字段名分账动账金额
* 是否必填
* 描述分账动账金额单位为分只能为整数
* </pre>
*/
@SerializedName("amount")
private Integer amount;
/**
* <pre>
* 字段名分账/回退描述
* 是否必填
* 描述分账/回退描述
* </pre>
*/
@SerializedName("description")
private String description;
}
}

View File

@ -1,5 +1,6 @@
package com.github.binarywang.wxpay.service;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.profitsharingV3.*;
import com.github.binarywang.wxpay.exception.WxPayException;
@ -161,4 +162,22 @@ public interface ProfitSharingV3Service {
*/
ProfitSharingReceiver deleteProfitSharingReceiver(ProfitSharingReceiver receiver) throws WxPayException;
/**
* <pre>
* 分账动账通知
*
* 分账或分账回退成功后微信会把相关变动结果发送给分账接收方只支持商户
* 对后台通知交互时如果微信收到应答不是成功或超时微信认为通知失败微信会通过一定的策略定期重新发起通知尽可能提高通知的成功率但微信不保证通知最终能成功
* 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_10.shtml
* </pre>
*
* @param notifyData 分账通知实体
* @param header 分账通知头 {@link SignatureHeader}
* @return {@link ProfitSharingNotifyData} 资源对象
* @throws WxPayException the wx pay exception
* @see <a href="https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_1_10.shtml">微信文档</a>
*/
ProfitSharingNotifyData getProfitSharingNotifyData(String notifyData, SignatureHeader header) throws WxPayException;
}

View File

@ -1,15 +1,24 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.profitsharingV3.*;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.ProfitSharingV3Service;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.auth.Verifier;
import com.github.binarywang.wxpay.v3.util.AesUtils;
import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Objects;
/**
* 微信支付V3-资金应用-分账Service
*
@ -82,4 +91,48 @@ public class ProfitSharingV3ServiceImpl implements ProfitSharingV3Service {
String result = this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
return GSON.fromJson(result, ProfitSharingReceiver.class);
}
@Override
public ProfitSharingNotifyData getProfitSharingNotifyData(String notifyData, SignatureHeader header) throws WxPayException {
ProfitSharingNotifyData response = parseNotifyData(notifyData, header);
ProfitSharingNotifyData.Resource resource = response.getResource();
String cipherText = resource.getCipherText();
String associatedData = resource.getAssociatedData();
String nonce = resource.getNonce();
String apiV3Key = this.payService.getConfig().getApiV3Key();
try {
String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
ProfitSharingNotifyData notifyResult = GSON.fromJson(result, ProfitSharingNotifyData.class);
return notifyResult;
} catch (GeneralSecurityException | IOException e) {
throw new WxPayException("解析报文异常!", e);
}
}
private ProfitSharingNotifyData parseNotifyData(String data, SignatureHeader header) throws WxPayException {
if (Objects.nonNull(header) && !this.verifyNotifySign(header, data)) {
throw new WxPayException("非法请求,头部信息验证失败");
}
return GSON.fromJson(data, ProfitSharingNotifyData.class);
}
/**
* 校验通知签名
*
* @param header 通知头信息
* @param data 通知数据
* @return true:校验通过 false:校验不通过
*/
private boolean verifyNotifySign(SignatureHeader header, String data) throws WxPayException {
String beforeSign = String.format("%s\n%s\n%s\n",
header.getTimeStamp(),
header.getNonce(),
data);
Verifier verifier = this.payService.getConfig().getVerifier();
if (verifier == null) {
throw new WxPayException("证书检验对象为空");
}
return verifier.verify(header.getSerialNo(),
beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
}
}

View File

@ -0,0 +1,38 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.testbase.ApiTestModule;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
/**
* 测试类
*
* @author yuanbo
* @create 2022-04-26-22:33 PM
*/
@Test
@Guice(modules = ApiTestModule.class)
public class ProfitSharingV3ServiceImplTest {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Inject
private WxPayService payService;
@Test
public void testProfitSharingNotifyData() throws WxPayException {
SignatureHeader header = new SignatureHeader();
header.setSerialNo("Wechatpay-Serial");
header.setTimeStamp("Wechatpay-Timestamp");
header.setNonce("Wechatpay-Nonce");
header.setSigned("Wechatpay-Signature");
String data = "body";
this.logger.info(this.payService.getProfitSharingV3Service().getProfitSharingNotifyData(data,header).toString());
}
}