🆕 #3162 【微信支付】接入服务商微信支付分签约计划相关接口功能

This commit is contained in:
尼禄·saber·潍納斯 2023-11-13 11:04:50 +08:00 committed by Binary Wang
parent f2abe7429d
commit f1d790c710
16 changed files with 1240 additions and 0 deletions

View File

@ -0,0 +1,94 @@
package com.github.binarywang.wxpay.bean.payscore;
import com.github.binarywang.wxpay.bean.payscore.enums.SignPlanServiceOrderPlanDetailStateEnum;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author UltramanNoa
* @className PartnerUserSignPlanDetail
* @description 签约计划明细列表
* @createTime 2023/11/3 17:19
**/
@Data
@NoArgsConstructor
public class PartnerUserSignPlanDetail implements Serializable {
private static final long serialVersionUID = 2089297485318293622L;
/**
* 计划明细序号
*/
@SerializedName("plan_detail_no")
private Integer planDetailNo;
/**
* 计划明细原支付金额单位分
*/
@SerializedName("original_price")
private Integer originalPrice;
/**
* 计划明细优惠说明
*/
@SerializedName("plan_discount_description")
private String planDiscountDescription;
/**
* 计划明细实际支付金额单位分
*/
@SerializedName("actual_price")
private Integer actualPrice;
/**
* 计划明细状态
*
* @see SignPlanServiceOrderPlanDetailStateEnum
*/
@SerializedName("plan_detail_state")
private String planDetailState;
/**
* 计划明细对应的支付分服务单号
*/
@SerializedName("order_id")
private String orderId;
/**
* 商户侧计划明细使用订单号
*/
@SerializedName("merchant_plan_detail_no")
private String merchantPlanDetailNo;
/**
* 计划详情名称
*/
@SerializedName("plan_detail_name")
private Integer planDetailName;
/**
* 计划明细对应订单实际支付金额单位分
*/
@SerializedName("actual_pay_price")
private Integer actualPayPrice;
/**
* 详情使用时间
*/
@SerializedName("use_time")
private String useTime;
/**
* 详情完成时间
*/
@SerializedName("complete_time")
private String completeTime;
/**
* 详情取消时间
*/
@SerializedName("cancel_time")
private String cancelTime;
}

View File

@ -0,0 +1,165 @@
package com.github.binarywang.wxpay.bean.payscore;
import com.github.binarywang.wxpay.bean.payscore.enums.SignPlanServiceOrderStateEnum;
import com.github.binarywang.wxpay.bean.payscore.enums.UserSignPlanCancelSignTypeEnum;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import java.io.Serializable;
import java.util.List;
/**
* @author UltramanNoa
* @className PartnerUserSignPlanEntity
* @description 用户的签约计划
* @createTime 2023/11/3 16:05
**/
@Data
@NoArgsConstructor
public class PartnerUserSignPlanEntity implements Serializable {
private static final long serialVersionUID = -662901613603698430L;
public static PartnerUserSignPlanEntity fromJson(String json) {
return WxGsonBuilder.create().fromJson(json, PartnerUserSignPlanEntity.class);
}
/**
* 待创建服务订单对应的用户的签约计划
*/
@SerializedName("sign_plan_id")
private String signPlanId;
@SerializedName("openid")
private String openid;
/**
* <pre>
* 字段名二级商户用户标识
* 变量名sub_openid
* 是否必填
* 类型string128
* 描述
* 用户在二级商户appid下的唯一标识
* 示例值oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
* </pre>
*/
@SerializedName(value = "sub_openid")
private String subOpenid;
@SerializedName("service_id")
private String serviceId;
@SerializedName("mchid")
private String mchid;
/**
* 子商户商户号
*/
@SerializedName("sub_mchid")
private String subMchid;
@SerializedName("appid")
private String appid;
/**
* 子商户AppID
*/
@SerializedName("sub_appid")
private String subAppid;
/**
* 商户签约计划单号
*/
@SerializedName("merchant_sign_plan_no")
private String merchantSignPlanNo;
/**
* 商户回调地址
*/
@SerializedName("merchant_callback_url")
private String merchantCallbackUrl;
/**
* 支付分计划ID
*/
@SerializedName("plan_id")
private String planId;
/**
* 目前用户进行到的计划详情序号
*/
@SerializedName("going_detail_no")
private Integer goingDetailNo;
/**
* 计划签约状态
*
* @see SignPlanServiceOrderStateEnum
*/
@SerializedName("sign_state")
private String signState;
/**
* 签约计划取消时间
*/
@SerializedName("cancel_sign_time")
private String cancelSignTime;
/**
* 签约计划取消类型
*
* @see UserSignPlanCancelSignTypeEnum
*/
@SerializedName("cancel_sign_type")
private String cancelSignType;
/**
* 签约计划取消原因
*/
@SerializedName("cancel_reason")
private String cancelReason;
/**
* 签约计划的名称
*/
@SerializedName("plan_name")
private String planName;
/**
* 签约计划的过期时间
*/
@SerializedName("plan_over_time")
private String planOverTime;
/**
* 签约计划原总金额(单位分)
*/
@SerializedName("total_origin_price")
private Integer totalOriginPrice;
/**
* 签约计划扣费次数
*/
@SerializedName("deduction_quantity")
private Integer deductionQuantity;
/**
* 签约计划实际总金额单位分
*/
@SerializedName("total_actual_price")
private Integer totalActualPrice;
@SerializedName("signed_detail_list")
private List<PartnerUserSignPlanDetail> signedDetailList;
/**
* 签约时间
*/
@SerializedName("sign_time")
private String signTime;
}

