🎨 #1827 微信支付分相关接口优化

1. 将原有请求模型类中一些基础数据类型改为对应的包装类,因为在用户没有显式set的情况下,这些基础数据类型序列化为json时也会以默认值的形式作为参数传到微信端,造成微信端返回错误。
2. 微信支付分相关的回调数据处理方法加上签名验证。
3. 增加方法授权/解除授权服务回调数据处理
This commit is contained in:
winter 2020-10-29 15:20:21 +08:00 committed by GitHub
parent 8bd95bcc22
commit 7eb11c8799
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 224 additions and 22 deletions

View File

@ -1,11 +1,12 @@
package com.github.binarywang.wxpay.bean.payscore;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 后付费项目.
*
@ -29,5 +30,5 @@ public class PostPayment implements Serializable {
@SerializedName("description")
private String description;
@SerializedName("count")
private int count;
private Integer count;
}

View File

@ -0,0 +1,138 @@
package com.github.binarywang.wxpay.bean.payscore;
import java.io.Serializable;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 授权/解除授权服务回调通知结果
* <pre>
* 文档地址https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml
* </pre>
*/
@Data
@NoArgsConstructor
public class UserAuthorizationStatusNotifyResult implements Serializable {
/**
* 源数据
*/
private PayScoreNotifyData rawData;
/**
* <pre>
* 字段名公众账号ID
* 变量名appid
* 是否必填
* 类型string[1,32]
* 描述
* 调用授权服务接口提交的公众账号ID
* 示例值wxd678efh567hg6787
* </pre>
*/
@SerializedName(value = "appid")
private String appid;
/**
* <pre>
* 字段名商户号
* 变量名mchid
* 是否必填
* 类型string[1,32]
* 描述
* 调用授权服务接口提交的商户号
* 示例值1230000109
* </pre>
*/
@SerializedName(value = "mchid")
private String mchid;
/**
* <pre>
* 字段名商户签约单号
* 变量名out_request_no
* 是否必填
* 类型 string[1,64]
* 描述
* 调用授权服务接口提交的商户请求唯一标识新签约的用户且在授权签约中上传了该字段则在解约授权回调通知中有返回
* 示例值1234323JKHDFE1243252
* </pre>
*/
@SerializedName(value = "out_request_no")
private String outRequestNo;
/**
* <pre>
* 字段名服务ID
* 变量名service_id
* 是否必填
* 类型 string[1,32]
* 描述
* 调用授权服务接口提交的服务ID
* 示例值1234323JKHDFE1243252
* </pre>
*/
@SerializedName(value = "service_id")
private String serviceId;
/**
* <pre>
* 字段名用户标识
* 变量名openid
* 是否必填
* 类型 string[1,128]
* 描述
* 微信用户在商户对应appid下的唯一标识
* 示例值oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
* </pre>
*/
@SerializedName(value = "openid")
private String openid;
/**
* <pre>
* 字段名回调状态
* 变量名user_service_status
* 是否必填
* 类型 string[1,32]
* 描述
* 1USER_OPEN_SERVICE授权成功
* 2USER_CLOSE_SERVICE解除授权成功
* 示例值USER_OPEN_SERVICE
* </pre>
*/
@SerializedName(value = "user_service_status")
private String userServiceStatus;
/**
* <pre>
* 字段名服务授权/解除授权时间
* 变量名openorclose_time
* 是否必填
* 类型 string[1,32]
* 描述
* 服务授权/解除授权成功时间
* 示例值20180225112233
* </pre>
*/
@SerializedName(value = "openorclose_time")
private String openOrCloseTime;
/**
* <pre>
* 字段名授权协议号
* 变量名authorization_code
* 是否必填
* 类型 string[1,32]
* 描述
* 授权协议号预授权时返回非预授权不返回
* 示例值1275342195190894594
* </pre>
*/
@SerializedName(value = "authorization_code")
private String authorizationCode;
}

View File

