Skip to content

Commit

Permalink
🎨 #1827 微信支付分相关接口优化
Browse files Browse the repository at this point in the history
1. 将原有请求模型类中一些基础数据类型改为对应的包装类,因为在用户没有显式set的情况下,这些基础数据类型序列化为json时也会以默认值的形式作为参数传到微信端,造成微信端返回错误。
2. 微信支付分相关的回调数据处理方法加上签名验证。
3. 增加方法授权/解除授权服务回调数据处理
  • Loading branch information
winter4666 authored Oct 29, 2020
1 parent 8bd95bc commit 7eb11c8
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -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;

/**
* 后付费项目.
*
Expand All @@ -29,5 +30,5 @@ public class PostPayment implements Serializable {
@SerializedName("description")
private String description;
@SerializedName("count")
private int count;
private Integer count;
}
Original file line number Diff line number Diff line change
@@ -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]
* 描述:
* 1、USER_OPEN_SERVICE:授权成功
* 2、USER_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;

}
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
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;
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
Expand Down Expand Up @@ -63,15 +64,15 @@ public String toJson() {
@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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ private void setFieldValue(UnmarshallingContext context, WxPayOrderNotifyResult
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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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>
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,40 @@
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;
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
* @date 2020/5/14 9:43
*/
@RequiredArgsConstructor
public class PayScoreServiceImpl implements PayScoreService {

private static final Gson GSON = new GsonBuilder().create();
private final WxPayService payService;

@Override
Expand Down Expand Up @@ -129,7 +139,7 @@ public WxPayScoreResult permissionsTerminateByOpenId(String openId, String reaso

@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());
Expand Down Expand Up @@ -247,10 +257,31 @@ public WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPay
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
Expand All @@ -266,4 +297,19 @@ public WxPayScoreResult decryptNotifyDataResource(PayScoreNotifyData data) throw
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());
}
}

0 comments on commit 7eb11c8

Please sign in to comment.