View File

@ -35,6 +35,8 @@ public class PayScoreNotifyData implements Serializable {
* <p>2解除授权成功通知的类型为PAYSCORE.USER_CLOSE_SERVICE</p>
* <p>3用户确认成功通知的类型为PAYSCORE.USER_CONFIRM</p>
* <p>4支付成功通知的类型为PAYSCORE.USER_PAID</p>
* <p>5取消签约成功通知类型为PAYSCORE.USER_CANCEL_SIGN_PLAN</p>
* <p>6签约计划成功通知类型为PAYSCORE</p>
*/
@SerializedName("event_type")
private String eventType;
@ -85,5 +87,11 @@ public class PayScoreNotifyData implements Serializable {
*/
@SerializedName("associated_data")
private String associatedData;
/**
* 原始回调类型支付分的原始回调类型为payscore
*/
@SerializedName("original_type")
private String originalType;
}
}

View File

@ -0,0 +1,49 @@
package com.github.binarywang.wxpay.bean.payscore;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author UltramanNoa
* @className PayScorePlanDetail
* @description 支付分计划明细列表
* @createTime 2023/11/3 11:22
**/
@Data
@NoArgsConstructor
public class PayScorePlanDetail implements Serializable {
private static final long serialVersionUID = 999251141141181820L;
/**
* 计划明细原支付金额(单位分)
*/
@SerializedName("original_price")
private Integer originalPrice;
/**
* 计划明细优惠说明
*/
@SerializedName("plan_discount_description")
private String planDiscountDescription;
/**
* 计划明细实际支付金额(单位分)
*/
@SerializedName("actual_price")
private String actualPrice;
/**
* 计划明细名称
*/
@SerializedName("plan_detail_name")
private String planDetailName;
/**
* 计划明细序号(返回参数)
*/
@SerializedName("plan_detail_no")
private Integer planDetailNo;
}

View File

@ -0,0 +1,32 @@
package com.github.binarywang.wxpay.bean.payscore;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author UltramanNoa
* @className UserSignPlanDetailMerchatNo
* @description 签约计划对应的计划详情列表的商户侧单号信息
* @createTime 2023/11/3 15:51
**/
@Data
@NoArgsConstructor
public class UserSignPlanDetailMerchatNo implements Serializable {
private static final long serialVersionUID = 2668791598158720023L;
/**
* 计划明细序号
*/
@SerializedName("plan_detail_no")
private Integer planDetailNo;
/**
* 商户侧计划明细使用订单号
*/
@SerializedName("merchant_plan_detail_no")
private String merchantPlanDetailNo;
}

View File

