Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Apr 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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;

}
Original file line number Diff line number Diff line change
@@ -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
*
Expand Down Expand Up @@ -82,4 +91,48 @@ public ProfitSharingReceiver deleteProfitSharingReceiver(ProfitSharingReceiver r
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());
}
}
Loading