Skip to content

Commit 7eb11c8

Browse files
authored
🎨 #1827 微信支付分相关接口优化
1. 将原有请求模型类中一些基础数据类型改为对应的包装类,因为在用户没有显式set的情况下,这些基础数据类型序列化为json时也会以默认值的形式作为参数传到微信端,造成微信端返回错误。 2. 微信支付分相关的回调数据处理方法加上签名验证。 3. 增加方法授权/解除授权服务回调数据处理
1 parent 8bd95bc commit 7eb11c8

File tree

6 files changed

+224
-22
lines changed

6 files changed

+224
-22
lines changed

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.github.binarywang.wxpay.bean.payscore;
22

3+
import java.io.Serializable;
4+
35
import com.google.gson.annotations.SerializedName;
6+
47
import lombok.Data;
58
import lombok.NoArgsConstructor;
69

7-
import java.io.Serializable;
8-
910
/**
1011
* 后付费项目.
1112
*
@@ -29,5 +30,5 @@ public class PostPayment implements Serializable {
2930
@SerializedName("description")
3031
private String description;
3132
@SerializedName("count")
32-
private int count;
33+
private Integer count;
3334
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.github.binarywang.wxpay.bean.payscore;
2+
3+
import java.io.Serializable;
4+
5+
import com.google.gson.annotations.SerializedName;
6+
7+
import lombok.Data;
8+
import lombok.NoArgsConstructor;
9+
10+
/**
11+
* 授权/解除授权服务回调通知结果
12+
* <pre>
13+
* 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml
14+
* </pre>
15+
*/
16+
@Data
17+
@NoArgsConstructor
18+
public class UserAuthorizationStatusNotifyResult implements Serializable {
19+
20+
/**
21+
* 源数据
22+
*/
23+
private PayScoreNotifyData rawData;
24+
25+
/**
26+
* <pre>
27+
* 字段名:公众账号ID
28+
* 变量名:appid
29+
* 是否必填:是
30+
* 类型:string[1,32]
31+
* 描述:
32+
* 调用授权服务接口提交的公众账号ID。
33+
* 示例值:wxd678efh567hg6787
34+
* </pre>
35+
*/
36+
@SerializedName(value = "appid")
37+
private String appid;
38+
39+
/**
40+
* <pre>
41+
* 字段名:商户号
42+
* 变量名:mchid
43+
* 是否必填:是
44+
* 类型:string[1,32]
45+
* 描述:
46+
* 调用授权服务接口提交的商户号。
47+
* 示例值:1230000109
48+
* </pre>
49+
*/
50+
@SerializedName(value = "mchid")
51+
private String mchid;
52+
53+
/**
54+
* <pre>
55+
* 字段名:商户签约单号
56+
* 变量名:out_request_no
57+
* 是否必填:否
58+
* 类型: string[1,64]
59+
* 描述:
60+
* 调用授权服务接口提交的商户请求唯一标识(新签约的用户,且在授权签约中上传了该字段,则在解约授权回调通知中有返回)。
61+
* 示例值:1234323JKHDFE1243252
62+
* </pre>
63+
*/
64+
@SerializedName(value = "out_request_no")
65+
private String outRequestNo;
66+
67+
/**
68+
* <pre>
69+
* 字段名:服务ID
70+
* 变量名:service_id
71+
* 是否必填:是
72+
* 类型: string[1,32]
73+
* 描述:
74+
* 调用授权服务接口提交的服务ID。
75+
* 示例值:1234323JKHDFE1243252
76+
* </pre>
77+
*/
78+
@SerializedName(value = "service_id")
79+
private String serviceId;
80+
81+
/**
82+
* <pre>
83+
* 字段名:用户标识
84+
* 变量名:openid
85+
* 是否必填:是
86+
* 类型: string[1,128]
87+
* 描述:
88+
* 微信用户在商户对应appid下的唯一标识。
89+
* 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
90+
* </pre>
91+
*/
92+
@SerializedName(value = "openid")
93+
private String openid;
94+
95+
/**
96+
* <pre>
97+
* 字段名:回调状态
98+
* 变量名:user_service_status
99+
* 是否必填:否
100+
* 类型: string[1,32]
101+
* 描述:
102+
* 1、USER_OPEN_SERVICE:授权成功
103+
* 2、USER_CLOSE_SERVICE:解除授权成功
104+
* 示例值:USER_OPEN_SERVICE
105+
* </pre>
106+
*/
107+
@SerializedName(value = "user_service_status")
108+
private String userServiceStatus;
109+
110+
/**
111+
* <pre>
112+
* 字段名:服务授权/解除授权时间
113+
* 变量名:openorclose_time
114+
* 是否必填:否
115+
* 类型: string[1,32]
116+
* 描述:
117+
* 服务授权/解除授权成功时间。
118+
* 示例值:20180225112233
119+
* </pre>
120+
*/
121+
@SerializedName(value = "openorclose_time")
122+
private String openOrCloseTime;
123+
124+
/**
125+
* <pre>
126+
* 字段名:授权协议号
127+
* 变量名:authorization_code
128+
* 是否必填:否
129+
* 类型: string[1,32]
130+
* 描述:
131+
* 授权协议号,预授权时返回,非预授权不返回
132+
* 示例值:1275342195190894594
133+
* </pre>
134+
*/
135+
@SerializedName(value = "authorization_code")
136+
private String authorizationCode;
137+
138+
}

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
package com.github.binarywang.wxpay.bean.payscore;
22