@ -0,0 +1,130 @@
package com.github.binarywang.wxpay.bean.payscore;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import java.util.List;
/**
* @author UltramanNoa
* @className WxPartnerPayScoreSignPlanRequest
* @description 支付分计划请求参数
* @createTime 2023/11/3 09:54
**/
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = true)
public class WxPartnerPayScoreSignPlanRequest extends WxPayScoreRequest {
private static final long serialVersionUID = 6269843192878112955L;
public String toJson() {
return WxGsonBuilder.create().toJson(this);
}
/**
* 子商户appid
*/
@SerializedName("sub_appid")
private String subAppid;
/**
* 子商户mchid
*/
@SerializedName("sub_mchid")
private String subMchid;
/**
* 子商户公众号下的用户标识
*/
@SerializedName("sub_openid")
private String subOpenid;
/**
* 支付分计划名称
*/
@SerializedName("plan_name")
private String planName;
/**
* 支付分计划有效期(单位天)
*/
@SerializedName("plan_duration")
private Integer planDuration;
/**
* 支付分计划扣费次数
*/
@SerializedName("deduction_quantity")
private Integer deductionQuantity;
/**
* 支付分计划原总金额(单位分)
*/
@SerializedName("total_original_price")
private Integer totalOriginalPrice;
/**
* 支付分计划实际扣费总金额(单位分)
*/
@SerializedName("total_actual_price")
private Integer totalActualPrice;
/**
* 支付分计划明细列表
*/
@SerializedName("plan_detail_list")
private List<PayScorePlanDetail> planDetailList;
/**
* 商户侧计划号
*/
@SerializedName("merchant_plan_no")
private String merchantPlanNo;
/**
* 待创建服务订单对应的用户的签约计划
*/
@SerializedName("sign_plan_id")
private String signPlanId;
/**
* 待创建服务订单对应的用户的签约计划详情序号
*/
@SerializedName("plan_detail_no")
private Integer planDetailNo;
/**
* 商户侧订单号
*/
@SerializedName("out_trade_no")
private String outTradeNo;
/**
* 支付分计划ID
*/
@SerializedName("plan_id")
private String planId;
/**
* 商户签约计划单号
*/
@SerializedName("merchant_sign_plan_no")
private String merchantSignPlanNo;
/**
* 签约计划对应的计划详情列表的商户侧单号信息
*/
@SerializedName("sign_plan_detail")
private List<UserSignPlanDetailMerchatNo> signPlanDetail;
}

View File

@ -0,0 +1,104 @@
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;
import java.util.List;
/**
* @author UltramanNoa
* @className WxPartnerPayScoreSignPlanResult
* @description 支付分计划响应参数
* @createTime 2023/11/3 10:11
**/
@Data
@NoArgsConstructor
public class WxPartnerPayScoreSignPlanResult extends WxPayScoreResult {
private static final long serialVersionUID = 4048529978029913621L;
public static WxPartnerPayScoreSignPlanResult fromJson(String json) {
return WxGsonBuilder.create().fromJson(json, WxPartnerPayScoreSignPlanResult.class);
}
/**
* 子商户AppID
*/
@SerializedName("sub_appid")
private String subAppid;
/**
* 子商户商户号
*/
@SerializedName("sub_mchid")
private String subMchid;
/**
* 支付分计划ID
*/
@SerializedName("plan_id")
private String planId;
/**
* 商户侧计划号
*/
@SerializedName("merchant_plan_no")
private String merchantPlanNo;
/**
* 支付分计划名称
*/
@SerializedName("plan_name")
private String planName;
/**
* 支付分计划有效期(单位天)
*/
@SerializedName("plan_duration")
private String planDuration;
/**
* 支付分计划状态
*
* @see
*/
@SerializedName("plan_state")
private String planState;
/**
* 支付分计划原总金额(单位分)
*/
@SerializedName("total_original_price")
private String totalOriginalPrice;
/**
* 支付分计划扣费次数
*/
@SerializedName("deduction_quantity")
private String deductionQuantity;
/**
* 支付分计划实际扣费总金额(单位分)
*/
@SerializedName("total_actual_price")
private Integer totalActualPrice;
/**
* 支付分计划明细列表
*/
@SerializedName("plan_detail_list")
private List<PayScorePlanDetail> planDetailList;
/**
* 终止方商户号
*/
@SerializedName("stop_mchid")
private String stopMchid;
/**
* 终止时间
*/
@SerializedName("stop_time")
private String stopTime;
}

View File

