diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java index fef0b5ab8b..3486c14012 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java @@ -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; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/UserAuthorizationStatusNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/UserAuthorizationStatusNotifyResult.java new file mode 100644 index 0000000000..2a97d29738 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/UserAuthorizationStatusNotifyResult.java @@ -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; + +/** + * 授权/解除授权服务回调通知结果 + *
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml + *+ */ +@Data +@NoArgsConstructor +public class UserAuthorizationStatusNotifyResult implements Serializable { + + /** + * 源数据 + */ + private PayScoreNotifyData rawData; + + /** + *
+ * 字段名:公众账号ID + * 变量名:appid + * 是否必填:是 + * 类型:string[1,32] + * 描述: + * 调用授权服务接口提交的公众账号ID。 + * 示例值:wxd678efh567hg6787 + *+ */ + @SerializedName(value = "appid") + private String appid; + + /** + *
+ * 字段名:商户号 + * 变量名:mchid + * 是否必填:是 + * 类型:string[1,32] + * 描述: + * 调用授权服务接口提交的商户号。 + * 示例值:1230000109 + *+ */ + @SerializedName(value = "mchid") + private String mchid; + + /** + *
+ * 字段名:商户签约单号 + * 变量名:out_request_no + * 是否必填:否 + * 类型: string[1,64] + * 描述: + * 调用授权服务接口提交的商户请求唯一标识(新签约的用户,且在授权签约中上传了该字段,则在解约授权回调通知中有返回)。 + * 示例值:1234323JKHDFE1243252 + *+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+ * 字段名:服务ID + * 变量名:service_id + * 是否必填:是 + * 类型: string[1,32] + * 描述: + * 调用授权服务接口提交的服务ID。 + * 示例值:1234323JKHDFE1243252 + *+ */ + @SerializedName(value = "service_id") + private String serviceId; + + /** + *
+ * 字段名:用户标识 + * 变量名:openid + * 是否必填:是 + * 类型: string[1,128] + * 描述: + * 微信用户在商户对应appid下的唯一标识。 + * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o + *+ */ + @SerializedName(value = "openid") + private String openid; + + /** + *
+ * 字段名:回调状态 + * 变量名:user_service_status + * 是否必填:否 + * 类型: string[1,32] + * 描述: + * 1、USER_OPEN_SERVICE:授权成功 + * 2、USER_CLOSE_SERVICE:解除授权成功 + * 示例值:USER_OPEN_SERVICE + *+ */ + @SerializedName(value = "user_service_status") + private String userServiceStatus; + + /** + *
+ * 字段名:服务授权/解除授权时间 + * 变量名:openorclose_time + * 是否必填:否 + * 类型: string[1,32] + * 描述: + * 服务授权/解除授权成功时间。 + * 示例值:20180225112233 + *+ */ + @SerializedName(value = "openorclose_time") + private String openOrCloseTime; + + /** + *
+ * 字段名:授权协议号 + * 变量名:authorization_code + * 是否必填:否 + * 类型: string[1,32] + * 描述: + * 授权协议号,预授权时返回,非预授权不返回 + * 示例值:1275342195190894594 + *+ */ + @SerializedName(value = "authorization_code") + private String authorizationCode; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java index 6091df26f9..0f4b92a7b7 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java @@ -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.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 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
+ * 授权/解除授权服务回调数据处理 + * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml + *+ * + * @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; /** *
@@ -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; /** *diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java index 221128c297..2f4dd76964 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java @@ -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.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 @@ */ @RequiredArgsConstructor public class PayScoreServiceImpl implements PayScoreService { + + private static final Gson GSON = new GsonBuilder().create(); private final WxPayService payService; @Override @@ -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()); @@ -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 @@ -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()); + } }