3+
import java.io.Serializable;
4+
import java.util.List;
5+
36
import com.google.gson.annotations.SerializedName;
7+
48
import lombok.AllArgsConstructor;
59
import lombok.Builder;
610
import lombok.Data;
711
import lombok.NoArgsConstructor;
812
import lombok.experimental.Accessors;
913
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
1014

11-
import java.io.Serializable;
12-
import java.util.List;
13-
1415
/**
1516
* @author doger.wang
1617
* @date 2020/5/12 16:36
@@ -63,15 +64,15 @@ public String toJson() {
6364
@SerializedName("openid")
6465
private String openid;
6566
@SerializedName("need_user_confirm")
66-
private boolean needUserConfirm;
67+
private Boolean needUserConfirm;
6768
@SerializedName("profit_sharing")
68-
private boolean profitSharing;
69+
private Boolean profitSharing;
6970
@SerializedName("post_payments")
7071
private List<PostPayment> postPayments;
7172
@SerializedName("post_discounts")
7273
private List<PostDiscount> postDiscounts;
7374
@SerializedName("total_amount")
74-
private int totalAmount;
75+
private Integer totalAmount;
7576
@SerializedName("reason")
7677
private String reason;
7778
@SerializedName("goods_tag")

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ private void setFieldValue(UnmarshallingContext context, WxPayOrderNotifyResult
112112
Object val = context.convertAnother(obj, field.getType());
113113
try {
114114
if (val != null) {
115-
PropertyDescriptor pd = new PropertyDescriptor(field.getName(), obj.getClass());
115+
//这里加一个看似多余的(String)强转可解决高jdk版本下的编译报错问题,详情见讨论https://github.com/vaadin/framework/issues/10737
116+
PropertyDescriptor pd = new PropertyDescriptor((String)field.getName(), obj.getClass());
116117
pd.getWriteMethod().invoke(obj, val);
117118
}
118119
} catch (Exception ignored) {

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.github.binarywang.wxpay.service;
22

3+
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
34
import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData;
5+
import com.github.binarywang.wxpay.bean.payscore.UserAuthorizationStatusNotifyResult;
46
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest;
57
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult;
68
import com.github.binarywang.wxpay.exception.WxPayException;
@@ -196,6 +198,19 @@ public interface PayScoreService {
196198
* @throws WxPayException the wx pay exception
197199
*/
198200
WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPayException;
201+
202+
/**
203+
* <pre>
204+
* 授权/解除授权服务回调数据处理
205+
* 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml
206+
* </pre>
207+
*
208+
* @param notifyData 通知数据
209+
* @param header 通知头部数据,不传则表示不校验头
210+
* @return 解密后通知数据 return user authorization status notify result
211+
* @throws WxPayException the wx pay exception
212+
*/
213+
UserAuthorizationStatusNotifyResult parseUserAuthorizationStatusNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
199214

200215
/**
201216
* <pre>
@@ -206,7 +221,7 @@ public interface PayScoreService {
206221
* @param data the data
207222
* @return the wx pay score result
208223
*/
209-
PayScoreNotifyData parseNotifyData(String data);
224+
PayScoreNotifyData parseNotifyData(String data,SignatureHeader header) throws WxPayException;
210225

