瀏覽代碼

:art: #2653 【微信支付】支付分相关接口增加服务商支持

高岩 3 年之前
父節點
當前提交
529f786814

+ 6 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsRequest.java

@@ -1,6 +1,8 @@
 package com.github.binarywang.wxpay.bean.ecommerce;
 
 import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
@@ -17,7 +19,9 @@ import java.util.List;
  * @author cloudX
  */
 @Data
+@Builder
 @NoArgsConstructor
+@AllArgsConstructor
 public class PartnerTransactionsRequest implements Serializable {
   private static final long serialVersionUID = -1550405819444680465L;
 
@@ -277,6 +281,7 @@ public class PartnerTransactionsRequest implements Serializable {
 
   @Data
   @NoArgsConstructor
+  @AllArgsConstructor
   public static class Amount implements Serializable {
     private static final long serialVersionUID = -4967636398225864273L;
 
@@ -312,6 +317,7 @@ public class PartnerTransactionsRequest implements Serializable {
 
   @Data
   @NoArgsConstructor
+  @AllArgsConstructor
   public static class Payer implements Serializable {
     private static final long serialVersionUID = -3946401119476159971L;
 

+ 4 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SignatureHeader.java

@@ -1,5 +1,7 @@
 package com.github.binarywang.wxpay.bean.ecommerce;
 
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
@@ -10,7 +12,9 @@ import java.io.Serializable;
  * 文档地址: https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/qian-ming-yan-zheng
  */
 @Data
+@Builder
 @NoArgsConstructor
+@AllArgsConstructor
 public class SignatureHeader implements Serializable {
   private static final long serialVersionUID = -6958015499416059949L;
   /**

+ 4 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/SignatureHeader.java

@@ -1,5 +1,7 @@
 package com.github.binarywang.wxpay.bean.notify;
 
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
@@ -10,7 +12,9 @@ import java.io.Serializable;
  * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay4_1.shtml
  */
 @Data
+@Builder
 @NoArgsConstructor
+@AllArgsConstructor
 public class SignatureHeader implements Serializable {
   private static final long serialVersionUID = -1L;
   /**

+ 39 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerPayScoreRequest.java

@@ -0,0 +1,39 @@
+package com.github.binarywang.wxpay.bean.payscore;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import lombok.experimental.SuperBuilder;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+/**
+ * @author hallkk
+ * @date 2022/05/18
+ */
+@Data
+@SuperBuilder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WxPartnerPayScoreRequest extends WxPayScoreRequest {
+  private static final long serialVersionUID = 6269843192878112955L;
+
+  public String toJson() {
+    return WxGsonBuilder.create().toJson(this);
+  }
+
+  @SerializedName("sub_appid")
+  private String subAppid;
+
+  @SerializedName("sub_mchid")
+  private String subMchid;
+
+  @SerializedName("out_apply_no")
+  private String outApplyNo;
+
+  @SerializedName("result_notify_url")
+  private String resultNotifyUrl;
+
+}

+ 42 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPartnerPayScoreResult.java

@@ -0,0 +1,42 @@
+package com.github.binarywang.wxpay.bean.payscore;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+/**
+ * @author hallkk
+ * @date 2022/05/18
+ */
+@Data
+@NoArgsConstructor
+public class WxPartnerPayScoreResult extends WxPayScoreResult {
+  private static final long serialVersionUID = 718267574622164410L;
+
+  public static WxPartnerPayScoreResult fromJson(String json) {
+    return WxGsonBuilder.create().fromJson(json, WxPartnerPayScoreResult.class);
+  }
+
+  @SerializedName("sub_appid")
+  private String subAppid;
+
+  @SerializedName("sub_mchid")
+  private String subMchid;
+
+  @SerializedName("sub_openid")
+  private String subOpenId;
+
+  @SerializedName("out_apply_no")
+  private String outApplyNo;
+
+  @SerializedName("result_notify_url")
+  private String resultNotifyUrl;
+
+  @SerializedName("apply_state")
+  private String applyState;
+
+  @SerializedName("reject_reason")
+  private String rejectReason;
+
+}

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

@@ -10,6 +10,7 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
+import lombok.experimental.SuperBuilder;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 
 /**
@@ -17,7 +18,7 @@ import me.chanjar.weixin.common.util.json.WxGsonBuilder;
  * @date 2020/5/12 16:36
  */
 @Data
-@Builder
+@SuperBuilder
 @NoArgsConstructor
 @AllArgsConstructor
 @Accessors(chain = true)
@@ -82,6 +83,6 @@ public class WxPayScoreRequest implements Serializable {
   @SerializedName("detail")
   private Detail detail;
   @SerializedName("authorization_code")
-  private  String authorizationCode;
+  private String authorizationCode;
 
 }

+ 8 - 5
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java

@@ -85,20 +85,23 @@ public class WxPayScoreResult implements Serializable {
   @SerializedName("payScoreSignInfo")
   private Map<String, String> payScoreSignInfo;
 
+  @SerializedName("openid")
+  private String openId;
+
   @SerializedName("apply_permissions_token")
-  private  String applyPermissionsToken;
+  private String applyPermissionsToken;
 
   @SerializedName("authorization_code")
-  private  String authorizationCode;
+  private String authorizationCode;
 
   @SerializedName("authorization_state")
-  private  String authorizationState;
+  private String authorizationState;
 
   @SerializedName("cancel_authorization_time")
-  private  String cancelAuthorizationTime;
+  private String cancelAuthorizationTime;
 
   @SerializedName("authorization_success_time")
-  private  String authorizationSuccessTime;
+  private String authorizationSuccessTime;
 
   @SerializedName("openid")
   private String openid;

+ 277 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PartnerPayScoreService.java

@@ -0,0 +1,277 @@
+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.WxPartnerPayScoreRequest;
+import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreResult;
+import com.github.binarywang.wxpay.exception.WxPayException;
+
+/**
+ * <pre>
+ *  服务商支付分相关服务类.
+ *   微信支付分是对个人的身份特质、支付行为、使用历史等情况的综合计算分值,旨在为用户提供更简单便捷的生活方式。
+ *   微信用户可以在具体应用场景中,开通微信支付分。开通后,用户可以在【微信—>钱包—>支付分】中查看分数和使用记录。
+ *   (即需在应用场景中使用过一次,钱包才会出现支付分入口)
+ *
+ * @author hallkk
+ * @date 2022/05/18
+ */
+public interface PartnerPayScoreService {
+
+
+  /**
+   * <pre>
+   * 支付分商户预授权API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter5_1.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/permissions
+   * </pre>
+   *
+   * @param request 请求对象
+   * @return WxPartnerPayScoreResult wx  partner payscore result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult permissions(WxPartnerPayScoreRequest request) throws WxPayException;
+
+
+  /**
+   * <pre>
+   * 支付分查询与用户授权记录(授权协议号)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter5_2.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/permissions/authorization-code/{authorization_code}
+   * </pre>
+   *
+   * @param serviceId
+   * @param subMchid
+   * @param authorizationCode
+   * @return WxPayScoreResult wx partner payscore result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult permissionsQueryByAuthorizationCode(String serviceId, String subMchid,
+                                                              String authorizationCode) throws WxPayException;
+
+
+  /**
+   * <pre>
+   * 解除用户授权关系(授权协议号)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter5_4.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/permissions/authorization-code/{authorization_code}/terminate
+   * </pre>
+   *
+   * @param serviceId
+   * @param subMchid
+   * @param authorizationCode
+   * @param reason
+   * @return WxPartnerPayScoreResult wx partner payscore result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult permissionsTerminateByAuthorizationCode(String serviceId, String subMchid,
+                                                                  String authorizationCode, String reason) throws WxPayException;
+
+
+  /**
+   * <pre>
+   * 支付分查询与用户授权记录(openid)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter5_3.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/permissions/search
+   * </pre>
+   *
+   * @param serviceId
+   * @param subMchid
+   * @param subAppid
+   * @param openId
+   * @param subOpenid
+   * @return WxPayScoreResult wx partner payscore result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult permissionsQueryByOpenId(String serviceId, String appId, String subMchid, String subAppid,
+                                                   String openId, String subOpenid) throws WxPayException;
+
+
+  /**
+   * <pre>
+   * 解除用户授权关系(openid)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter5_5.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/permissions/openid/{openid}/terminate
+   * </pre>
+   *
+   * @param serviceId
+   * @param subMchid
+   * @param subAppid
+   * @param openId
+   * @param subOpenid
+   * @param reason
+   * @return WxPayScoreResult wx partner payscore result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult permissionsTerminateByOpenId(String serviceId, String appId, String subMchid, String subAppid,
+                                                       String openId, String subOpenid, String reason) throws WxPayException;
+
+
+  /**
+   * <pre>
+   * 支付分创建订单API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter3_1.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/serviceorder
+   * </pre>
+   *
+   * @param request 请求对象
+   * @return WxPayScoreResult wx partner payscore result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult createServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 支付分查询订单API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter3_2.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/serviceorder
+   * </pre>
+   *
+   * @param serviceId
+   * @param subMchid
+   * @param outOrderNo the out order no
+   * @param queryId    the query id
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult queryServiceOrder(String serviceId, String subMchid,
+                                            String outOrderNo, String queryId) throws WxPayException;
+
+  /**
+   * <pre>
+   * 支付分取消订单API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter3_3.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/serviceorder/{out_order_no}/cancel
+   * </pre>
+   * <p>
+   *
+   * @param serviceId
+   * @param subMchid
+   * @param outOrderNo the out order no
+   * @param reason     the reason
+   * @return com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult cancelServiceOrder(String serviceId, String appId, String subMchid,
+                                             String outOrderNo, String reason) throws WxPayException;
+
+  /**
+   * <pre>
+   * 支付分修改订单金额API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter3_4.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/partner/serviceorder/{out_order_no}/modify
+   * </pre>
+   * <p>
+   *
+   * @param request the request
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult modifyServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 支付分完结订单API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter3_5.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payscore/partner/serviceorder/{out_order_no}/complete
+   * </pre>
+   *
+   * @param request the request
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  void completeServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 商户发起催收扣款API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter3_6.shtml
+   * 请求URL:https://api.mch.weixin.qq.com/v3/payscore/partner/serviceorder/{out_order_no}/pay
+   *
+   * </pre>
+   *
+   * @param serviceId
+   * @param subMchid
+   * @param outOrderNo the out order no
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult payServiceOrder(String serviceId, String appId, String subMchid, String outOrderNo) throws WxPayException;
+
+  /**
+   * <pre>
+   * 支付分订单收款API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter3_7.shtml
+   * 请求URL: https://api.mch.weixin.qq.com/v3/payscore/partner/serviceorder/{out_order_no}/sync
+   * </pre>
+   *
+   * @param request the request
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult syncServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 收付通子商户申请绑定支付分服务API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter9_1.shtml
+   * 请求URL: https://api.mch.weixin.qq.com/v3/payscore/partner/service-account-applications
+   * </pre>
+   *
+   * @param request the request
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult applyServiceAccount(WxPartnerPayScoreRequest request) throws WxPayException;
+
+  /**
+   * <pre>
+   * 查询收付通子商户服务绑定结果API.
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore_partner/chapter9_2.shtml
+   * 请求URL: https://api.mch.weixin.qq.com/v3/payscore/partner/service-account-applications/{out_apply_no}
+   * </pre>
+   *
+   * @param outApplyNo 商户申请绑定单号
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult queryServiceAccountState(String outApplyNo) 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>
+   * 支付分回调内容解析方法
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_2.shtml
+   * </pre>
+   *
+   * @param data the data
+   * @return the wx pay score result
+   */
+  PayScoreNotifyData parseNotifyData(String data, SignatureHeader header) throws WxPayException;
+
+  /**
+   * <pre>
+   * 支付分回调NotifyData解密resource
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_2.shtml
+   * </pre>
+   *
+   * @param data the data
+   * @return the wx pay score result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPartnerPayScoreResult decryptNotifyDataResource(PayScoreNotifyData data) throws WxPayException;
+
+}

+ 341 - 0
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PartnerPayScoreServiceImpl.java

@@ -0,0 +1,341 @@
+package com.github.binarywang.wxpay.service.impl;
+
+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.WxPartnerPayScoreRequest;
+import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreResult;
+import com.github.binarywang.wxpay.config.WxPayConfig;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.PartnerPayScoreService;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.v3.util.AesUtils;
+import com.google.common.collect.Maps;
+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.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @author hallkk
+ * @date 2022/05/18
+ */
+@RequiredArgsConstructor
+public class PartnerPayScoreServiceImpl implements PartnerPayScoreService {
+  private static final Gson GSON = new GsonBuilder().create();
+  private final WxPayService payService;
+
+  @Override
+  public WxPartnerPayScoreResult permissions(WxPartnerPayScoreRequest request) throws WxPayException {
+    String url = this.payService.getPayBaseUrl() + "/v3/payscore/partner/permissions";
+    request.setAppid(request.getAppid());
+    request.setServiceId(request.getServiceId());
+    WxPayConfig config = this.payService.getConfig();
+    String permissionNotifyUrl = config.getPayScorePermissionNotifyUrl();
+    if (StringUtils.isBlank(permissionNotifyUrl)) {
+      throw new WxPayException("授权回调地址未配置");
+    }
+    String authorizationCode = request.getAuthorizationCode();
+    if (StringUtils.isBlank(authorizationCode)) {
+      throw new WxPayException("authorizationCode不允许为空");
+    }
+    request.setNotifyUrl(permissionNotifyUrl);
+    String result = this.payService.postV3(url, request.toJson());
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult permissionsQueryByAuthorizationCode(String serviceId, String subMchid, String authorizationCode)
+    throws WxPayException {
+    if (StringUtils.isBlank(authorizationCode)) {
+      throw new WxPayException("authorizationCode不允许为空");
+    }
+    String url = String.format("%s/v3/payscore/partner/permissions/authorization-code/%s", this.payService.getPayBaseUrl(), authorizationCode);
+    URIBuilder uriBuilder;
+    try {
+      uriBuilder = new URIBuilder(url);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+
+    uriBuilder.setParameter("service_id", serviceId);
+    uriBuilder.setParameter("sub_mchid", subMchid);
+    try {
+      String result = payService.getV3(uriBuilder.build().toString());
+      return WxPartnerPayScoreResult.fromJson(result);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+  }
+
+  @Override
+  public WxPartnerPayScoreResult permissionsTerminateByAuthorizationCode(String serviceId, String subMchid,
+                                                                         String authorizationCode, String reason) throws WxPayException {
+    if (StringUtils.isBlank(authorizationCode)) {
+      throw new WxPayException("authorizationCode不允许为空");
+    }
+    String url = String.format(
+      "%s/v3/payscore/partner/permissions/authorization-code/%s/terminate",
+      this.payService.getPayBaseUrl(),
+      authorizationCode
+    );
+    Map<String, Object> map = new HashMap<>(4);
+    map.put("service_id", serviceId);
+    map.put("sub_mchid", subMchid);
+    map.put("reason", reason);
+    String result = payService.postV3(url, WxGsonBuilder.create().toJson(map));
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult permissionsQueryByOpenId(String serviceId, String appId, String subMchid,
+                                                          String subAppid, String openId, String subOpenid) throws WxPayException {
+    if (StringUtils.isAllEmpty(openId, subOpenid) || !StringUtils.isAnyEmpty(openId, subOpenid)) {
+      throw new WxPayException("open_id,sub_openid不允许都填写或都不填写");
+    }
+    if (StringUtils.isBlank(subMchid)) {
+      throw new WxPayException("sub_mchid不允许都为空");
+    }
+    String url = String.format("%s/v3/payscore/partner/permissions/openid/%s", this.payService.getPayBaseUrl(), openId);
+    URIBuilder uriBuilder;
+    try {
+      uriBuilder = new URIBuilder(url);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+
+    uriBuilder.setParameter("appid", appId);
+    uriBuilder.setParameter("service_id", serviceId);
+    uriBuilder.setParameter("sub_mchid", subMchid);
+    uriBuilder.setParameter("sub_appid", subAppid);
+    uriBuilder.setParameter("openid", openId);
+    uriBuilder.setParameter("sub_openid", subOpenid);
+
+    if (StringUtils.isNotEmpty(openId)) {
+      uriBuilder.setParameter("openid", openId);
+    }
+    if (StringUtils.isNotEmpty(subOpenid)) {
+      uriBuilder.setParameter("sub_openid", subOpenid);
+    }
+
+    try {
+      String result = payService.getV3(uriBuilder.build().toString());
+      return WxPartnerPayScoreResult.fromJson(result);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+  }
+
+  @Override
+  public WxPartnerPayScoreResult permissionsTerminateByOpenId(String serviceId, String appId, String subMchid, String subAppid, String openId, String subOpenid, String reason) throws WxPayException {
+    if (StringUtils.isAllEmpty(openId, subOpenid) || !StringUtils.isAnyEmpty(openId, subOpenid)) {
+      throw new WxPayException("open_id,sub_openid不允许都填写或都不填写");
+    }
+    String url = String.format("%s/v3/payscore/partner/permissions/openid/%s/terminate", this.payService.getPayBaseUrl(), openId);
+    Map<String, Object> map = new HashMap<>(4);
+    map.put("appid", appId);
+    map.put("sub_appid", subAppid);
+    map.put("service_id", serviceId);
+    if (StringUtils.isNotEmpty(openId)) {
+      map.put("openid", openId);
+    }
+    if (StringUtils.isNotEmpty(subOpenid)) {
+      map.put("sub_openid", subOpenid);
+    }
+    map.put("sub_mchid", subMchid);
+    map.put("reason", reason);
+    String result = payService.postV3(url, WxGsonBuilder.create().toJson(map));
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult createServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException {
+    String url = this.payService.getPayBaseUrl() + "/v3/payscore/partner/serviceorder";
+
+    WxPayConfig config = this.payService.getConfig();
+    request.setNotifyUrl(config.getPayScoreNotifyUrl());
+    String result = this.payService.postV3(url, request.toJson());
+
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult queryServiceOrder(String serviceId, String subMchid, String outOrderNo,
+                                                   String queryId) throws WxPayException {
+    String url = this.payService.getPayBaseUrl() + "/v3/payscore/partner/serviceorder";
+
+    URIBuilder uriBuilder;
+    try {
+      uriBuilder = new URIBuilder(url);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+    uriBuilder.setParameter("service_id", serviceId);
+    uriBuilder.setParameter("sub_mchid", subMchid);
+    if (StringUtils.isAllEmpty(outOrderNo, queryId) || !StringUtils.isAnyEmpty(outOrderNo, queryId)) {
+      throw new WxPayException("out_order_no,query_id不允许都填写或都不填写");
+    }
+    if (StringUtils.isNotEmpty(outOrderNo)) {
+      uriBuilder.setParameter("out_order_no", outOrderNo);
+    }
+    if (StringUtils.isNotEmpty(queryId)) {
+      uriBuilder.setParameter("query_id", queryId);
+    }
+
+    try {
+      String result = payService.getV3(uriBuilder.build().toString());
+      return WxPartnerPayScoreResult.fromJson(result);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+  }
+
+  @Override
+  public WxPartnerPayScoreResult cancelServiceOrder(String serviceId, String appId, String subMchid, String outOrderNo, String reason) throws WxPayException {
+    String url = String.format("%s/v3/payscore/partner/serviceorder/%s/cancel", this.payService.getPayBaseUrl(), outOrderNo);
+    Map<String, Object> map = new HashMap<>(4);
+    map.put("appid", appId);
+    map.put("service_id", serviceId);
+    map.put("sub_mchid", subMchid);
+    map.put("reason", reason);
+    if (StringUtils.isAnyEmpty(appId, serviceId, subMchid, reason)) {
+      throw new WxPayException("appid, service_id, sub_mchid, reason都不能为空");
+    }
+    String result = payService.postV3(url, WxGsonBuilder.create().toJson(map));
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult modifyServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException {
+    String outOrderNo = request.getOutOrderNo();
+    String url = String.format("%s/v3/payscore/partner/serviceorder/%s/modify", this.payService.getPayBaseUrl(), outOrderNo);
+    request.setAppid(this.payService.getConfig().getAppId());
+    request.setOutOrderNo(null);
+    String result = payService.postV3(url, request.toJson());
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public void completeServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException {
+    String outOrderNo = request.getOutOrderNo();
+    String url = String.format("%s/v3/payscore/partner/serviceorder/%s/complete", this.payService.getPayBaseUrl(), outOrderNo);
+    request.setAppid(request.getAppid());
+    request.setServiceId(request.getServiceId());
+    request.setOutOrderNo(null);
+    request.setSubMchid(request.getSubMchid());
+    this.payService.postV3(url, request.toJson());
+  }
+
+  @Override
+  public WxPartnerPayScoreResult payServiceOrder(String serviceId, String appId, String subMchid, String outOrderNo) throws WxPayException {
+    String url = String.format("%s/v3/payscore/partner/serviceorder/%s/pay", this.payService.getPayBaseUrl(), outOrderNo);
+    Map<String, Object> map = new HashMap<>(3);
+    map.put("appid", appId);
+    map.put("service_id", serviceId);
+    map.put("sub_mchid", subMchid);
+    if (StringUtils.isAnyEmpty(appId, serviceId, subMchid)) {
+      throw new WxPayException("appid, service_id, sub_mchid都不能为空");
+    }
+    String result = payService.postV3(url, WxGsonBuilder.create().toJson(map));
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult syncServiceOrder(WxPartnerPayScoreRequest request) throws WxPayException {
+    String outOrderNo = request.getOutOrderNo();
+    String url = String.format("%s/v3/payscore/partner/serviceorder/%s/sync", this.payService.getPayBaseUrl(), outOrderNo);
+    request.setAppid(this.payService.getConfig().getAppId());
+    request.setOutOrderNo(null);
+    String result = payService.postV3(url, request.toJson());
+    return WxPartnerPayScoreResult.fromJson(result);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult applyServiceAccount(WxPartnerPayScoreRequest request) throws WxPayException {
+    String url = String.format("%s/v3/payscore/partner/service-account-applications", this.payService.getPayBaseUrl());
+    Map<String, String> params = Maps.newHashMap();
+    params.put("service_id", request.getServiceId());
+    params.put("appid", request.getAppid());
+    params.put("sub_mchid", request.getSubMchid());
+    params.put("sub_appid", request.getSubAppid());
+    params.put("out_apply_no", request.getOutApplyNo());
+    params.put("result_notify_url", request.getResultNotifyUrl());
+
+    String result = payService.postV3(url, WxGsonBuilder.create().toJson(params));
+    return WxPartnerPayScoreResult.fromJson(result);
+
+  }
+
+  @Override
+  public WxPartnerPayScoreResult queryServiceAccountState(String outApplyNo) throws WxPayException {
+    String url = String.format("%s/v3/payscore/partner/service-account-applications/%s", this.payService.getPayBaseUrl(), outApplyNo);
+    String result = payService.getV3(url);
+    return WxPartnerPayScoreResult.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, SignatureHeader header) throws WxPayException {
+    if (Objects.nonNull(header) && !this.verifyNotifySign(header, data)) {
+      throw new WxPayException("非法请求,头部信息验证失败");
+    }
+    return GSON.fromJson(data, PayScoreNotifyData.class);
+  }
+
+  @Override
+  public WxPartnerPayScoreResult decryptNotifyDataResource(PayScoreNotifyData data) throws WxPayException {
+    PayScoreNotifyData.Resource resource = data.getResource();
+    String cipherText = resource.getCipherText();
+    String associatedData = resource.getAssociatedData();
+    String nonce = resource.getNonce();
+    String apiV3Key = this.payService.getConfig().getApiV3Key();
+    try {
+      return WxPartnerPayScoreResult.fromJson(AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key));
+    } catch (GeneralSecurityException | IOException e) {
+      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 this.payService.getConfig().getVerifier().verify(
+      header.getSerialNo(),
+      beforeSign.getBytes(StandardCharsets.UTF_8),
+      header.getSigned()
+    );
+  }
+}