@ -1,6 +1,10 @@
package com.github.binarywang.wxpay.bean.payscore;
import java.io.Serializable;
import java.util.List;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -8,9 +12,6 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* @author doger.wang
* @date 2020/5/12 16:36
@ -63,15 +64,15 @@ public class WxPayScoreRequest implements Serializable {
@SerializedName("openid")
private String openid;
@SerializedName("need_user_confirm")
private boolean needUserConfirm;
private Boolean needUserConfirm;
@SerializedName("profit_sharing")
private boolean profitSharing;
private Boolean profitSharing;
@SerializedName("post_payments")
private List<PostPayment> postPayments;
@SerializedName("post_discounts")
private List<PostDiscount> postDiscounts;
@SerializedName("total_amount")
private int totalAmount;
private Integer totalAmount;
@SerializedName("reason")
private String reason;
@SerializedName("goods_tag")

View File

@ -112,7 +112,8 @@ public class WxPayOrderNotifyResultConverter extends AbstractReflectionConverter
Object val = context.convertAnother(obj, field.getType());
try {
if (val != null) {
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), obj.getClass());
//这里加一个看似多余的(String)强转可解决高jdk版本下的编译报错问题详情见讨论https://github.com/vaadin/framework/issues/10737
PropertyDescriptor pd = new PropertyDescriptor((String)field.getName(), obj.getClass());
pd.getWriteMethod().invoke(obj, val);
}
} catch (Exception ignored) {

View File

@ -1,6 +1,8 @@
package com.github.binarywang.wxpay.service;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData;
import com.github.binarywang.wxpay.bean.payscore.UserAuthorizationStatusNotifyResult;
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest;
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult;
import com.github.binarywang.wxpay.exception.WxPayException;
@ -196,6 +198,19 @@ public interface PayScoreService {
* @throws WxPayException the wx pay exception
*/
WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPayException;
/**
* <pre>
* 授权/解除授权服务回调数据处理
* 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml
* </pre>
*
* @param notifyData 通知数据
* @param header 通知头部数据不传则表示不校验头
* @return 解密后通知数据 return user authorization status notify result
* @throws WxPayException the wx pay exception
*/
UserAuthorizationStatusNotifyResult parseUserAuthorizationStatusNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
/**
* <pre>
@ -206,7 +221,7 @@ public interface PayScoreService {
* @param data the data
* @return the wx pay score result
*/
PayScoreNotifyData parseNotifyData(String data);
PayScoreNotifyData parseNotifyData(String data,SignatureHeader header) throws WxPayException;
/**
* <pre>

View File

@ -1,6 +1,19 @@
package com.github.binarywang.wxpay.service.impl;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData;
import com.github.binarywang.wxpay.bean.payscore.UserAuthorizationStatusNotifyResult;
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest;
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
@ -8,16 +21,11 @@ import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.PayScoreService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.v3.util.AesUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.RequiredArgsConstructor;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.utils.URIBuilder;
import java.io.IOException;
import java.net.URISyntaxException;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
/**
* @author doger.wang
@ -25,6 +33,8 @@ import java.util.Map;
*/
@RequiredArgsConstructor
public class PayScoreServiceImpl implements PayScoreService {
private static final Gson GSON = new GsonBuilder().create();
private final WxPayService payService;
@Override
@ -129,7 +139,7 @@ public class PayScoreServiceImpl implements PayScoreService {
@Override
public WxPayScoreResult createServiceOrder(WxPayScoreRequest request) throws WxPayException {
boolean needUserConfirm = request.isNeedUserConfirm();
boolean needUserConfirm = request.getNeedUserConfirm();
WxPayConfig config = this.payService.getConfig();
String url = this.payService.getPayBaseUrl() + "/v3/payscore/serviceorder";
request.setAppid(config.getAppId());
@ -247,10 +257,31 @@ public class PayScoreServiceImpl implements PayScoreService {
String result = payService.postV3(url, request.toJson());
return WxPayScoreResult.fromJson(result);
}
@Override
public UserAuthorizationStatusNotifyResult parseUserAuthorizationStatusNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
PayScoreNotifyData response = parseNotifyData(notifyData,header);
PayScoreNotifyData.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);
UserAuthorizationStatusNotifyResult notifyResult = GSON.fromJson(result, UserAuthorizationStatusNotifyResult.class);
notifyResult.setRawData(response);
return notifyResult;
} catch (GeneralSecurityException | IOException e) {
throw new WxPayException("解析报文异常!", e);
}
}
@Override
public PayScoreNotifyData parseNotifyData(String data) {
return WxGsonBuilder.create().fromJson(data, PayScoreNotifyData.class);
public PayScoreNotifyData parseNotifyData(String data,SignatureHeader header) throws WxPayException {
if(Objects.nonNull(header) && !this.verifyNotifySign(header, data)){
throw new WxPayException("非法请求,头部信息验证失败");
}
return GSON.fromJson(data, PayScoreNotifyData.class);
}
@Override
@ -266,4 +297,19 @@ public class PayScoreServiceImpl implements PayScoreService {
throw new WxPayException("解析报文异常!", e);
}
}
/**
* 校验通知签名
* @param header 通知头信息
* @param data 通知数据
* @return true:校验通过 false:校验不通过
*/
private boolean verifyNotifySign(SignatureHeader header, String data) {
String beforeSign = String.format("%s\n%s\n%s\n",
header.getTimeStamp(),
header.getNonce(),
data);
return payService.getConfig().getVerifier().verify(header.getSerialNo(),
beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
}
}