211226
/**
212227
* <pre>

weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,40 @@
11
package com.github.binarywang.wxpay.service.impl;
22

3+
import java.io.IOException;
4+
import java.net.URISyntaxException;
5+
import java.nio.charset.StandardCharsets;
6+
import java.security.GeneralSecurityException;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import java.util.Objects;
10+
11+
import org.apache.commons.lang3.StringUtils;
12+
import org.apache.http.client.utils.URIBuilder;
13+
14+
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
315
import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData;
16+
import com.github.binarywang.wxpay.bean.payscore.UserAuthorizationStatusNotifyResult;
417
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest;
518
import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult;
619
import com.github.binarywang.wxpay.config.WxPayConfig;
720
import com.github.binarywang.wxpay.exception.WxPayException;
821
import com.github.binarywang.wxpay.service.PayScoreService;
922
import com.github.binarywang.wxpay.service.WxPayService;
1023
import com.github.binarywang.wxpay.v3.util.AesUtils;
24+
import com.google.gson.Gson;
25+
import com.google.gson.GsonBuilder;
26+
1127
import lombok.RequiredArgsConstructor;
1228
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
13-
import org.apache.commons.lang3.StringUtils;
14-
import org.apache.http.client.utils.URIBuilder;
15-
16-
import java.io.IOException;
17-
import java.net.URISyntaxException;
18-
import java.security.GeneralSecurityException;
19-
import java.util.HashMap;
20-
import java.util.Map;
2129

2230
/**
2331
* @author doger.wang
2432
* @date 2020/5/14 9:43
2533
*/
2634
@RequiredArgsConstructor
2735
public class PayScoreServiceImpl implements PayScoreService {
36+
37+
private static final Gson GSON = new GsonBuilder().create();
2838
private final WxPayService payService;
2939

3040
@Override
@@ -129,7 +139,7 @@ public WxPayScoreResult permissionsTerminateByOpenId(String openId, String reaso
129139

130140
@Override
131141
public WxPayScoreResult createServiceOrder(WxPayScoreRequest request) throws WxPayException {
132-
boolean needUserConfirm = request.isNeedUserConfirm();
142+
boolean needUserConfirm = request.getNeedUserConfirm();
133143
WxPayConfig config = this.payService.getConfig();
134144
String url = this.payService.getPayBaseUrl() + "/v3/payscore/serviceorder";
135145
request.setAppid(config.getAppId());
@@ -247,10 +257,31 @@ public WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPay
247257
String result = payService.postV3(url, request.toJson());
248258
return WxPayScoreResult.fromJson(result);
249259
}
260+
261+
@Override
262+
public UserAuthorizationStatusNotifyResult parseUserAuthorizationStatusNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
263+
PayScoreNotifyData response = parseNotifyData(notifyData,header);
264+
PayScoreNotifyData.Resource resource = response.getResource();
265+
String cipherText = resource.getCipherText();
266+
String associatedData = resource.getAssociatedData();
267+
String nonce = resource.getNonce();
268+
String apiV3Key = this.payService.getConfig().getApiV3Key();
269+
try {
270+
String result = AesUtils.decryptToString(associatedData, nonce,cipherText, apiV3Key);
271+
UserAuthorizationStatusNotifyResult notifyResult = GSON.fromJson(result, UserAuthorizationStatusNotifyResult.class);
272+
notifyResult.setRawData(response);
273+
return notifyResult;
274+
} catch (GeneralSecurityException | IOException e) {
275+
throw new WxPayException("解析报文异常!", e);
276+
}
277+
}
250278

251279
@Override
252-
public PayScoreNotifyData parseNotifyData(String data) {
253-
return WxGsonBuilder.create().fromJson(data, PayScoreNotifyData.class);
280+
public PayScoreNotifyData parseNotifyData(String data,SignatureHeader header) throws WxPayException {
281+
if(Objects.nonNull(header) && !this.verifyNotifySign(header, data)){
282+
throw new WxPayException("非法请求,头部信息验证失败");
283+
}
284+
return GSON.fromJson(data, PayScoreNotifyData.class);
254285
}
255286

256287
@Override
@@ -266,4 +297,19 @@ public WxPayScoreResult decryptNotifyDataResource(PayScoreNotifyData data) throw
266297
throw new WxPayException("解析报文异常!", e);
267298
}
268299
}
300+
301+
/**
302+
* 校验通知签名
303+
* @param header 通知头信息
304+
* @param data 通知数据
305+
* @return true:校验通过 false:校验不通过
306+
*/
307+
private boolean verifyNotifySign(SignatureHeader header, String data) {
308+
String beforeSign = String.format("%s\n%s\n%s\n",
309+
header.getTimeStamp(),
310+
header.getNonce(),
311+
data);
312+
return payService.getConfig().getVerifier().verify(header.getSerialNo(),
313+
beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
314+
}
269315
}

0 commit comments

Comments
 (0)