From 305cafb9e534245282eba06e48573530fc674e39 Mon Sep 17 00:00:00 2001 From: chenliang Date: Fri, 13 Aug 2021 11:46:08 +0800 Subject: [PATCH] =?UTF-8?q?:new:#2254=E3=80=90=E5=BE=AE=E4=BF=A1=E6=94=AF?= =?UTF-8?q?=E4=BB=98=E3=80=91=E5=A2=9E=E5=8A=A0=E5=BE=AE=E4=BF=A1=E7=AD=BE?= =?UTF-8?q?=E7=BA=A6=E4=BB=A3=E6=89=A3=E9=80=BB=E8=BE=91=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/request/WxH5EntrustRequest.java | 193 +++++++++ .../bean/request/WxMaEntrustRequest.java | 179 +++++++++ .../bean/request/WxMpEntrustRequest.java | 179 +++++++++ .../bean/request/WxPayEntrustRequest.java | 368 ++++++++++++++++++ .../bean/request/WxPreWithholdRequest.java | 65 ++++ .../bean/request/WxSignQueryRequest.java | 105 +++++ .../request/WxTerminatedContractRequest.java | 122 ++++++ .../request/WxWithholdOrderQueryRequest.java | 64 +++ .../wxpay/bean/request/WxWithholdRequest.java | 194 +++++++++ .../wxpay/bean/result/WxH5EntrustResult.java | 43 ++ .../wxpay/bean/result/WxPayEntrustResult.java | 190 +++++++++ .../wxpay/bean/result/WxSignQueryResult.java | 123 ++++++ .../bean/result/WxSignStatusNotifyResult.java | 127 ++++++ .../result/WxTerminationContractResult.java | 57 +++ .../bean/result/WxWithholdNotifyResult.java | 234 +++++++++++ .../result/WxWithholdOrderQueryResult.java | 182 +++++++++ .../wxpay/bean/result/WxWithholdResult.java | 44 +++ .../wxpay/service/WxEntrustPapService.java | 157 ++++++++ .../wxpay/service/WxPayService.java | 6 + .../service/impl/BaseWxPayServiceImpl.java | 7 + .../service/impl/WxEntrustPapServiceImpl.java | 139 +++++++ .../service/impl/WxEntrustPapServiceTest.java | 236 +++++++++++ 22 files changed, 3014 insertions(+) create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxH5EntrustRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMaEntrustRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMpEntrustRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayEntrustRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPreWithholdRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxSignQueryRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxTerminatedContractRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdOrderQueryRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdRequest.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxH5EntrustResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayEntrustResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignStatusNotifyResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxTerminationContractResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdResult.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java create mode 100644 weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceTest.java diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxH5EntrustRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxH5EntrustRequest.java new file mode 100644 index 0000000000..57f34eb136 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxH5EntrustRequest.java @@ -0,0 +1,193 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + * @author chenliang + * @date 2021-08-02 5:12 下午 + * + *
+ *   微信h5纯签约入参
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxH5EntrustRequest extends BaseWxPayRequest { + + /** + *
+   * 协议模板ID
+   * plan_id
+   * 是
+   * String(28)
+   * 12535
+   * 协议模板ID,分为首次签约,支付中签约,重新签约
+   * 
+ */ + @Required + @XStreamAlias("plan_id") + private String planId; + + /** + *
+   * 签约协议号
+   * contract_code
+   * 是
+   * String(32)
+   * 100000
+   * 商户侧签约协议号,由商户生成,只能是数字,大小写字母组成
+   * 
+ */ + @Required + @XStreamAlias("contract_code") + private String contractCode; + + /** + *
+   * 请求序列号
+   * request_serial
+   * 是
+   * int(64)
+   * 1000
+   * 商户请求签约时的序列号,要求唯一性,禁止使用0开头的,用户排序,纯数字
+   * 
+ */ + @Required + @XStreamAlias("request_serial") + private Long requestSerial; + + /** + *
+   * 用户账户展示名称
+   * contract_display_account
+   * 是
+   * string(32)
+   * 微信代扣
+   * 签约用户的名称,用户页面展示,不支持符号表情
+   * 
+ */ + @Required + @XStreamAlias("contract_display_account") + private String contractDisplayAccount; + + /** + *
+   * 回调通知URL
+   * notify_url
+   * 是
+   * string(256)
+   * https://weixin.qq.com
+   * 用于接收签约成功消息的回调通知地址
+   * 
+ */ + @Required + @XStreamAlias("notify_url") + private String notifyUrl; + + /** + *
+   * 版本号
+   * sign
+   * 是
+   * string(8)
+   * 1.0
+   * 固定值1.0
+   * 
+ */ + @Required + @XStreamAlias("version") + private String version; + + /** + *
+   * 时间戳
+   * timestamp
+   * 是
+   * string(10)
+   * 1414488825
+   * 系统当前时间,10位
+   * 
+ */ + @Required + @XStreamAlias("timestamp") + private String timestamp; + + /** + *
+   * 客户端IP
+   * clientip
+   * 是
+   * string(32)
+   * 127.0.0.1
+   * 用户客户端的IP地址
+   * 
+ */ + @Required + @XStreamAlias("clientip") + private String clientIp; + + /** + *
+   * 回调应用appid
+   * return_appid
+   * 否
+   * string(32)
+   * wxcbda96de0b16
+   * 用来控制签约页面结束后的返回路径,
+   * 当指定该字段是,签约成功将返回return_appid指定的APP应用,如果不填且签约发起的浏览器ua可被微信识别,
+   * 则挑战到浏览器,否则留在微信
+   * 
+ */ + @XStreamAlias("return_appid") + private String returnAppid; + + /** + *
+   * 商户测用户标识
+   * outerid
+   * 否
+   * string(32)
+   * 陈*(10000001)
+   * 用于多账号签约,值与contract_display_account一样就行
+   * 
+ */ + @XStreamAlias("outerid") + private String outerId; + + + /** + * 是否需要nonce_str + */ + @Override + protected boolean needNonceStr() { + return false; + } + + @Override + protected void checkConstraints() throws WxPayException { + + } + + @Override + protected void storeMap(Map map) { + map.put("plan_id", planId); + map.put("contract_code", contractCode); + map.put("request_serial", String.valueOf(requestSerial)); + map.put("contract_display_account", contractDisplayAccount); + map.put("notify_url", notifyUrl); + map.put("version", version); + map.put("timestamp", timestamp); + map.put("clientip", clientIp); + map.put("return_appid", returnAppid); + map.put("outerid", outerId); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMaEntrustRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMaEntrustRequest.java new file mode 100644 index 0000000000..0a9e8c2f24 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMaEntrustRequest.java @@ -0,0 +1,179 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.annotations.SerializedName; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + * @author chenliang + * @date 2021-08-02 5:13 下午 + *
+ *   小程序纯签约入参
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxMaEntrustRequest extends BaseWxPayRequest { + /** + *
+   * 协议模板ID
+   * plan_id
+   * 是
+   * String(28)
+   * 12535
+   * 协议模板ID,分为首次签约,支付中签约,重新签约
+   * 
+ */ + @Required + @SerializedName(value = "plan_id") + @XStreamAlias("plan_id") + private String planId; + + /** + *
+   * 签约协议号
+   * contract_code
+   * 是
+   * String(32)
+   * 100000
+   * 商户侧签约协议号,由商户生成,只能是数字,大小写字母组成
+   * 
+ */ + @Required + @SerializedName(value = "contract_code") + @XStreamAlias("contract_code") + private String contractCode; + + /** + *
+   * 请求序列号
+   * request_serial
+   * 是
+   * int(64)
+   * 1000
+   * 商户请求签约时的序列号,要求唯一性,禁止使用0开头的,用户排序,纯数字
+   * 
+ */ + @Required + @SerializedName(value = "request_serial") + @XStreamAlias("request_serial") + private Long requestSerial; + + /** + *
+   * 用户账户展示名称
+   * contract_display_account
+   * 是
+   * string(32)
+   * 微信代扣
+   * 签约用户的名称,用户页面展示,不支持符号表情
+   * 
+ */ + @Required + @SerializedName(value = "contract_display_account") + @XStreamAlias("contract_display_account") + private String contractDisplayAccount; + + /** + *
+   * 回调通知URL
+   * notify_url
+   * 是
+   * string(256)
+   * https://weixin.qq.com
+   * 用于接收签约成功消息的回调通知地址
+   * 
+ */ + @Required + @SerializedName(value = "notify_url") + @XStreamAlias("notify_url") + private String notifyUrl; + + /** + *
+   * 版本号
+   * sign
+   * 是
+   * string(8)
+   * 1.0
+   * 固定值1.0
+   * 
+ */ + @Required + @XStreamAlias("version") + private String version; + + + /** + *
+   * 时间戳
+   * timestamp
+   * 是
+   * string(10)
+   * 1414488825
+   * 系统当前时间,10位
+   * 
+ */ + @Required + @XStreamAlias("timestamp") + private String timestamp; + + /** + *
+   * 商户侧用户标识
+   * outerId
+   * 否
+   * String
+   * 陈*(141448825)
+   * 用户在商户侧的标识
+   * 
+ */ + @XStreamAlias("outerid") + private String outerId; + + @Override + protected void checkConstraints() throws WxPayException { + + } + + /** + * 是否需要nonce_str + */ + @Override + protected boolean needNonceStr() { + return false; + } + + @Override + protected void storeMap(Map map) { + map.put("plan_id", planId); + map.put("contract_code", contractCode); + map.put("request_serial", String.valueOf(requestSerial)); + map.put("contract_display_account", contractDisplayAccount); + map.put("notify_url", notifyUrl); + map.put("timestamp", timestamp); + map.put("outerid", outerId); + } + + @Override + public String toString() { + GsonBuilder gsonBuilder = new GsonBuilder(); + + gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES); + + Gson gson = gsonBuilder.create(); + + return gson.toJson(this); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMpEntrustRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMpEntrustRequest.java new file mode 100644 index 0000000000..0344f36d86 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxMpEntrustRequest.java @@ -0,0 +1,179 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; +import java.util.Objects; + +/** + * @author chenliang + * @date 2021-08-02 5:17 下午 + * + *
+ *   公众号纯签约入参
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxMpEntrustRequest extends BaseWxPayRequest { + + /** + *
+   * 协议模板ID
+   * plan_id
+   * 是
+   * String(28)
+   * 12535
+   * 协议模板ID,分为首次签约,支付中签约,重新签约
+   * 
+ */ + @Required + @XStreamAlias("plan_id") + private String planId; + + /** + *
+   * 签约协议号
+   * contract_code
+   * 是
+   * String(32)
+   * 100000
+   * 商户侧签约协议号,由商户生成,只能是数字,大小写字母组成
+   * 
+ */ + @Required + @XStreamAlias("contract_code") + private String contractCode; + + /** + *
+   * 请求序列号
+   * request_serial
+   * 是
+   * int(64)
+   * 1000
+   * 商户请求签约时的序列号,要求唯一性,禁止使用0开头的,用户排序,纯数字
+   * 
+ */ + @Required + @XStreamAlias("request_serial") + private Long requestSerial; + + /** + *
+   * 用户账户展示名称
+   * contract_display_account
+   * 是
+   * string(32)
+   * 微信代扣
+   * 签约用户的名称,用户页面展示,不支持符号表情
+   * 
+ */ + @Required + @XStreamAlias("contract_display_account") + private String contractDisplayAccount; + + /** + *
+   * 回调通知URL
+   * notify_url
+   * 是
+   * string(256)
+   * https://weixin.qq.com
+   * 用于接收签约成功消息的回调通知地址
+   * 
+ */ + @Required + @XStreamAlias("notify_url") + private String notifyUrl; + + /** + *
+   * 版本号
+   * sign
+   * 是
+   * string(8)
+   * 1.0
+   * 固定值1.0
+   * 
+ */ + @Required + @XStreamAlias("version") + private String version; + + /** + *
+   * 时间戳
+   * timestamp
+   * 是
+   * string(10)
+   * 1414488825
+   * 系统当前时间,10位
+   * 
+ */ + @Required + @XStreamAlias("timestamp") + private String timestamp; + + /** + *
+   * 返回web
+   * return_web
+   * 否
+   * int
+   * 1
+   * 用来控制签约页面结束后的返回路径(不传签约后留在微信内),1 表示返回签约页面的referrer url,
+   * 不填或获取不到referrer则不返回,跳转referrer url 时会自动带上from_wxpay=1
+   * 
+ */ + @XStreamAlias("return_web") + private Integer returnWeb; + + /** + *
+   * 商户测的用户标识
+   * outerid
+   * 否
+   * String()
+   * 陈*(101000203)
+   * 用于多账户签约,同一个模板下要保持一致,取值和contractDisplayAccount取一样就行
+   * 
+ */ + @XStreamAlias("outerid") + private String outerId; + + @Override + protected void checkConstraints() throws WxPayException { + + } + + /** + * 是否需要nonce_str + */ + @Override + protected boolean needNonceStr() { + return false; + } + + @Override + protected void storeMap(Map map) { + map.put("plan_id", planId); + map.put("contract_code", contractCode); + map.put("request_serial", String.valueOf(requestSerial)); + map.put("contract_display_account", contractDisplayAccount); + map.put("notify_url", notifyUrl); + map.put("version", version); + map.put("timestamp", timestamp); + if (Objects.nonNull(returnWeb)) { + map.put("return_web", String.valueOf(returnWeb)); + } + map.put("outerid", outerId); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayEntrustRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayEntrustRequest.java new file mode 100644 index 0000000000..764e0e56af --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayEntrustRequest.java @@ -0,0 +1,368 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + * @author chenliang + * @date 2021-08-02 5:18 下午 + *
+ *   支付中签约入参
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxPayEntrustRequest extends BaseWxPayRequest { + + /** + *
+   * 签约商户号
+   * contract_mchid
+   * 是
+   * String(32)
+   * 1200009811
+   * 签约商户号,必须与mch_id一致
+   * 
+ */ + @Required + @XStreamAlias("contract_mchid") + private String contractMchId; + + /** + *
+   * 签约APPID
+   * contract_appid
+   * 是
+   * String(32)
+   * wxcbda96de0b165486
+   * 签约公众号,必须与APPID一致
+   * 
+ */ + @Required + @XStreamAlias("contract_appid") + private String contractAppId; + + /** + *
+   * 商户订单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 123456
+   * 商户系统内部的订单号,32字符内,可包含字母
+   * 
+ */ + @Required + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 设备号
+   * device_info
+   * 否
+   * String(32)
+   * 013467007045764
+   * 终端设备号,若为PC网页或公众号内则传WEB
+   * 
+ */ + @XStreamAlias("device_info") + private String deviceInfo; + + /** + *
+   * 商品描述
+   * body
+   * 是
+   * String(128)
+   * ipad mini 16G 白色
+   * 商品支付单简要描述
+   * 
+ */ + @Required + @XStreamAlias("body") + private String body; + + /** + *
+   * 商品详情
+   * detail
+   * 否
+   * String(8192)
+   * ipad mini 16G 白色
+   * 商品名称明细列表
+   * 
+ */ + @XStreamAlias("detail") + private String detail; + + /** + *
+   * 附加数据
+   * attach
+   * 否
+   * String(127)
+   * online/dev/dev1
+   * 商家数据包
+   * 
+ */ + @XStreamAlias("attach") + private String attach; + + /** + *
+   * 回调通知url
+   * notify_url
+   * 是
+   * String(256)
+   * https://weixin.qq.com
+   * 回调通知地址
+   * 
+ */ + @Required + @XStreamAlias("notify_url") + private String notifyUrl; + + /** + *
+   * 总金额
+   * total_fee
+   * 是
+   * int
+   * 888
+   * 订单总金额,单位分
+   * 
+ */ + @Required + @XStreamAlias("total_fee") + private Integer totalFee; + + /** + *
+   * 终端ip
+   * spbill_create_ip
+   * 是
+   * String(16)
+   * 127.0.0.1
+   * 用户的客户端IP
+   * 
+ */ + @Required + @XStreamAlias("spbill_create_ip") + private String spbillCreateIp; + + /** + *
+   * 交易起始时间
+   * time_start
+   * 否
+   * String(14)
+   * 20201025171529
+   * 订单生成时间,格式yyyyMMddHHmmss
+   * 
+ */ + @XStreamAlias("time_start") + private String timeStart; + + /** + *
+   * 交易结束时间
+   * time_expire
+   * 否
+   * String(14)
+   * 20201025171529
+   * 订单失效时间,格式yyyyMMddHHmmss
+   * 
+ */ + @XStreamAlias("time_expire") + private String timeExpire; + + /** + *
+   * 商品标记
+   * goods_tag
+   * 否
+   * String(32)
+   * wxg
+   * 商品标记,代金券或立减优惠功能参数
+   * 
+ */ + @XStreamAlias("goods_tag") + private String goodsTag; + + /** + *
+   * 交易类型
+   * trade_type
+   * 是
+   * String(16)
+   * JSAPI
+   * JSAPI,MWEB
+   * 
+ */ + @Required + @XStreamAlias("trade_type") + private String tradeType; + + /** + *
+   * 商品ID
+   * product_id
+   * 否
+   * String(32)
+   * 12234355463434643
+   * 二维码支付必传,二维码中包含商品ID
+   * 
+ */ + @XStreamAlias("product_id") + private String productId; + + /** + *
+   * 指定支付方式
+   * limit_pay
+   * 否
+   * String(32)
+   * no_credit
+   * no_credit--指定不能使用信用卡支付
+   * 
+ */ + @XStreamAlias("limit_pay") + private String limitPay; + + /** + *
+   * 用户表示
+   * openid
+   * 否
+   * String(128)
+   * oUpF4sdsidj3Jds89
+   * tradetype=JSAPI 则必传
+   * 
+ */ + @XStreamAlias("openid") + private String openId; + + /** + *
+   * 协议模板ID
+   * plan_id
+   * 是
+   * String(28)
+   * 12535
+   * 协议模板ID,分为首次签约,支付中签约,重新签约
+   * 
+ */ + @Required + @XStreamAlias("plan_id") + private String planId; + + /** + *
+   * 签约协议号
+   * contract_code
+   * 是
+   * String(32)
+   * 100000
+   * 商户侧签约协议号,由商户生成,只能是数字,大小写字母组成
+   * 
+ */ + @Required + @XStreamAlias("contract_code") + private String contractCode; + + /** + *
+   * 请求序列号
+   * request_serial
+   * 是
+   * int(64)
+   * 1000
+   * 商户请求签约时的序列号,要求唯一性,禁止使用0开头的,用户排序,纯数字
+   * 
+ */ + @Required + @XStreamAlias("request_serial") + private Long requestSerial; + + /** + *
+   * 用户账户展示名称
+   * contract_display_account
+   * 是
+   * string(32)
+   * 微信代扣
+   * 签约用户的名称,用户页面展示,不支持符号表情
+   * 
+ */ + @Required + @XStreamAlias("contract_display_account") + private String contractDisplayAccount; + + /** + *
+   * 签约信息通知URL
+   * contract_notify_url
+   * 是
+   * string(32)
+   * https://yoursite.com
+   * 签约信息回调通知URL
+   * 
+ */ + @Required + @XStreamAlias("contract_notify_url") + private String contractNotifyUrl; + + /** + *
+   * 商户测的用户标识
+   * contract_outerid
+   * 否
+   * string(32)
+   * 陈*(12000002)
+   * 用于多账户签约,值与contract_display_account相同即可,同一模板下唯一
+   * 
+ */ + @XStreamAlias("contract_outerid") + private String contractOuterId; + + @Override + protected void checkConstraints() throws WxPayException { + + } + + @Override + protected void storeMap(Map map) { + map.put("contract_mchid", contractMchId); + map.put("contract_appid", contractAppId); + map.put("out_trade_no", outTradeNo); + map.put("device_info", deviceInfo); + map.put("body", body); + map.put("detail", detail); + map.put("attach", attach); + map.put("notify_url", notifyUrl); + map.put("total_fee", totalFee.toString()); + map.put("spbill_create_ip", spbillCreateIp); + map.put("time_start", timeStart); + map.put("time_expire", timeExpire); + map.put("goods_tag", goodsTag); + map.put("trade_type", tradeType); + map.put("product_id", productId); + map.put("limit_pay", limitPay); + map.put("openid", openId); + map.put("plan_id", planId); + map.put("contract_code", contractCode); + map.put("request_serial", requestSerial.toString()); + map.put("contract_display_account", contractDisplayAccount); + map.put("contract_notify_url", contractNotifyUrl); + map.put("contract_outerid", contractOuterId); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPreWithholdRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPreWithholdRequest.java new file mode 100644 index 0000000000..ed806afecf --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPreWithholdRequest.java @@ -0,0 +1,65 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * @author chenliang + * @date 2021-08-02 5:20 下午 + * + *
+ *   微信预扣款请求参数
+ * 
+ */ +@Data +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +public class WxPreWithholdRequest implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 委托代扣协议ID + */ + @SerializedName(value = "contract_id") + private String contractId; + + /** + * 直连商户号 + */ + @SerializedName(value = "mchid") + private String mchId; + + /** + * 公众号ID + */ + @SerializedName(value = "appid") + private String appId; + + /** + * 预计扣款的金额信息 + */ + @SerializedName(value = "estimated_amount") + private EstimateAmount estimateAmount; + + + @Data + public static class EstimateAmount implements Serializable { + /** + * 预计扣费金额 + */ + private Integer amount; + + /** + * 人民币:CNY + * 非必填 + */ + private String currency; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxSignQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxSignQueryRequest.java new file mode 100644 index 0000000000..3ecee3fe81 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxSignQueryRequest.java @@ -0,0 +1,105 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; +import java.util.Objects; + +/** + * @author chenliang + * @date 2021-08-02 5:23 下午 + *
+ *   微信签约状态查询入参
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxSignQueryRequest extends BaseWxPayRequest { + + //方式1. 使用contract_id查 + /** + *
+   * 字段名:委托代扣协议ID.
+   * 变量名:contract_id
+   * 是否必填:二选一
+   * 类型:String(32)
+   * 示例值:1000005698
+   * 签约成功后由微信返回
+   * 
+ */ + @XStreamAlias("contract_id") + private String contractId; + + /** + *
+   * 字段名:接口版本号.
+   * 变量名:version
+   * 是否必填:是
+   * 类型:String(8)
+   * 示例值:1.0
+   * 固定填写1.0,
+   * 
+ */ + @Required + @XStreamAlias("version") + private String version; + + //方式2. 使用plan_id和contract_code查 + /** + *
+   * 字段名:模板ID.
+   * 变量名:plan_id
+   * 是否必填:二选一
+   * 类型:int
+   * 示例值:123
+   * 代扣模板ID
+   * 
+ */ + @XStreamAlias("plan_id") + private Integer planId; + + /** + *
+   * 字段名:签约协议号.
+   * 变量名:contract_code
+   * 是否必填:二选一
+   * 类型:String(32)
+   * 示例值:12332343
+   * 商户侧唯一
+   * 
+ */ + @XStreamAlias("contract_code") + private String contractCode; + + + @Override + protected boolean needNonceStr() { + return false; + } + + @Override + protected void checkConstraints() throws WxPayException { + if (StringUtils.isNotBlank(contractId) && + (Objects.nonNull(planId) || StringUtils.isNotBlank(contractCode))) { + throw new WxPayException("contractId 和 planId&contractCode 不能同时存在或同时为空,必须二选一"); + } + } + + @Override + protected void storeMap(Map map) { + map.put("contract_id", contractId); + map.put("version", version); + if (Objects.nonNull(planId)) { + map.put("plan_id", planId.toString()); + } + map.put("contract_code", contractCode); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxTerminatedContractRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxTerminatedContractRequest.java new file mode 100644 index 0000000000..2ebd96e780 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxTerminatedContractRequest.java @@ -0,0 +1,122 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; +import java.util.Objects; + +/** + * @author chenliang + * @date 2021-08-02 5:24 下午 + * + *
+ *   微信api申请解约
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxTerminatedContractRequest extends BaseWxPayRequest { + //方式1. 使用contract_id查 + /** + *
+   * 字段名:委托代扣协议ID.
+   * 变量名:contract_id
+   * 是否必填:二选一
+   * 类型:String(32)
+   * 示例值:1000005698
+   * 签约成功后由微信返回
+   * 
+ */ + @XStreamAlias("contract_id") + private String contractId; + + /** + *
+   * 字段名:解约备注.
+   * 变量名:contract_termination_remark
+   * 是否必填:是
+   * 类型:String(256)
+   * 示例值:解约原因
+   * 例如:签约信息有误,须重新签约
+   * 
+ */ + @Required + @XStreamAlias("contract_termination_remark") + private String contractTerminationRemark; + + /** + *
+   * 字段名:接口版本号.
+   * 变量名:version
+   * 是否必填:是
+   * 类型:String(8)
+   * 示例值:1.0
+   * 固定填写1.0,
+   * 
+ */ + @Required + @XStreamAlias("version") + private String version; + + //方式2. 使用plan_id和contract_code查 + + /** + *
+   * 字段名:模板ID.
+   * 变量名:plan_id
+   * 是否必填:二选一
+   * 类型:int
+   * 示例值:123
+   * 代扣模板ID
+   * 
+ */ + @XStreamAlias("plan_id") + private Integer planId; + + /** + *
+   * 字段名:签约协议号.
+   * 变量名:contract_code
+   * 是否必填:二选一
+   * 类型:String(32)
+   * 示例值:12332343
+   * 商户侧唯一
+   * 
+ */ + @XStreamAlias("contract_code") + private String contractCode; + + + @Override + protected void checkConstraints() throws WxPayException { + if (StringUtils.isNotBlank(contractId) && + (Objects.nonNull(planId) || StringUtils.isNotBlank(contractCode))) { + throw new WxPayException("contractId 和 planId&contractCode 不能同时存在或同时为空,必须二选一"); + } + } + + @Override + protected boolean needNonceStr() { + return false; + } + + + @Override + protected void storeMap(Map map) { + map.put("contract_id", contractId); + map.put("contract_termination_remark", contractTerminationRemark); + map.put("version", version); + if (Objects.nonNull(planId)) { + map.put("plan_id", planId.toString()); + } + map.put("contract_code", contractCode); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdOrderQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdOrderQueryRequest.java new file mode 100644 index 0000000000..a94e0a4c16 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdOrderQueryRequest.java @@ -0,0 +1,64 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import org.apache.commons.lang3.StringUtils; + +import java.util.Map; + +/** + * @author chenliang + * @date 2021-08-02 5:25 下午 + * + *
代扣订单查询参数
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxWithholdOrderQueryRequest extends BaseWxPayRequest { + /** + *
+   * 字段名:微信订单号.
+   * 变量名:transaction_id
+   * 是否必填:二选一
+   * 类型:String(32)
+   * 示例值:1000005698
+   * 微信生成的单号,支付通知中返回
+   * 
+ */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户订单号.
+   * 变量名:out_trade_no
+   * 是否必填:二选一
+   * 类型:String(32)
+   * 示例值:1000005698
+   * 商户系统内部订单号
+   * 
+ */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + + @Override + protected void checkConstraints() throws WxPayException { + if (StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo)) { + throw new WxPayException("transactionId 和 outTradeNo 不能同时存在或同时为空,必须二选一"); + } + } + + @Override + protected void storeMap(Map map) { + + map.put("transaction_id", transactionId); + map.put("out_trade_no", outTradeNo); + + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdRequest.java new file mode 100644 index 0000000000..d31e3a36ed --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxWithholdRequest.java @@ -0,0 +1,194 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + * @author chenliang + * @date 2021-08-02 5:26 下午 + * + *
+ *   发起微信委托代扣参数
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class WxWithholdRequest extends BaseWxPayRequest { + + /** + *
+   * 商品描述
+   * body
+   * 是
+   * String(128)
+   * ipad mini 16G 白色
+   * 商品支付单简要描述
+   * 
+ */ + @Required + @XStreamAlias("body") + private String body; + + /** + *
+   * 商品详情
+   * detail
+   * 否
+   * String(8192)
+   * ipad mini 16G 白色
+   * 商品名称明细列表
+   * 
+ */ + @XStreamAlias("detail") + private String detail; + + /** + *
+   * 附加数据
+   * attach
+   * 否
+   * String(127)
+   * online/dev/dev1
+   * 商家数据包
+   * 
+ */ + @XStreamAlias("attach") + private String attach; + + /** + *
+   * 商户订单号
+   * out_trade_no
+   * 是
+   * String(32)
+   * 123456
+   * 商户系统内部的订单号,32字符内,可包含字母
+   * 
+ */ + @Required + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + *
+   * 总金额
+   * total_fee
+   * 是
+   * int
+   * 888
+   * 订单总金额,单位分
+   * 
+ */ + @Required + @XStreamAlias("total_fee") + private Integer totalFee; + + /** + *
+   * 货币类型
+   * fee_type
+   * 否
+   * String(16)
+   * CNY
+   * 默认人民币:CNY
+   * 
+ */ + @XStreamAlias("fee_type") + private String feeType; + + /** + *
+   * 终端ip
+   * spbill_create_ip
+   * 否
+   * String(16)
+   * 127.0.0.1
+   * 用户的客户端IP
+   * 
+ */ + @XStreamAlias("spbill_create_ip") + private String spbillCreateIp; + + /** + *
+   * 商品标记
+   * goods_tag
+   * 否
+   * String(32)
+   * wxg
+   * 商品标记,代金券或立减优惠功能参数
+   * 
+ */ + @XStreamAlias("goods_tag") + private String goodsTag; + + /** + *
+   * 回调通知url
+   * notify_url
+   * 是
+   * String(256)
+   * https://weixin.qq.com
+   * 回调通知地址
+   * 
+ */ + @Required + @XStreamAlias("notify_url") + private String notifyUrl; + + /** + *
+   * 交易类型
+   * trade_type
+   * 是
+   * String(16)
+   * JSAPI
+   * JSAPI,MWEB
+   * 
+ */ + @Required + @XStreamAlias("trade_type") + private String tradeType; + + /** + *
+   * 委托代扣协议ID
+   * contract_id
+   * 是
+   * String(32)
+   * Wx234324808503234483920
+   * 签约成功后微信返回的委托代扣协议ID
+   * 
+ */ + @Required + @XStreamAlias("contract_id") + private String contractId; + + @Override + protected void checkConstraints() throws WxPayException { + + } + + @Override + protected void storeMap(Map map) { + map.put("body", body); + map.put("detail", detail); + map.put("attach", attach); + map.put("out_trade_no", outTradeNo); + map.put("total_fee", totalFee.toString()); + map.put("fee_type", feeType); + map.put("spbill_create_ip", spbillCreateIp); + map.put("goods_tag", goodsTag); + map.put("notify_url", notifyUrl); + map.put("trade_type", tradeType); + map.put("contract_id", contractId); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxH5EntrustResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxH5EntrustResult.java new file mode 100644 index 0000000000..3cd8daad71 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxH5EntrustResult.java @@ -0,0 +1,43 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + * @author chenliang + * @date 2021-08-02 5:37 下午 + * + *
+ *   h5纯签约后结果
+ * 
+ */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WxH5EntrustResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 业务结果描述 + */ + @XStreamAlias("result_msg") + private String resultMsg; + + /** + * 跳转url + */ + @XStreamAlias("redirect_url") + private String redirectUrl; + + @Override + protected void loadXml(Document d) { + resultMsg = readXmlString(d, "result_msg"); + redirectUrl = readXmlString(d, "redirect_url"); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayEntrustResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayEntrustResult.java new file mode 100644 index 0000000000..4dc6f19ae4 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayEntrustResult.java @@ -0,0 +1,190 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.util.SignUtils; +import com.google.common.collect.Lists; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Document; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * @author chenliang + * @date 2021-08-02 5:38 下午 + * + *
+ *   支付中签约返回结果
+ * 
+ */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WxPayEntrustResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 预签约结果 + */ + @XStreamAlias("contract_result_code") + private String contractResultCode; + + /** + * 预约签约错误码 + */ + @XStreamAlias("contract_err_code") + private String contractErrCode; + + /** + * 预签约错误描述 + */ + @XStreamAlias("contract_err_code_des") + private String contractErrCodeDes; + + /** + * 预支付ID + */ + @XStreamAlias("prepay_id") + private String prepayId; + + /** + * 交易类型 + */ + @XStreamAlias("trade_type") + private String tradeType; + + /** + * 二维码链接 + * 非必传 + */ + @XStreamAlias("code_url") + private String codeUrl; + + /** + * 模板ID + * 非必传 + */ + @XStreamAlias("plan_id") + private Integer planId; + + /** + * 请求序列号 + * 非必传 + */ + @XStreamAlias("request_serial") + private Integer requestSerial; + + /** + * 签约协议号 + * 非必传 + */ + @XStreamAlias("contract_code") + private String contractCode; + + /** + * 用户账户展示名称 + * 非必传 + */ + @XStreamAlias("contract_display_account") + private String contractDisplayAccount; + + /** + * 支付跳转链接 + * 非必传 + */ + @XStreamAlias("mweb_url") + private String mwebUrl; + + /** + * 商户订单号 + */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + + @Override + protected void loadXml(Document d) { + contractResultCode = readXmlString(d, "contract_result_code"); + contractErrCode = readXmlString(d, "contract_err_code"); + contractErrCodeDes = readXmlString(d, "contract_err_code_des"); + prepayId = readXmlString(d, "prepay_id"); + tradeType = readXmlString(d, "trade_type"); + codeUrl = readXmlString(d, "code_url"); + planId = readXmlInteger(d, "plan_id"); + requestSerial = readXmlInteger(d, "request_serial"); + contractCode = readXmlString(d, "contract_code"); + contractDisplayAccount = readXmlString(d, "contract_display_account"); + mwebUrl = readXmlString(d, "mweb_url"); + outTradeNo = readXmlString(d, "out_trade_no"); + } + + /** + * 校验返回结果签名. + * + * @param wxPayService the wx pay service + * @param signType 签名类型 + * @param checkSuccess 是否同时检查结果是否成功 + * @throws WxPayException the wx pay exception + */ + + @Override + public void checkResult(WxPayService wxPayService, String signType, boolean checkSuccess) throws WxPayException { + //校验返回结果签名 + Map map = toMap(); + if (getSign() != null && !SignUtils.checkSign(map, signType, wxPayService.getConfig().getMchKey())) { + this.getLogger().debug("校验结果签名失败,参数:{}", map); + throw new WxPayException("参数格式校验错误!"); + } + + //校验结果是否成功 + if (checkSuccess) { + List successStrings = Lists.newArrayList(WxPayConstants.ResultCode.SUCCESS, ""); + if (!successStrings.contains(StringUtils.trimToEmpty(getReturnCode()).toUpperCase()) + || !successStrings.contains(StringUtils.trimToEmpty(getResultCode()).toUpperCase())) { + StringBuilder errorMsg = new StringBuilder(); + if (getReturnCode() != null) { + errorMsg.append("返回代码:").append(getReturnCode()); + } + if (getReturnMsg() != null) { + errorMsg.append(",返回信息:").append(getReturnMsg()); + } + if (getResultCode() != null) { + errorMsg.append(",结果代码:").append(getResultCode()); + } + if (getErrCode() != null) { + errorMsg.append(",错误代码:").append(getErrCode()); + } + if (getErrCodeDes() != null) { + errorMsg.append(",错误详情:").append(getErrCodeDes()); + } + if (getContractErrCode() != null) { + errorMsg.append(",预签约错误代码:").append(getContractErrCode()); + } + if (getContractErrCodeDes() != null) { + errorMsg.append(",预签约错误描述:").append(getContractErrCodeDes()); + } + if (getContractResultCode() != null) { + errorMsg.append(",预签约结果:").append(getContractResultCode()); + } + + + this.getLogger().warn("\n结果业务代码异常,返回结果:{},\n{}", map, errorMsg.toString()); + throw WxPayException.from(this); + } + } + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java new file mode 100644 index 0000000000..492c6c9251 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignQueryResult.java @@ -0,0 +1,123 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + * @author chenliang + * @date 2021-08-02 5:40 下午 + * + *
+ *   微信签约查询返回结果
+ * 
+ */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WxSignQueryResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 委托代扣协议ID + */ + @XStreamAlias("contractId") + private String contractId; + + /** + * 模板ID + */ + @XStreamAlias("plan_id") + private String planId; + + /** + * 请求序列号 + */ + @XStreamAlias("request_serial") + private Integer requestSerial; + + /** + * 签约协议号 + */ + @XStreamAlias("contract_code") + private String contractCode; + + /** + * 用户账户展示名称 + */ + @XStreamAlias("contract_display_account") + private String contractDisplayAccount; + + /** + * 协议状态 + */ + @XStreamAlias("contract_state") + private Integer contractState; + + /** + * 协议签署时间 + */ + @XStreamAlias("contract_signed_time") + private String contractSignedTime; + + /** + * 协议到期时间 + */ + @XStreamAlias("contract_expired_time") + private String contractExpiredTime; + + /** + * 协议解约时间 + * 非必传 + */ + @XStreamAlias("contract_terminated_time") + private String contractTerminatedTime; + + /** + * 协议解约方式 + * 非必传 + */ + @XStreamAlias("contract_terminated_mode") + private Integer contractTerminatedMode; + + /** + * 解约备注 + * 非必传 + */ + @XStreamAlias("contract_termination_remark") + private String contractTerminationRemark; + + /** + * 用户表示 + */ + @XStreamAlias("openid") + private String openId; + + + @Override + protected void loadXml(Document d) { + contractId = readXmlString(d, "contract_id"); + planId = readXmlString(d, "plan_id"); + requestSerial = readXmlInteger(d, "request_serial"); + contractCode = readXmlString(d, "contract_code"); + contractDisplayAccount = readXmlString(d, "contract_display_account"); + contractState = readXmlInteger(d, "contract_state"); + contractSignedTime = readXmlString(d, "contract_signed_time"); + contractExpiredTime = readXmlString(d, "contrace_Expired_time"); + contractTerminatedTime = readXmlString(d, "contract_terminated_time"); + contractTerminatedMode = readXmlInteger(d, "contract_terminate_mode"); + contractTerminationRemark = readXmlString(d, "contract_termination_remark"); + openId = readXmlString(d, "openid"); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignStatusNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignStatusNotifyResult.java new file mode 100644 index 0000000000..f55b576e36 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxSignStatusNotifyResult.java @@ -0,0 +1,127 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import me.chanjar.weixin.common.util.xml.XStreamInitializer; +import org.w3c.dom.Document; + +/** + * @author chenliang + * @date 2021-08-02 4:59 下午 + *
+ *   微信签约/解约 回调结果
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@XStreamAlias("xml") +public class WxSignStatusNotifyResult extends BaseWxPayResult { + + private static final long serialVersionUID = 1L; + + /** + * 签约协议号 + */ + @XStreamAlias("contract_code") + private String contractCode; + + /** + * 模板ID + */ + @XStreamAlias("plan_id") + private String planId; + + /** + * 用户表示 + */ + @XStreamAlias("openid") + private String openId; + + /** + * 变更类型, ADD:签约,DELETE:解约 + */ + @XStreamAlias("change_type") + private String changeType; + + /** + * 操作时间 + */ + @XStreamAlias("operate_time") + private String operateTime; + + /** + * 委托代扣协议ID + */ + @XStreamAlias("contract_id") + private String contractId; + + /** + * 协议到期时间,ADD会有,长期有效,忽略 + * 非必传 + */ + @XStreamAlias("contract_expired_time") + private String contractExpiredTime; + + /** + * DELETE时返回,0: 未解约,1:有效期过期自动解约,2:用户主动解约,3:商户api解约,4:商户平台解约,5:用户账号注销 + * 非必传 + */ + @XStreamAlias("contract_termination_mode") + private Integer contractTerminationMode; + + /** + * 请求序列号 + */ + @XStreamAlias("request_serial") + private Integer requestSerial; + + @Override + public void checkResult(WxPayService wxPayService, String signType, boolean checkSuccess) throws WxPayException { + //防止伪造成功通知 + if (WxPayConstants.ResultCode.SUCCESS.equals(getReturnCode()) && getSign() == null) { + throw new WxPayException("伪造的通知!"); + } + + super.checkResult(wxPayService, signType, checkSuccess); + } + + /** + * From xml wx sign notify result. + * + * @param xmlString the xml string + * @return the wx sign result + */ + public static WxSignStatusNotifyResult fromXML(String xmlString) { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxSignStatusNotifyResult.class); + WxSignStatusNotifyResult result = (WxSignStatusNotifyResult) xstream.fromXML(xmlString); + result.setXmlString(xmlString); + return result; + } + + @Override + protected void loadXml(Document d) { + contractCode = readXmlString(d, "contract_code"); + planId = readXmlString(d, "plan_id"); + openId = readXmlString(d, "openid"); + changeType = readXmlString(d, "change_type"); + operateTime = readXmlString(d, "operate_time"); + contractId = readXmlString(d, "contract_id"); + contractExpiredTime = readXmlString(d, "contract_expired_time"); + contractTerminationMode = readXmlInteger(d, "contract_termination_mode"); + requestSerial = readXmlInteger(d, "request_serial"); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxTerminationContractResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxTerminationContractResult.java new file mode 100644 index 0000000000..77067d0213 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxTerminationContractResult.java @@ -0,0 +1,57 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + * @author chenliang + * @date 2021-08-02 5:41 下午 + * + *
+ *   主动解约返回值
+ * 
+ */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WxTerminationContractResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 委托代扣协议ID + */ + @XStreamAlias("contractId") + private String contractId; + + /** + * 模板ID + */ + @XStreamAlias("plan_id") + private String planId; + + /** + * 签约协议号 + */ + @XStreamAlias("contract_code") + private String contractCode; + + + @Override + protected void loadXml(Document d) { + contractId = readXmlString(d, "contract_id"); + planId = readXmlString(d, "plan_id"); + contractCode = readXmlString(d, "contract_code"); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java new file mode 100644 index 0000000000..87cbfd1b71 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdNotifyResult.java @@ -0,0 +1,234 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyCoupon; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.converter.WxPayOrderNotifyResultConverter; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.util.SignUtils; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import me.chanjar.weixin.common.util.xml.XStreamInitializer; +import org.w3c.dom.Document; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author chenliang + * @date 2021-08-02 5:09 下午 + * + *
+ *   微信代扣扣款回调结果
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@XStreamAlias("xml") +public class WxWithholdNotifyResult extends BaseWxPayResult { + + private static final long serialVersionUID = 1L; + + /** + * 设备号 + * 非必传 + */ + @XStreamAlias("device_info") + private String deviceInfo; + + /** + * 用户标识 + */ + @XStreamAlias("openid") + private String openId; + + /** + * 用户子标识 + * 非必传 + */ + @XStreamAlias("sub_openid") + private String subOpenId; + + /** + * 是否关注公众号 + * 非必传 + */ + @XStreamAlias("is_subscribe") + private String isSubscribe; + + /** + * 付款银行 + */ + @XStreamAlias("bank_type") + private String bankType; + + /** + * 总金额 + */ + @XStreamAlias("total_fee") + private Integer totalFee; + + /** + * 货币类型 + * 非必传 + */ + @XStreamAlias("fee_type") + private String feeType; + + /** + * 现金支付金额 + */ + @XStreamAlias("cash_fee") + private Integer cashFee; + + /** + * 现金支付货币类型 + * 非必传 + */ + @XStreamAlias("cash_fee_type") + private String cashFeeType; + + /** + * 交易状态 + * SUCCESS : 支付成功,REFUND:转入退款,NOTPAY:未支付,CLOSED:已关闭,ACCEPT:已接收,PAY_FAIL:支付失败 + */ + @XStreamAlias("trade_state") + private String tradeState; + + /** + * 代金券或立减优惠金额 + * 非必传 + */ + @XStreamAlias("coupon_fee") + private Integer couponFee; + + /** + * 代金券或立减优惠使用数量 + */ + @XStreamAlias("coupon_count") + private Integer couponCount; + + private List couponList; + + + /** + * 微信支付单号 + */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + * 商户订单号 + */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + * 商家数据包 + */ + @XStreamAlias("attach") + private String attach; + + /** + * 支付完成时间 + */ + @XStreamAlias("time_end") + private String timeEnd; + + /** + * 委托代扣协议ID + */ + @XStreamAlias("contract_id") + private String contractId; + + + + @Override + public void checkResult(WxPayService wxPayService, String signType, boolean checkSuccess) throws WxPayException { + //防止伪造成功通知 + if (WxPayConstants.ResultCode.SUCCESS.equals(getReturnCode()) && getSign() == null) { + throw new WxPayException("伪造的通知!"); + } + + super.checkResult(wxPayService, signType, checkSuccess); + } + + /** + * From xml wx withhold notify result. + * + * @param xmlString the xml string + * @return the wx withhold result + */ + public static WxWithholdNotifyResult fromXML(String xmlString) { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxWithholdNotifyResult.class); + xstream.registerConverter(new WxPayOrderNotifyResultConverter(xstream.getMapper(), xstream.getReflectionProvider())); + WxWithholdNotifyResult result = (WxWithholdNotifyResult) xstream.fromXML(xmlString); + result.setXmlString(xmlString); + return result; + } + + @Override + public Map toMap() { + Map resultMap = SignUtils.xmlBean2Map(this); + if (this.getCouponCount() != null && this.getCouponCount() > 0) { + for (int i = 0; i < this.getCouponCount(); i++) { + WxPayOrderNotifyCoupon coupon = couponList.get(i); + resultMap.putAll(coupon.toMap(i)); + } + } + return resultMap; + } + + @Override + protected void loadXml(Document d) { + deviceInfo = readXmlString(d, "device_info"); + openId = readXmlString(d, "openid"); + isSubscribe = readXmlString(d, "is_subscribe"); + subOpenId = readXmlString(d, "sub_openid"); + bankType = readXmlString(d, "bank_type"); + totalFee = readXmlInteger(d, "total_fee"); + feeType = readXmlString(d, "fee_type"); + cashFee = readXmlInteger(d, "cash_fee"); + cashFeeType = readXmlString(d, "cash_fee_type"); + couponFee = readXmlInteger(d, "coupon_fee"); + couponCount = readXmlInteger(d, "coupon_count"); + transactionId = readXmlString(d, "transaction_id"); + outTradeNo = readXmlString(d, "out_trade_no"); + attach = readXmlString(d, "attach"); + timeEnd = readXmlString(d, "time_end"); + tradeState = readXmlString(d, "trade_state"); + contractId = readXmlString(d, "contract_id"); + + composeCoupons(); + } + + /** + * 通过xml组装couponList属性内容. + */ + protected void composeCoupons() { + if (this.couponCount == null || this.couponCount == 0) { + return; + } + this.couponList = new ArrayList(couponCount); + for (int i = 0; i < this.couponCount; i++) { + WxPayOrderNotifyCoupon coupon = new WxPayOrderNotifyCoupon(); + coupon.setCouponId(this.getXmlValue("xml/coupon_id_" + i)); + coupon.setCouponType(this.getXmlValue("xml/coupon_type_" + i)); + coupon.setCouponFee(this.getXmlValueAsInt("xml/coupon_fee_" + i)); + couponList.add(coupon); + } + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java new file mode 100644 index 0000000000..752bf7e64a --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdOrderQueryResult.java @@ -0,0 +1,182 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyCoupon; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.w3c.dom.Document; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * @author chenliang + * @date 2021-08-02 5:42 下午 + * + *
+ *   代扣订单查询结果
+ * 
+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +@XStreamAlias("xml") +public class WxWithholdOrderQueryResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 设备号 + * 非必传 + */ + @XStreamAlias("device_info") + private String deviceInfo; + + /** + * 用户标识 + */ + @XStreamAlias("openid") + private String openId; + + /** + * 是否关注公众号 + * 非必传 + */ + @XStreamAlias("is_subscribe") + private String isSubscribe; + + /** + * 交易类型 + */ + @XStreamAlias("trade_type") + private String tradeType; + + /** + * 交易状态 + * SUCCESS : 支付成功,REFUND:转入退款,NOTPAY:未支付,CLOSED:已关闭,ACCEPT:已接收,PAY_FAIL:支付失败 + */ + @XStreamAlias("trade_state") + private String tradeState; + + /** + * 付款银行 + */ + @XStreamAlias("bank_type") + private String bankType; + + /** + * 总金额 + */ + @XStreamAlias("total_fee") + private Integer totalFee; + + /** + * 货币类型 + * 非必传 + */ + @XStreamAlias("fee_type") + private String feeType; + + /** + * 现金支付金额 + */ + @XStreamAlias("cash_fee") + private Integer cashFee; + + /** + * 现金支付货币类型 + * 非必传 + */ + @XStreamAlias("cash_fee_type") + private String cashFeeType; + + /** + * 代金券或立减优惠金额 + * 非必传 + */ + @XStreamAlias("coupon_fee") + private Integer couponFee; + + /** + * 代金券或立减优惠使用数量 + */ + @XStreamAlias("coupon_count") + private Integer couponCount; + + private List couponList; + + + /** + * 微信支付单号 + */ + @XStreamAlias("transaction_id") + private String transactionId; + + /** + * 商户订单号 + */ + @XStreamAlias("out_trade_no") + private String outTradeNo; + + /** + * 商家数据包 + */ + @XStreamAlias("attach") + private String attach; + + /** + * 支付完成时间 + */ + @XStreamAlias("time_end") + private String timeEnd; + + /** + * 交易状态描述 + * 例如:支付失败,请重新下单 + */ + @XStreamAlias("trade_state_desc") + private String tradeStateDesc; + + + /** + * 通过xml组装couponList属性内容. + */ + protected void composeCoupons() { + if (this.couponCount == null || this.couponCount == 0) { + return; + } + this.couponList = new ArrayList(couponCount); + for (int i = 0; i < this.couponCount; i++) { + WxPayOrderNotifyCoupon coupon = new WxPayOrderNotifyCoupon(); + coupon.setCouponId(this.getXmlValue("xml/coupon_id_" + i)); + coupon.setCouponType(this.getXmlValue("xml/coupon_type_" + i)); + coupon.setCouponFee(this.getXmlValueAsInt("xml/coupon_fee_" + i)); + couponList.add(coupon); + } + } + + @Override + protected void loadXml(Document d) { + deviceInfo = readXmlString(d, "device_info"); + openId = readXmlString(d, "openid"); + isSubscribe = readXmlString(d, "is_subscribe"); + bankType = readXmlString(d, "bank_type"); + totalFee = readXmlInteger(d, "total_fee"); + feeType = readXmlString(d, "fee_type"); + cashFee = readXmlInteger(d, "cash_fee"); + tradeType = readXmlString(d, "trade_type"); + cashFeeType = readXmlString(d, "cash_fee_type"); + couponFee = readXmlInteger(d, "coupon_fee"); + couponCount = readXmlInteger(d, "coupon_count"); + transactionId = readXmlString(d, "transaction_id"); + outTradeNo = readXmlString(d, "out_trade_no"); + attach = readXmlString(d, "attach"); + timeEnd = readXmlString(d, "time_end"); + tradeState = readXmlString(d, "trade_state"); + tradeStateDesc = readXmlString(d, "trade_state_desc"); + + composeCoupons(); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdResult.java new file mode 100644 index 0000000000..a2c4dcf055 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxWithholdResult.java @@ -0,0 +1,44 @@ +package com.github.binarywang.wxpay.bean.result; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import org.w3c.dom.Document; + +import java.io.Serializable; + +/** + * @author chenliang + * @date 2021-08-02 5:39 下午 + * + *
+ *   委托扣款返回值
+ * 
+ */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class WxWithholdResult extends BaseWxPayResult implements Serializable { + + private static final long serialVersionUID = 1L; + + + /** + * 临时字段 + */ + @XStreamAlias("temp") + private String temp; + + + @Override + protected void loadXml(Document d) { + temp = readXmlString(d, "temp"); + } + + @Override + public String toString() { + return WxGsonBuilder.create().toJson(this); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java new file mode 100644 index 0000000000..be76b34c47 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxEntrustPapService.java @@ -0,0 +1,157 @@ +package com.github.binarywang.wxpay.service; + +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.exception.WxPayException; + +/** + *
+ *   微信签约代扣相关接口.
+ *   https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter2_8.shtml
+ *  
+ * + * @author chenliang + * @date 2021-08-02 4:50 下午 + */ +public interface WxEntrustPapService { + + /** + * + *
+   *   获取公众号纯签约链接,
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_1.shtml
+   *   该接口返回一个签约链接,该链接只能在微信内打开
+   * 
+ * + * @param wxMpEntrustRequest + * @return + * @throws WxPayException + */ + String mpSign(WxMpEntrustRequest wxMpEntrustRequest) throws WxPayException; + + /** + * + *
+   *   获取小程序纯签约参数json
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_3.shtml
+   *   返回一个json 前端用来拉起一个新的签约小程序进行签约
+   * 
+ * + * + * @param wxMaEntrustRequest + * @return + * @throws WxPayException + */ + String maSign(WxMaEntrustRequest wxMaEntrustRequest) throws WxPayException; + + /** + * + *
+   *   获取h5纯签约支付跳转链接
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_4.shtml
+   *   返回一个签约链接  在浏览器请求链接拉起微信
+   * 
+ * + * @param wxH5EntrustRequest + * @return + * @throws WxPayException + */ + WxH5EntrustResult h5Sign(WxH5EntrustRequest wxH5EntrustRequest) throws WxPayException; + + /** + * + *
+   *   支付中签约
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_5.shtml
+   *   请求微信 若微信内请求 需要构造json返回,
+   *   若h5请求 直接使用mweb_url 链接即可拉起微信
+   * 
+ * + * @param wxPayEntrustRequest + * @return + * @throws WxPayException + */ + WxPayEntrustResult paySign(WxPayEntrustRequest wxPayEntrustRequest) throws WxPayException; + + /** + * 申请扣款 + *
+   *   申请扣款
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_8.shtml
+   *   请求微信发起委托扣款,扣款额度和次数由使用的签约模板限制,
+   *   该扣款接口是立即扣款 无延时 扣款前无消息通知。
+   *
+   *   • 特殊情况:周期扣费为通知后24小时扣费方式情况下,如果用户为首次签约(包含解约后重新签约),
+   *   从用户签约成功时间开始算,商户在12小时内发起的扣款,会被立即执行,无延迟。商户超过12小时以后发起的扣款,都按24小时扣费规则执行
+   * 
+ * + * @param wxWithholdRequest + * @return + * @throws WxPayException + */ + WxWithholdResult withhold(WxWithholdRequest wxWithholdRequest) throws WxPayException; + + /** + * 预扣费通知 + *
+   *   预扣费接口
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_10.shtml
+   *   商户进行委托代扣扣费前需要在可通知时间段内调用「预扣费通知」的接口为用户发送扣费提醒,
+   *   并设定扣费持续天数和预计扣费金额,经过扣费等待期后,在可扣费期内可发起扣费,扣款金额不能高于预计扣费金额,
+   *   扣费失败可主动发起重试扣费(重试次数由其他规则限制),直到扣费成功,或者可扣费期结束。
+   *   商户只能在北京时间每天 6:00~22:00调用「预扣费通知」
+   * 
+ * + * @param wxPreWithholdRequest + * @return + * @throws WxPayException + */ + String preWithhold(WxPreWithholdRequest wxPreWithholdRequest) throws WxPayException; + + /** + * 签约状态查询 + *
+   *   签约状态查询
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_7.shtml
+   *   查询签约关系接口提供单笔签约关系查询。
+   * 
+ * + * @param wxSignQueryRequest + * @return + * @throws WxPayException + */ + WxSignQueryResult querySign(WxSignQueryRequest wxSignQueryRequest) throws WxPayException; + + + /** + * 申请解约 + *
+   *   申请解约
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter3_9.shtml
+   *   商户与用户的签约关系有误或者商户主动要求与用户解除之前的签约协议时可调用此接口完成解约。
+   *   商户可以在商户后台(pay.weixin.qq.com)设置解约回调地址,当发生解约关系的时候,微信服务器会向此地址通知解约信息,内容与签约返回一致
+   * 
+ * + * @param wxTerminatedContractRequest + * @return + * @throws WxPayException + */ + WxTerminationContractResult terminationContract(WxTerminatedContractRequest wxTerminatedContractRequest) throws WxPayException; + + /** + * + *
+   *   查询代扣订单
+   *   详见:https://pay.weixin.qq.com/wiki/doc/api/wxpay_v2/papay/chapter4_5.shtml
+   *   该接口仅提供微信扣款服务申请扣款接口创建的订单进行查询,商户可以通过该接口主动查询微信代扣订单状态,完成下一步的业务逻辑。
+   *   ACCEPT等待扣款:为24小时延时扣费场景下独有的,当没有达到24小时前一直是这种状态;
+   *   NOTPAY未支付:系统已经启动扣款流程,这个状态只是瞬间状态,很快会进入终态(SUCCESS、PAY_FAIL)
+   *
+   * 
+ * + * @param wxWithholdOrderQueryRequest + * @return + * @throws WxPayException + */ + WxWithholdOrderQueryResult papOrderQuery(WxWithholdOrderQueryRequest wxWithholdOrderQueryRequest) throws WxPayException; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java index e11f65c139..9b42bd75be 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java @@ -173,6 +173,12 @@ public interface WxPayService { */ InputStream downloadV3(String url) throws WxPayException; + /** + * 获取微信签约代扣服务类 + * @return entrust service + */ + WxEntrustPapService getWxEntrustPapService(); + /** * 获取企业付款服务类. * diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java index 95919ed254..a80a7f4527 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java @@ -74,6 +74,8 @@ public abstract class BaseWxPayServiceImpl implements WxPayService { private final MarketingMediaService marketingMediaService = new MarketingMediaServiceImpl(this); private final MarketingFavorService marketingFavorService = new MarketingFavorServiceImpl(this); private final MarketingBusiFavorService marketingBusiFavorService = new MarketingBusiFavorServiceImpl(this); + private final WxEntrustPapService wxEntrustPapService = new WxEntrustPapServiceImpl(this); + protected Map configMap; @@ -137,6 +139,11 @@ public void setEntPayService(EntPayService entPayService) { this.entPayService = entPayService; } + @Override + public WxEntrustPapService getWxEntrustPapService() { + return wxEntrustPapService; + } + @Override public WxPayConfig getConfig() { if (this.configMap.size() == 1) { diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java new file mode 100644 index 0000000000..7555425a81 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceImpl.java @@ -0,0 +1,139 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.constant.WxPayConstants; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxEntrustPapService; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.util.SignUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.net.URLEncoder; + +/** + * @author chenliang + * @date 2021-08-02 4:53 下午 + */ +@Slf4j +@RequiredArgsConstructor +public class WxEntrustPapServiceImpl implements WxEntrustPapService { + + private final WxPayService payService; + + + @Override + public String mpSign(WxMpEntrustRequest wxMpEntrustRequest) throws WxPayException { + StringBuilder signStrTemp = new StringBuilder(payService.getPayBaseUrl() + "/papay/entrustweb"); + signStrTemp.append("?appid=").append(wxMpEntrustRequest.getAppid()); + signStrTemp.append("&contract_code=").append(wxMpEntrustRequest.getContractCode()); + signStrTemp.append("&contract_display_account=").append(URLEncoder.encode(wxMpEntrustRequest.getContractDisplayAccount())); + signStrTemp.append("&mch_id=").append(wxMpEntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxMpEntrustRequest.getNotifyUrl())); + signStrTemp.append("&plan_id=").append(wxMpEntrustRequest.getPlanId()).append("&outerid=").append(URLEncoder.encode(wxMpEntrustRequest.getOuterId())); + signStrTemp.append("&request_serial=").append(wxMpEntrustRequest.getRequestSerial()).append("×tamp=").append(wxMpEntrustRequest.getTimestamp()); + signStrTemp.append("&version=").append(wxMpEntrustRequest.getVersion()).append("&return_web=").append(wxMpEntrustRequest.getReturnWeb()).append("&sign=").append(wxMpEntrustRequest.getSign()); + + return signStrTemp.toString(); + } + + @Override + public String maSign(WxMaEntrustRequest wxMaEntrustRequest) throws WxPayException { + wxMaEntrustRequest.checkAndSign(payService.getConfig()); + wxMaEntrustRequest.setNotifyUrl(URLEncoder.encode(wxMaEntrustRequest.getNotifyUrl())); + return wxMaEntrustRequest.toString(); + } + + @Override + public WxH5EntrustResult h5Sign(WxH5EntrustRequest wxH5EntrustRequest) throws WxPayException { + wxH5EntrustRequest.checkAndSign(payService.getConfig()); + + String sign = SignUtils.createSign(wxH5EntrustRequest, WxPayConstants.SignType.HMAC_SHA256, payService.getConfig().getMchKey(), null); + /** + * https://api.mch.weixin.qq.com/papay/h5entrustweb?appid=wxxxxx&contract_code=001 + * &contract_display_account=name1&mch_id=1223816102¬ify_url=www.qq.com%2Ftest%2Fpapay&plan_id=106 + * &request_serial=123&return_appid= wxcbda96de0b165542&clientip=12.1.1.12×tamp=1414488825 + * &version=1.0&sign= 130C7B07DD3B8074F7BF8BEF5C9A86487A1C57478F8C55587876B9C782F72036 + */ + String url = payService.getPayBaseUrl() + "/papay/h5entrustweb"; + + StringBuilder strBuilder = new StringBuilder(url); + strBuilder.append("?appid=").append(wxH5EntrustRequest.getAppid()); + strBuilder.append("&contract_code=").append(wxH5EntrustRequest.getContractCode()); + strBuilder.append("&contract_display_account=").append(URLEncoder.encode(wxH5EntrustRequest.getContractDisplayAccount())); + strBuilder.append("&mch_id=").append(wxH5EntrustRequest.getMchId()).append("¬ify_url=").append(URLEncoder.encode(wxH5EntrustRequest.getNotifyUrl())); + strBuilder.append("&plan_id=").append(wxH5EntrustRequest.getPlanId()).append("&outerid=").append(URLEncoder.encode(wxH5EntrustRequest.getOuterId())); + strBuilder.append("&return_appid=").append(wxH5EntrustRequest.getReturnAppid()); + strBuilder.append("&clientip=").append(wxH5EntrustRequest.getClientIp()); + strBuilder.append("&request_serial=").append(wxH5EntrustRequest.getRequestSerial()).append("×tamp=").append(wxH5EntrustRequest.getTimestamp()); + strBuilder.append("&version=").append(wxH5EntrustRequest.getVersion()).append("&sign=").append(sign); + + log.debug("h5纯签约请求URL:{}", strBuilder.toString()); + + String responseContent = payService.getV3(strBuilder.toString()); + WxH5EntrustResult result = BaseWxPayResult.fromXML(responseContent, WxH5EntrustResult.class); + result.checkResult(payService, wxH5EntrustRequest.getSignType(), true); + return result; + } + + @Override + public WxPayEntrustResult paySign(WxPayEntrustRequest wxPayEntrustRequest) throws WxPayException { + wxPayEntrustRequest.checkAndSign(payService.getConfig()); + + String url = payService.getPayBaseUrl() + "/pay/contractorder"; + String responseContent = payService.post(url, wxPayEntrustRequest.toXML(), false); + WxPayEntrustResult result = BaseWxPayResult.fromXML(responseContent, WxPayEntrustResult.class); + result.checkResult(payService, wxPayEntrustRequest.getSignType(), true); + + return result; + } + + @Override + public WxWithholdResult withhold(WxWithholdRequest wxWithholdRequest) throws WxPayException { + wxWithholdRequest.checkAndSign(payService.getConfig()); + String url = payService.getPayBaseUrl() + "/pay/pappayapply"; + String responseContent = payService.post(url, wxWithholdRequest.toXML(), false); + WxWithholdResult result = BaseWxPayResult.fromXML(responseContent, WxWithholdResult.class); + result.checkResult(payService, wxWithholdRequest.getSignType(), true); + return result; + } + + @Override + public String preWithhold(WxPreWithholdRequest wxPreWithholdRequest) throws WxPayException { + String requestParam = WxGsonBuilder.create().toJson(wxPreWithholdRequest); + String url = payService.getPayBaseUrl() + "/v3/papay/contracts/%s/notify"; // %s为{contract_id} + String httpResponse = payService.postV3(String.format(url, wxPreWithholdRequest.getContractId()), requestParam); + return httpResponse; + } + + @Override + public WxSignQueryResult querySign(WxSignQueryRequest wxSignQueryRequest) throws WxPayException { + wxSignQueryRequest.checkAndSign(payService.getConfig()); + String url = payService.getPayBaseUrl() + "/papay/querycontract"; + String responseContent = payService.post(url, wxSignQueryRequest.toXML(), false); + WxSignQueryResult result = BaseWxPayResult.fromXML(responseContent, WxSignQueryResult.class); + result.checkResult(payService, wxSignQueryRequest.getSignType(), true); + return result; + } + + @Override + public WxTerminationContractResult terminationContract(WxTerminatedContractRequest wxTerminatedContractRequest) throws WxPayException { + wxTerminatedContractRequest.checkAndSign(payService.getConfig()); + String url = payService.getPayBaseUrl() + "/papay/deletecontract"; + String responseContent = payService.post(url, wxTerminatedContractRequest.toXML(), false); + WxTerminationContractResult terminationContractResult = BaseWxPayResult.fromXML(responseContent, WxTerminationContractResult.class); + terminationContractResult.checkResult(payService, wxTerminatedContractRequest.getSignType(), true); + return terminationContractResult; + } + + @Override + public WxWithholdOrderQueryResult papOrderQuery(WxWithholdOrderQueryRequest wxWithholdOrderQueryRequest) throws WxPayException { + wxWithholdOrderQueryRequest.checkAndSign(payService.getConfig()); + String url = payService.getPayBaseUrl() + "/pay/paporderquery"; + String responseContent = payService.post(url, wxWithholdOrderQueryRequest.toXML(), false); + WxWithholdOrderQueryResult wxWithholdOrderQueryResult = BaseWxPayResult.fromXML(responseContent, WxWithholdOrderQueryResult.class); + wxWithholdOrderQueryResult.checkResult(payService, wxWithholdOrderQueryRequest.getSignType(), true); + return wxWithholdOrderQueryResult; + } +} diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceTest.java new file mode 100644 index 0000000000..9323108963 --- /dev/null +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxEntrustPapServiceTest.java @@ -0,0 +1,236 @@ +package com.github.binarywang.wxpay.service.impl; + +import com.github.binarywang.wxpay.bean.request.*; +import com.github.binarywang.wxpay.bean.result.*; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.testbase.ApiTestModule; +import com.google.common.base.Joiner; +import com.google.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +/** + * @author chenliang + * @date 2021-08-02 6:45 下午 + */ +@Test +@Guice(modules = ApiTestModule.class) +public class WxEntrustPapServiceTest { + + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + + @Inject + private WxPayService payService; + + /** + * 公众号纯签约 + */ + @Test + public void testMpSign() { + + String contractCode = "222200002222"; + String displayAccount = Joiner.on("").join("陈*", "(", "10000014", ")"); + WxMpEntrustRequest wxMpEntrust = WxMpEntrustRequest.newBuilder() + .planId("142323") //模板ID:跟微信申请 + .contractCode(contractCode) + .contractDisplayAccount(displayAccount) + .notifyUrl("http://domain.com/api/wxpay/sign/callback.do") + .requestSerial(6L) + //.returnWeb(1) + .version("1.0") + .timestamp(String.valueOf(System.currentTimeMillis() / 1000)) + .outerId(displayAccount) + .build(); + + String url = null; + try { + url = this.payService.getWxEntrustPapService().mpSign(wxMpEntrust); + } catch (WxPayException e) { + e.printStackTrace(); + } + logger.info(url); + } + + /** + * 小程序纯签约 + */ + @Test + public void testMaSign() { + String contractCode = "222220000022222"; + String displayAccount = Joiner.on("").join("陈*", "(", "10000001", ")"); + + WxMaEntrustRequest wxMaEntrustRequest = WxMaEntrustRequest.newBuilder() + .contractCode(contractCode) + .contractDisplayAccount(contractCode) + .notifyUrl("http://domain.com/api/wxpay/sign/callback.do") + .outerId(displayAccount) + .planId("141535") + .requestSerial(2L) + .timestamp(String.valueOf(System.currentTimeMillis() / 1000)) + .build(); + + try { + String url = this.payService.getWxEntrustPapService().maSign(wxMaEntrustRequest); + logger.info(url); + } catch (WxPayException e) { + e.printStackTrace(); + } + } + + /** + * h5纯签约 + */ + @Test + public void testH5Sign() { + String contractCode = "222111122222"; + String displayAccount = Joiner.on("").join("陈*", "(", "100000000", ")"); + + WxH5EntrustRequest wxH5EntrustRequest = WxH5EntrustRequest.newBuilder() + .requestSerial(2L) + .clientIp("127.0.0.1") + .contractCode(contractCode) + .contractDisplayAccount(displayAccount) + .notifyUrl("http://domain.com/api/wxpay/sign/callback.do") + .planId("141535") + .returnAppid("1") + .timestamp(String.valueOf(System.currentTimeMillis() / 1000)) + .version("1.0") + .outerId(displayAccount) + .build(); + + try { + WxH5EntrustResult wxH5EntrustResult = this.payService.getWxEntrustPapService().h5Sign(wxH5EntrustRequest); + logger.info(wxH5EntrustResult.toString()); + } catch (WxPayException e) { + e.printStackTrace(); + } + } + + @Test + public void testPaySign() { + String contractCode = "2222211110000222"; + String displayAccount = Joiner.on("").join("陈*", "(", "10000005", ")"); + String outTradeNo = "11100111101"; + + WxPayEntrustRequest wxPayEntrustRequest = WxPayEntrustRequest.newBuilder() + .attach("local") + .body("产品名字") + .contractAppId(this.payService.getConfig().getAppId()) + .contractCode(contractCode) + .contractDisplayAccount(displayAccount) + .contractMchId(this.payService.getConfig().getMchId()) + //签约回调 + .contractNotifyUrl("http://domain.com/api/wxpay/sign/callback.do") + .detail("产品是好") + .deviceInfo("oneplus 7 pro") + //.goodsTag() + //.limitPay() + //支付回调 + .notifyUrl("http://domain.com/api/wxpay/pay/callback.do") + .openId("oIvLdt8Q-_aKy4Vo6f4YI6gsIhMc") //openId + .outTradeNo(outTradeNo) + .planId("141535") + //.productId() + .requestSerial(3L) + .spbillCreateIp("127.0.0.1") + //.timeExpire() + //.timeStart() + .totalFee(1) + .tradeType("MWEB") + .contractOuterId(displayAccount) + .build(); + + try { + WxPayEntrustResult wxPayEntrustResult = this.payService.getWxEntrustPapService().paySign(wxPayEntrustRequest); + logger.info(wxPayEntrustResult.toString()); + } catch (WxPayException e) { + e.printStackTrace(); + } + } + + @Test + public void testWithhold() { + String outTradeNo = "101010101"; + WxWithholdRequest withholdRequest = WxWithholdRequest.newBuilder() + .attach("local") + .body("产品名字") + .contractId("202011065409471222") // 微信返回的签约协议号 + .detail("产品描述") + .feeType("CNY") + //.goodsTag() + .notifyUrl("http://domain.com/api/wxpay/withhold/callback.do") + .outTradeNo(outTradeNo) + .spbillCreateIp("127.0.0.1") + .totalFee(1) + .tradeType("PAP") + .build(); + + try { + WxWithholdResult wxWithholdResult = this.payService.getWxEntrustPapService().withhold(withholdRequest); + logger.info(wxWithholdResult.toString()); + } catch (WxPayException e) { + e.printStackTrace(); + } + } + + @Test + public void testPreWithhold() { + WxPreWithholdRequest.EstimateAmount estimateAmount = new WxPreWithholdRequest.EstimateAmount(); + estimateAmount.setAmount(1); + estimateAmount.setCurrency("CNY"); + + WxPreWithholdRequest wxPreWithholdRequest = WxPreWithholdRequest.newBuilder() + .appId("wx73dssxxxxxx") + .contractId("202010275173070001") + .estimateAmount(estimateAmount) + .mchId("1600010102") + .build(); + + try { + String httpResponseModel = this.payService.getWxEntrustPapService().preWithhold(wxPreWithholdRequest); + logger.info(httpResponseModel); + } catch (WxPayException e) { + e.printStackTrace(); + } + } + + @Test + public void testQuerySign() { + String outTradeNo = "1212121212"; + + WxSignQueryRequest wxSignQueryRequest = WxSignQueryRequest.newBuilder() + //.contractId("202010275173073211") + .contractCode(outTradeNo) + .planId(1432112) + .version("1.0") + .build(); + + try { + WxSignQueryResult wxSignQueryResult = this.payService.getWxEntrustPapService().querySign(wxSignQueryRequest); + logger.info(wxSignQueryResult.toString()); + } catch (WxPayException e) { + logger.info("异常码:" + e.getErrCode()); + logger.info("异常:" + e); + } + } + + @Test + public void testTerminationContract() { + WxTerminatedContractRequest wxTerminatedContractRequest = WxTerminatedContractRequest.newBuilder() + .contractId("202010275173070231") + .contractTerminationRemark("测试解约") + .version("1.0") + .build(); + + try { + WxTerminationContractResult wxTerminationContractResult = this.payService.getWxEntrustPapService().terminationContract(wxTerminatedContractRequest); + logger.info(wxTerminationContractResult.toString()); + } catch (WxPayException e) { + logger.error(e.getMessage()); + } + } +}