@ -0,0 +1,31 @@
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;
import java.io.Serializable;
/**
* @author UltramanNoa
* @className WxPartnerUserSignPlanResult
* @description 微信支付分用户签约计划返回
* @createTime 2023/11/3 16:38
**/
@Data
@NoArgsConstructor
public class WxPartnerPayScoreUserSignPlanResult implements Serializable {
private static final long serialVersionUID = 4148075707018175845L;
public static WxPartnerPayScoreUserSignPlanResult fromJson(String json) {
return WxGsonBuilder.create().fromJson(json, WxPartnerPayScoreUserSignPlanResult.class);
}
@SerializedName("sign_plan")
private PartnerUserSignPlanEntity signPlan;
@SerializedName("package")
private String pack;
}

View File

@ -0,0 +1,30 @@
package com.github.binarywang.wxpay.bean.payscore.enums;
/**
* @author UltramanNoa
* @className SignPlanServiceOrderPlanDetailStateEnum
* @description 计划明细状态
* @createTime 2023/11/3 17:43
**/
public enum SignPlanServiceOrderPlanDetailStateEnum {
/**
* 本计划详情还未使用
*/
NOT_USED,
/**
* 本计划详情使用中已有对应的服务订单
*/
USING,
/**
* 本计划详情已使用对应的服务订单已完成支付
*/
USED,
/**
* 本计划详情已取消使用对应的服务订单已取消
*/
SIGN_PLAN_DETAIL_CANCEL,
;
}

View File

@ -0,0 +1,36 @@
package com.github.binarywang.wxpay.bean.payscore.enums;
/**
* @author UltramanNoa
* @className SignPlanServiceOrderStateEnum
* @description 签约计划服务订单状态
* @createTime 2023/11/3 15:28
**/
public enum SignPlanServiceOrderStateEnum {
/**
* 商户已创建服务订单
*/
CREATED,
/**
* 服务订单进行中
*/
DOING,
/**
* 服务订单完成
*/
DONE,
/**
* 商户取消服务订单
*/
REVOKED,
/**
* 服务订单已失效
*/
EXPIRED,
;
}

View File

@ -0,0 +1,30 @@
package com.github.binarywang.wxpay.bean.payscore.enums;
/**
* @author UltramanNoa
* @className UserSignPlanCancelSignTypeEnum
* @description 签约计划取消类型
* @createTime 2023/11/3 17:15
**/
public enum UserSignPlanCancelSignTypeEnum {
/**
* 用户已签约协议未取消
*/
NOT_CANCEL,
/**
* 用户取消已签约的协议
*/
USER,
/**
* 商户取消已签约的协议
*/
MERCHANT,
/**
* 用户解除服务授权时取消服务下的已签约协议
*/
REVOKE_SERVICE,
;
}

View File

@ -0,0 +1,138 @@
package com.github.binarywang.wxpay.service;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.payscore.PartnerUserSignPlanEntity;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreSignPlanRequest;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreSignPlanResult;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreUserSignPlanResult;
import com.github.binarywang.wxpay.exception.WxPayException;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* @author UltramanNoa
* @className PartnerPayScoreSignPlanService
* @description 微信支付分签约计划接口
* @createTime 2023/11/3 09:16
*
* <pre>
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/create-partner-pay-score-plan.html">微信支付分签约计划</a>
* <br>
* 文档更新时间2023.10.13
* <br>
* 微信支付分签约计划是不同模式的支付分接口随着国家大力推广教培行业先享后付政策,微信支付也紧跟政策于2023.07.25上线第一版签约计划接口以适用教培行业先享后付于2023.10.13文档推至官网文档中心
* <br>
* 免确认/需确认 用服务商创单接口 {@link PartnerPayScoreService} 需要用户授权
* <br>
* 签约计划用单独创单接口 {@link PartnerPayScoreSignPlanService} 不需要用户授权
* </pre>
**/
public interface PartnerPayScoreSignPlanService {
/**
* <p>description创建支付分计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 11:58 </p>
* <p>version v.1.0 </p>
*
* @param request {@link WxPartnerPayScoreSignPlanRequest}
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/create-partner-pay-score-plan.html">创建支付分计划</a>
**/
WxPartnerPayScoreSignPlanResult createPlans(WxPartnerPayScoreSignPlanRequest request) throws WxPayException;
/**
* <p>description 查询支付分计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 14:03 </p>
* <p>version v.1.0 </p>
*
* @param merchantPlanNo 路径参数支付分计划商户侧单号
* @param subMchid 子商户号
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/query-partner-pay-score-plan.html">查询支付分计划</a>
**/
WxPartnerPayScoreSignPlanResult queryPlans(@NonNull String merchantPlanNo, @NonNull String subMchid) throws WxPayException;
/**
* <p>description 停止支付分计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 14:24 </p>
* <p>version v.1.0 </p>
*
* @param merchantPlanNo 路径参数支付分计划商户侧单号
* @param subMchid 子商户号
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/stop-partner-pay-score-plan.html">停止支付分计划</a>
**/
WxPartnerPayScoreSignPlanResult stopPlans(@NonNull String merchantPlanNo, @NonNull String subMchid) throws WxPayException;
/**
* <p>description 创建用户的签约计划详情对应的服务订单 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 14:53 </p>
* <p>version v.1.0 </p>
*
* @param request {@link WxPartnerPayScoreSignPlanRequest}
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-service-order/create-partner-service-order.html">创建用户的签约计划详情对应的服务订单</a>
**/
WxPartnerPayScoreSignPlanResult signPlanServiceOrder(WxPartnerPayScoreSignPlanRequest request) throws WxPayException;
/**
* <p>description 创建用户的签约计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 17:48 </p>
* <p>version v.1.0 </p>
*
* @param request {@link WxPartnerPayScoreSignPlanRequest}
*
* @return {@link WxPartnerPayScoreUserSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-user-sign-plan/create-partner-user-sign-plan.html">创建用户的签约计划</a>
**/
WxPartnerPayScoreUserSignPlanResult createUserSignPlans(WxPartnerPayScoreSignPlanRequest request) throws WxPayException;
/**
* <p>description 查询用户的签约计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 18:01 </p>
* <p>version v.1.0 </p>
*
* @param merchantSignPlanNo 路径参数 商户签约计划号
* @param subMchid 子商户号
*
* @return {@link PartnerUserSignPlanEntity}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-user-sign-plan/query-partner-user-sign-plan.html">查询用户的签约计划</a>
**/
PartnerUserSignPlanEntity queryUserSignPlans(@NonNull String merchantSignPlanNo, @NonNull String subMchid) throws WxPayException;
/**
* <p>description 取消用户的签约计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 18:01 </p>
* <p>version v.1.0 </p>
*
* @param merchantSignPlanNo 路径参数 商户签约计划号 subMchid 子商户号
* @param subMchid 子商户号
* @param stopReason 停止签约计划原因
*
* @return {@link PartnerUserSignPlanEntity}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-user-sign-plan/stop-partner-user-sign-plan.html">取消用户的签约计划</a>
**/
PartnerUserSignPlanEntity stopUserSignPlans(@NonNull String merchantSignPlanNo, @NonNull String subMchid, @NonNull String stopReason) throws WxPayException;
/**
* <p>description 回调通知校验解密 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/6 10:27 </p>
* <p>version v.1.0 </p>
* @param
* @param notifyData
* @param header
* @return {@link PartnerUserSignPlanEntity}
**/
PartnerUserSignPlanEntity parseSignPlanNotifyResult(String notifyData, SignatureHeader header) throws WxPayException;
}

View File

@ -11,6 +11,7 @@ import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import java.io.File;
import java.io.InputStream;
import java.util.Date;
@ -1529,4 +1530,9 @@ public interface WxPayService {
*/
PartnerPayScoreService getPartnerPayScoreService();
/**
* 获取服务商直股份签约计划服务类
* @return the partner pay score sign plan service
*/
PartnerPayScoreSignPlanService getPartnerPayScoreSignPlanService();
}

View File

@ -119,6 +119,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
@Getter
private final PartnerPayScoreService partnerPayScoreService = new PartnerPayScoreServiceImpl(this);
@Getter
private final PartnerPayScoreSignPlanService partnerPayScoreSignPlanService=new PartnerPayScoreSignPlanServiceImpl(this);
@Getter
private final MerchantTransferService merchantTransferService = new MerchantTransferServiceImpl(this);

View File

@ -0,0 +1,308 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
import com.github.binarywang.wxpay.bean.payscore.*;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.PartnerPayScoreService;
import com.github.binarywang.wxpay.service.PartnerPayScoreSignPlanService;
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 org.checkerframework.checker.nullness.qual.NonNull;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Map;
import java.util.Objects;
/**
* @author UltramanNoa
* @className PartnerPayScoreSignPlanServiceImpl
* @description 微信支付分签约计划接口实现
* @createTime 2023/11/3 09:23
*
* <pre>
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/create-partner-pay-score-plan.html">微信支付分签约计划</a>
* <br>
* 文档更新时间2023.10.13
* <br>
* 微信支付分签约计划是不同模式的支付分接口随着国家大力推广教培行业先享后付政策,微信支付也紧跟政策于2023.07.25上线第一版签约计划接口以适用教培行业先享后付于2023.10.13文档推至官网文档中心
* <br>
* 免确认/需确认 用服务商创单接口 {@link PartnerPayScoreService} 需要用户授权
* <br>
* 签约计划用单独创单接口 {@link PartnerPayScoreSignPlanService} 不需要用户授权
* </pre>
**/
@RequiredArgsConstructor
public class PartnerPayScoreSignPlanServiceImpl implements PartnerPayScoreSignPlanService {
private static final Gson GSON = new GsonBuilder().create();
private final WxPayService payService ;
/**
* <p>description创建支付分计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 11:58 </p>
* <p>version v.1.0 </p>
*
* @param request {@link WxPartnerPayScoreSignPlanRequest}
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/create-partner-pay-score-plan.html">创建支付分计划</a>
**/
@Override
public WxPartnerPayScoreSignPlanResult createPlans(WxPartnerPayScoreSignPlanRequest request) throws WxPayException {
String url = String.format("%s/v3/payscore/plan/partner/payscore-plans", payService.getPayBaseUrl());
if (StringUtils.isBlank(request.getAppid())) {
request.setAppid(payService.getConfig().getAppId());
}
if (StringUtils.isBlank((request.getServiceId()))) {
request.setServiceId(payService.getConfig().getServiceId());
}
String result = payService.postV3(url, request.toJson());
return WxPartnerPayScoreSignPlanResult.fromJson(result);
}
/**
* <p>description 查询支付分计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 14:03 </p>
* <p>version v.1.0 </p>
*
* @param merchantPlanNo 路径参数支付分计划商户侧单号
* @param subMchid 子商户号
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/query-partner-pay-score-plan.html">查询支付分计划</a>
**/
@Override
public WxPartnerPayScoreSignPlanResult queryPlans(@NonNull String merchantPlanNo, @NonNull String subMchid) throws WxPayException {
String url = String.format("%s/v3/payscore/plan/partner/payscore-plans/merchant-plan-no/%s", payService.getPayBaseUrl(), merchantPlanNo);
try {
URI uri = new URIBuilder(url)
.setParameter("sub_mchid", subMchid)
.build();
String result = payService.getV3(uri.toString());
return WxPartnerPayScoreSignPlanResult.fromJson(result);
} catch (URISyntaxException e) {
throw new WxPayException("未知异常!", e);
}
}
/**
* <p>description 停止支付分计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 14:24 </p>
* <p>version v.1.0 </p>
*
* @param merchantPlanNo 路径参数支付分计划商户侧单号
* @param subMchid 子商户号
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-pay-score-plan/stop-partner-pay-score-plan.html">停止支付分计划</a>
**/
@Override
public WxPartnerPayScoreSignPlanResult stopPlans(@NonNull String merchantPlanNo, @NonNull String subMchid) throws WxPayException {
String url = String.format("%s/v3/payscore/plan/partner/payscore-plans/merchant-plan-no/%s/stop", payService.getPayBaseUrl(), merchantPlanNo);
Map<String, String> params = Maps.newHashMap();
params.put("sub_mchid", subMchid);
String result = payService.postV3(url, WxGsonBuilder.create().toJson(params));
return WxPartnerPayScoreSignPlanResult.fromJson(result);
}
/**
* <p>description 创建用户的签约计划详情对应的服务订单 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 14:53 </p>
* <p>version v.1.0 </p>
*
* @param request {@link WxPartnerPayScoreSignPlanRequest}
*
* @return {@link WxPartnerPayScoreSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-service-order/create-partner-service-order.html">创建用户的签约计划详情对应的服务订单</a>
**/
@Override
public WxPartnerPayScoreSignPlanResult signPlanServiceOrder(WxPartnerPayScoreSignPlanRequest request) throws WxPayException {
String url = String.format("%s/v3/payscore/sign-plan/partner/serviceorder", payService.getPayBaseUrl());
if (StringUtils.isBlank(request.getAppid())) {
request.setAppid(payService.getConfig().getAppId());
}
if (StringUtils.isBlank((request.getServiceId()))) {
request.setServiceId(payService.getConfig().getServiceId());
}
String result = payService.postV3(url, request.toJson());
return WxPartnerPayScoreSignPlanResult.fromJson(result);
}
/**
* <p>description 创建用户的签约计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 17:48 </p>
* <p>version v.1.0 </p>
*
* @param request {@link WxPartnerPayScoreSignPlanRequest}
*
* @return {@link WxPartnerPayScoreUserSignPlanResult}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-user-sign-plan/create-partner-user-sign-plan.html">创建用户的签约计划</a>
**/
@Override
public WxPartnerPayScoreUserSignPlanResult createUserSignPlans(WxPartnerPayScoreSignPlanRequest request) throws WxPayException {
String url = String.format("%s/v3/payscore/sign-plan/partner/user-sign-plans", payService.getPayBaseUrl());
if (StringUtils.isBlank(request.getAppid())) {
request.setAppid(payService.getConfig().getAppId());
}
if (StringUtils.isBlank((request.getServiceId()))) {
request.setServiceId(payService.getConfig().getServiceId());
}
String result = payService.postV3(url, request.toJson());
return WxPartnerPayScoreUserSignPlanResult.fromJson(result);
}
/**
* <p>description 查询用户的签约计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 18:01 </p>
* <p>version v.1.0 </p>
*
* @param merchantSignPlanNo 路径参数 商户签约计划号
* @param subMchid 子商户号
*
* @return {@link PartnerUserSignPlanEntity}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-user-sign-plan/query-partner-user-sign-plan.html">查询用户的签约计划</a>
**/
@Override
public PartnerUserSignPlanEntity queryUserSignPlans(@NonNull String merchantSignPlanNo, @NonNull String subMchid) throws WxPayException {
String url = String.format("%s/v3/payscore/sign-plan/partner/user-sign-plans/merchant-sign-plan-no/%s", payService.getPayBaseUrl(), merchantSignPlanNo);
try {
URI uri = new URIBuilder(url)
.setParameter("sub_mchid", subMchid)
.build();
String result = payService.getV3(uri.toString());
return PartnerUserSignPlanEntity.fromJson(result);
} catch (URISyntaxException e) {
throw new WxPayException("未知异常!", e);
}
}
/**
* <p>description 取消用户的签约计划 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 18:01 </p>
* <p>version v.1.0 </p>
*
* @param merchantSignPlanNo 路径参数 商户签约计划号 subMchid 子商户号
* @param subMchid 子商户号
* @param stopReason 停止签约计划原因
*
* @return {@link PartnerUserSignPlanEntity}
* @apiNote <a href="https://pay.weixin.qq.com/docs/partner/apis/partner-payscore-plan/partner-user-sign-plan/stop-partner-user-sign-plan.html">取消用户的签约计划</a>
**/
@Override
public PartnerUserSignPlanEntity stopUserSignPlans(@NonNull String merchantSignPlanNo, @NonNull String subMchid, @NonNull String stopReason) throws WxPayException {
String url = String.format("%s/v3/payscore/sign-plan/partner/user-sign-plans/merchant-sign-plan-no/%s/stop", payService.getPayBaseUrl(), merchantSignPlanNo);
Map<String, String> params = Maps.newHashMap();
params.put("sub_mchid", subMchid);
params.put("stop_reason", stopReason);
String result = payService.postV3(url, WxGsonBuilder.create().toJson(params));
return PartnerUserSignPlanEntity.fromJson(result);
}
/**
* <p>description回调通知 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 18:28 </p>
* <p>version v.1.0 </p>
*
* @param notifyData 回调参数
* @param header 签名
*
* @return {@link PartnerUserSignPlanEntity}
**/
@Override
public PartnerUserSignPlanEntity parseSignPlanNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
PayScoreNotifyData response = parseNotifyData(notifyData, header);
return decryptNotifyDataResource(response);
}
/**
* <p>description 检验并解析通知参数 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 18:30 </p>
* <p>version v.1.0 </p>
* @param data
* @param header
* @return {@link PayScoreNotifyData}
**/
public PayScoreNotifyData parseNotifyData(String data, SignatureHeader header) throws WxPayException {
if (Objects.nonNull(header) && !verifyNotifySign(header, data)) {
throw new WxPayException("非法请求,头部信息验证失败");
}
return GSON.fromJson(data, PayScoreNotifyData.class);
}
/**
* <p>description 解析回调通知参数 </p>
* <p>authorUltramanNoa </p>
* <p>create Time 2023/11/3 18:27 </p>
* <p>version v.1.0 </p>
*
* @param data {@link PayScoreNotifyData}
*
* @return {@link PartnerUserSignPlanEntity}
**/
public PartnerUserSignPlanEntity decryptNotifyDataResource(PayScoreNotifyData data) throws WxPayException {
PayScoreNotifyData.Resource resource = data.getResource();
String cipherText = resource.getCipherText();
String associatedData = resource.getAssociatedData();
String nonce = resource.getNonce();
String apiV3Key = payService.getConfig().getApiV3Key();
try {
return PartnerUserSignPlanEntity.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()
);
}
}

View File

@ -0,0 +1,76 @@
package com.github.binarywang.wxpay.service.impl;
import com.github.binarywang.wxpay.bean.payscore.WxPartnerPayScoreSignPlanRequest;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.PartnerPayScoreSignPlanService;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.testbase.ApiTestModule;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.Inject;
import lombok.extern.slf4j.Slf4j;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
/**
* @className PartnerPayScoreSignPlanServiceImplTest
* @description
* @author
* @createTime 2023/11/6 10:30
**/
@Slf4j
@Test
@Guice(modules = ApiTestModule.class)
public class PartnerPayScoreSignPlanServiceImplTest {
@Inject
private WxPayService wxPayService;
private static final Gson GSON = new GsonBuilder().create();
@Test
public void testcreatePlans()throws WxPayException{
PartnerPayScoreSignPlanService scoreSignPlan=new PartnerPayScoreSignPlanServiceImpl(wxPayService);
WxPartnerPayScoreSignPlanRequest request=new WxPartnerPayScoreSignPlanRequest();
request.setSubMchid("子商户号");
scoreSignPlan.createPlans(request);
}
@Test
public void testqueryPlans()throws WxPayException{
PartnerPayScoreSignPlanService scoreSignPlan=new PartnerPayScoreSignPlanServiceImpl(wxPayService);
scoreSignPlan.queryPlans("merchantPlanNo","子商户号");
}
@Test
public void teststopPlans()throws WxPayException{
PartnerPayScoreSignPlanService scoreSignPlan=new PartnerPayScoreSignPlanServiceImpl(wxPayService);
scoreSignPlan.stopPlans("merchantPlanNo","子商户号");
}
@Test
public void testsignPlanServiceOrder()throws WxPayException{
PartnerPayScoreSignPlanService scoreSignPlan=new PartnerPayScoreSignPlanServiceImpl(wxPayService);
WxPartnerPayScoreSignPlanRequest request=new WxPartnerPayScoreSignPlanRequest();
scoreSignPlan.signPlanServiceOrder(request);
}
@Test
public void testcreateUserSignPlans()throws WxPayException{
PartnerPayScoreSignPlanService scoreSignPlan=new PartnerPayScoreSignPlanServiceImpl(wxPayService);
WxPartnerPayScoreSignPlanRequest request=new WxPartnerPayScoreSignPlanRequest();
scoreSignPlan.createUserSignPlans(request);
}
@Test
public void testqueryUserSignPlans()throws WxPayException{
PartnerPayScoreSignPlanService scoreSignPlan=new PartnerPayScoreSignPlanServiceImpl(wxPayService);
scoreSignPlan.queryUserSignPlans("merchantPlanNo","子商户号");
}
@Test
public void teststopUserSignPlans()throws WxPayException{
PartnerPayScoreSignPlanService scoreSignPlan=new PartnerPayScoreSignPlanServiceImpl(wxPayService);
scoreSignPlan.stopUserSignPlans("merchantPlanNo","子商户号","测试取消");
}
}