From fbce241d00c0853b5273475073dc247ccceb649c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pursuer=E4=B8=B6?= Date: Fri, 16 Jun 2023 16:22:59 +0800 Subject: [PATCH] =?UTF-8?q?:art:=20#3056=20=E3=80=90=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E6=94=AF=E4=BB=98=E3=80=91=E4=BC=98=E5=8C=96=E6=94=AF=E4=BB=98?= =?UTF-8?q?/=E9=80=80=E6=AC=BE=E7=BB=93=E6=9E=9C=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=AF=B9V3=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E5=95=86=E7=9A=84=E4=B8=8B=E5=8D=95/?= =?UTF-8?q?=E9=80=80=E6=AC=BE=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bean/notify/WxPayBaseNotifyV3Result.java | 30 + ...V3Result.java => WxPayNotifyV3Result.java} | 2 +- .../notify/WxPayPartnerNotifyV3Result.java | 568 ++++++++++++++++ .../WxPayPartnerRefundNotifyV3Result.java | 230 +++++++ .../notify/WxPayRefundNotifyV3Result.java | 2 +- .../request/WxPayPartnerRefundV3Request.java | 55 ++ .../WxPayPartnerUnifiedOrderV3Request.java | 610 ++++++++++++++++++ .../wxpay/service/WxPayService.java | 65 +- .../service/impl/BaseWxPayServiceImpl.java | 75 ++- .../impl/BaseWxPayServiceImplTest.java | 2 +- 10 files changed, 1609 insertions(+), 30 deletions(-) create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java rename weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/{WxPayOrderNotifyV3Result.java => WxPayNotifyV3Result.java} (99%) create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerNotifyV3Result.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerRefundNotifyV3Result.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java create mode 100644 weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerUnifiedOrderV3Request.java diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java new file mode 100644 index 0000000000..86915d0956 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayBaseNotifyV3Result.java @@ -0,0 +1,30 @@ +package com.github.binarywang.wxpay.bean.notify; + +/** + * 微信支付公用回调 + * + * @author Pursuer + * @version 1.0 + * @date 2023/6/15 + */ +public interface WxPayBaseNotifyV3Result { + /** + * 设置原始数据 + * + * @param rawData 原始数据 + * @author Pursuer + * @date 2023/6/15 + * @since 1.0 + **/ + void setRawData(OriginNotifyResponse rawData); + + /** + * 解密后的数据 + * + * @param data 解密后的数据 + * @author Pursuer + * @date 2023/6/15 + * @since 1.0 + **/ + void setResult(T data); +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Result.java similarity index 99% rename from weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyV3Result.java rename to weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Result.java index 549e2af16c..22ed94fca3 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayOrderNotifyV3Result.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayNotifyV3Result.java @@ -15,7 +15,7 @@ */ @Data @NoArgsConstructor -public class WxPayOrderNotifyV3Result implements Serializable { +public class WxPayNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result { private static final long serialVersionUID = -1L; /** * 源数据 diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerNotifyV3Result.java new file mode 100644 index 0000000000..2a33910fd5 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerNotifyV3Result.java @@ -0,0 +1,568 @@ +package com.github.binarywang.wxpay.bean.notify; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 微信支付服务商下单回调 + * 文档见:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_5.shtml + * + * @author Guo Shuai + * @version 1.0 + * @date 2023/3/2 + */ +@Data +@NoArgsConstructor +public class WxPayPartnerNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result { + private static final long serialVersionUID = -1L; + /** + * 源数据 + */ + private OriginNotifyResponse rawData; + /** + * 解密后的数据 + */ + private DecryptNotifyResult result; + + @Data + @NoArgsConstructor + public static class DecryptNotifyResult implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:服务商应用ID
+     * 变量名:spAppid
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  由微信生成的应用ID,全局唯一。请求统一下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的APPID
+     *  示例值:wxd678efh567hg6787
+     * 
+ */ + @SerializedName(value = "sp_appid") + protected String spAppid; + /** + *
+     * 字段名:服务商商户号
+     * 变量名:spMchid
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  服务商商户号,由微信支付生成并下发。
+     *  示例值:1230000109
+     * 
+ */ + @SerializedName(value = "sp_mchid") + protected String spMchid; + /** + *
+     * 字段名:子商户应用ID
+     * 变量名:subAppid
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  由微信生成的应用ID,全局唯一。请求统一下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的APPID
+     *  示例值:wxd678efh567hg6787
+     * 
+ */ + @SerializedName(value = "sub_appid") + protected String subAppid; + /** + *
+     * 字段名:子商户商户号
+     * 变量名:subMchid
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  子商户商户号,由微信支付生成并下发。
+     *  示例值:1230000109
+     * 
+ */ + @SerializedName(value = "sub_mchid") + protected String subMchid; + /** + *
+     * 字段名:商户订单号
+     * 变量名:out_trade_no
+     * 是否必填:是
+     * 类型:string[6,32]
+     * 描述:
+     *  商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一。
+     *  特殊规则:最小字符长度为6
+     *  示例值:1217752501201407033233368018
+     * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + /** + *
+     * 字段名:微信支付订单号
+     * 变量名:transaction_id
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  微信支付系统生成的订单号。
+     *  示例值:1217752501201407033233368018
+     * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + /** + *
+     * 字段名:交易类型
+     * 变量名:trade_type
+     * 是否必填:是
+     * 类型:string[1,16]
+     * 描述:
+     *  交易类型,枚举值:
+     *  JSAPI:公众号支付
+     *  NATIVE:扫码支付
+     *  APP:APP支付
+     *  MICROPAY:付款码支付
+     *  MWEB:H5支付
+     *  FACEPAY:刷脸支付
+     *  示例值:MICROPAY
+     * 
+ */ + @SerializedName(value = "trade_type") + private String tradeType; + /** + *
+     * 字段名:交易状态
+     * 变量名:trade_state
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  交易状态,枚举值:
+     *  SUCCESS:支付成功
+     *  REFUND:转入退款
+     *  NOTPAY:未支付
+     *  CLOSED:已关闭
+     *  REVOKED:已撤销(付款码支付)
+     *  USERPAYING:用户支付中(付款码支付)
+     *  PAYERROR:支付失败(其他原因,如银行返回失败)
+     *  示例值:SUCCESS
+     * 
+ */ + @SerializedName(value = "trade_state") + private String tradeState; + /** + *
+     * 字段名:交易状态描述
+     * 变量名:trade_state_desc
+     * 是否必填:是
+     * 类型:string[1,256]
+     * 描述:
+     *  交易状态描述
+     *  示例值:支付成功
+     * 
+ */ + @SerializedName(value = "trade_state_desc") + private String tradeStateDesc; + /** + *
+     * 字段名:付款银行
+     * 变量名:bank_type
+     * 是否必填:是
+     * 类型:string[1,16]
+     * 描述:
+     *  银行类型,采用字符串类型的银行标识。银行标识请参考《银行类型对照表》https://pay.weixin.qq.com/wiki/doc/apiv3/terms_definition/chapter1_1_3.shtml#part-6
+     *  示例值:CMC
+     * 
+ */ + @SerializedName(value = "bank_type") + private String bankType; + /** + *
+     * 字段名:附加数据
+     * 变量名:attach
+     * 是否必填:否
+     * 类型:string[1,128]
+     * 描述:
+     *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+     *  示例值:自定义数据
+     * 
+ */ + @SerializedName(value = "attach") + private String attach; + /** + *
+     * 字段名:支付完成时间
+     * 变量名:success_time
+     * 是否必填:是
+     * 类型:string[1,64]
+     * 描述:
+     *  支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+     *  示例值:2018-06-08T10:34:56+08:00
+     * 
+ */ + @SerializedName(value = "success_time") + private String successTime; + /** + *
+     * 字段名:支付者
+     * 变量名:payer
+     * 是否必填:是
+     * 类型:object
+     * 描述:
+     *  支付者信息
+     * 
+ */ + private Payer payer; + /** + *
+     * 字段名:订单金额
+     * 变量名:amount
+     * 是否必填:否
+     * 类型:object
+     * 描述:
+     *  订单金额信息
+     * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + /** + *
+     * 字段名:场景信息
+     * 变量名:scene_info
+     * 是否必填:否
+     * 类型:object
+     * 描述:
+     *  支付场景信息描述
+     * 
+ */ + @SerializedName(value = "scene_info") + private SceneInfo sceneInfo; + /** + *
+     * 字段名:优惠功能
+     * 变量名:promotion_detail
+     * 是否必填:否
+     * 类型:array
+     * 描述:
+     *  优惠功能,享受优惠时返回该字段。
+     * 
+ */ + @SerializedName(value = "promotion_detail") + private List promotionDetails; + } + @Data + @NoArgsConstructor + public static class Payer implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:用户标识
+     * 变量名:openid
+     * 是否必填:是
+     * 类型:string[1,128]
+     * 描述:
+     *  用户在直连商户appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "openid") + private String openid; + } + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:总金额
+     * 变量名:total
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  订单总金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "total") + private Integer total; + /** + *
+     * 字段名:用户支付金额
+     * 变量名:payer_total
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  用户支付金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "payer_total") + private Integer payerTotal; + /** + *
+     * 字段名:货币类型
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string[1,16]
+     * 描述:
+     *  CNY:人民币,境内商户号仅支持人民币。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + /** + *
+     * 字段名:用户支付币种
+     * 变量名:payer_currency
+     * 是否必填:否
+     * 类型:string[1,16]
+     * 描述:
+     *  用户支付币种
+     *  示例值: CNY
+     * 
+ */ + @SerializedName(value = "payer_currency") + private String payerCurrency; + } + + @Data + @NoArgsConstructor + public static class SceneInfo implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:商户端设备号
+     * 变量名:device_id
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  终端设备号(门店号或收银设备ID)。
+     *  示例值:013467007045764
+     * 
+ */ + @SerializedName(value = "device_id") + private String deviceId; + } + + @Data + @NoArgsConstructor + public static class PromotionDetail implements Serializable { + /** + *
+     * 字段名:券ID
+     * 变量名:coupon_id
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  券ID
+     *  示例值:109519
+     * 
+ */ + @SerializedName(value = "coupon_id") + private String couponId; + /** + *
+     * 字段名:优惠名称
+     * 变量名:name
+     * 是否必填:否
+     * 类型:string[1,64]
+     * 描述:
+     *  优惠名称
+     *  示例值:单品惠-6
+     * 
+ */ + @SerializedName(value = "name") + private String name; + /** + *
+     * 字段名:优惠范围
+     * 变量名:scope
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  GLOBAL:全场代金券
+     *  SINGLE:单品优惠
+     *  示例值:GLOBAL
+     * 
+ */ + @SerializedName(value = "scope") + private String scope; + /** + *
+     * 字段名:优惠类型
+     * 变量名:type
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  CASH:充值
+     *  NOCASH:预充值
+     *  示例值:CASH
+     * 
+ */ + @SerializedName(value = "type") + private String type; + /** + *
+     * 字段名:优惠券面额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  优惠券面额
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + /** + *
+     * 字段名:活动ID
+     * 变量名:stock_id
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  活动ID
+     *  示例值:931386
+     * 
+ */ + @SerializedName(value = "stock_id") + private String stockId; + /** + *
+     * 字段名:微信出资
+     * 变量名:wechatpay_contribute
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  微信出资,单位为分
+     *  示例值:0
+     * 
+ */ + @SerializedName(value = "wechatpay_contribute") + private Integer wechatpayContribute; + /** + *
+     * 字段名:商户出资
+     * 变量名:merchant_contribute
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  商户出资,单位为分
+     *  示例值:0
+     * 
+ */ + @SerializedName(value = "merchant_contribute") + private Integer merchantContribute; + /** + *
+     * 字段名:其他出资
+     * 变量名:other_contribute
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  其他出资,单位为分
+     *  示例值:0
+     * 
+ */ + @SerializedName(value = "other_contribute") + private Integer otherContribute; + /** + *
+     * 字段名:优惠币种
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string[1,16]
+     * 描述:
+     *  CNY:人民币,境内商户号仅支持人民币。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + /** + *
+     * 字段名:单品列表
+     * 变量名:goods_detail
+     * 是否必填:否
+     * 类型:array
+     * 描述:
+     *  单品列表信息
+     * 
+ */ + @SerializedName(value = "goods_detail") + private List goodsDetails; + } + + @Data + @NoArgsConstructor + public static class GoodsDetail implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:商品编码
+     * 变量名:goods_id
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  商品编码
+     *  示例值:M1006
+     * 
+ */ + @SerializedName(value = "goods_id") + private String goodsId; + /** + *
+     * 字段名:商品数量
+     * 变量名:quantity
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  用户购买的数量
+     *  示例值:1
+     * 
+ */ + @SerializedName(value = "quantity") + private Integer quantity; + /** + *
+     * 字段名:商品单价
+     * 变量名:unit_price
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  商品单价,单位为分
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "unit_price") + private Integer unitPrice; + /** + *
+     * 字段名:商品优惠金额
+     * 变量名:discount_amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  商品优惠金额
+     *  示例值:0
+     * 
+ */ + @SerializedName(value = "discount_amount") + private Integer discountAmount; + /** + *
+     * 字段名:商品备注
+     * 变量名:goods_remark
+     * 是否必填:否
+     * 类型:string[1,128]
+     * 描述:
+     *  商品备注信息
+     *  示例值:商品备注信息
+     * 
+ */ + @SerializedName(value = "goods_remark") + private String goodsRemark; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerRefundNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerRefundNotifyV3Result.java new file mode 100644 index 0000000000..f3dcf168b1 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayPartnerRefundNotifyV3Result.java @@ -0,0 +1,230 @@ +package com.github.binarywang.wxpay.bean.notify; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信支付服务商退款回调 + * 文档见:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_11.shtml + * + * @author Guo Shuai + * @version 1.0 + * @date 2023/3/2 + */ +@Data +@NoArgsConstructor +public class WxPayPartnerRefundNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result { + private static final long serialVersionUID = -1L; + /** + * 源数据 + */ + private OriginNotifyResponse rawData; + /** + * 解密后的数据 + */ + private DecryptNotifyResult result; + + @Data + @NoArgsConstructor + public static class DecryptNotifyResult implements Serializable { + private static final long serialVersionUID = -1L; + /** + *
+     * 字段名:服务商的商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  服务商的商户号,由微信支付生成并下发。
+     *  示例值:1230000109
+     * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchId; + /** + *
+     * 字段名:子商户的商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string[1, 32]
+     * 描述:
+     *  子商户商户号,由微信支付生成并下发。
+     *  示例值:1230000109
+     * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchId; + /** + *
+     * 字段名:商户订单号
+     * 变量名:out_trade_no
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  返回的商户订单号
+     *  示例值: 1217752501201407033233368018
+     * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + /** + *
+     * 字段名:微信支付订单号
+     * 变量名:transaction_id
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  微信支付订单号
+     *  示例值: 1217752501201407033233368018
+     * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + /** + *
+     * 字段名:商户退款单号
+     * 变量名:out_refund_no
+     * 是否必填:是
+     * 类型:string[1,64]
+     * 描述:
+     *  商户退款单号
+     *  示例值: 1217752501201407033233368018
+     * 
+ */ + @SerializedName(value = "out_refund_no") + private String outRefundNo; + /** + *
+     * 字段名:微信支付退款号
+     * 变量名:refund_id
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  微信退款单号
+     *  示例值: 1217752501201407033233368018
+     * 
+ */ + @SerializedName(value = "refund_id") + private String refundId; + /** + *
+     * 字段名:退款状态
+     * 变量名:refund_status
+     * 是否必填:是
+     * 类型:string[1,16]
+     * 描述:
+     *  退款状态,枚举值:
+     *  SUCCESS:退款成功
+     *  CLOSE:退款关闭
+     *  ABNORMAL:退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【商户平台—>交易中心】,手动处理此笔退款
+     *  示例值:SUCCESS
+     * 
+ */ + @SerializedName(value = "refund_status") + private String refundStatus; + /** + *
+     * 字段名:退款成功时间
+     * 变量名:success_time
+     * 是否必填:否
+     * 类型:string[1,64]
+     * 描述:
+     *  1、退款成功时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+     *  2、当退款状态为退款成功时返回此参数。
+     *  示例值:2018-06-08T10:34:56+08:00
+     * 
+ */ + @SerializedName(value = "success_time") + private String successTime; + /** + *
+     * 字段名:退款入账账户
+     * 变量名:user_received_account
+     * 是否必填:是
+     * 类型:string[1,64]
+     * 描述:
+     *  取当前退款单的退款入账方。
+     *  1、退回银行卡:{银行名称}{卡类型}{卡尾号}
+     *  2、退回支付用户零钱: 支付用户零钱
+     *  3、退还商户: 商户基本账户、商户结算银行账户
+     *  4、退回支付用户零钱通:支付用户零钱通
+     *  示例值:招商银行信用卡0403
+     * 
+ */ + @SerializedName(value = "user_received_account") + private String userReceivedAccount; + /** + *
+     * 字段名:金额信息
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:object
+     * 描述:
+     *  金额信息
+     * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + } + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:订单金额
+     * 变量名:total
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  订单总金额,单位为分,只能为整数,详见支付金额
+     *  示例值:999
+     * 
+ */ + @SerializedName(value = "total") + private Integer total; + /** + *
+     * 字段名:退款金额
+     * 变量名:refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额,如果有使用券,后台会按比例退。
+     *  示例值:999
+     * 
+ */ + @SerializedName(value = "refund") + private Integer refund; + /** + *
+     * 字段名:用户支付金额
+     * 变量名:payer_total
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  用户实际支付金额,单位为分,只能为整数,详见支付金额
+     *  示例值:999
+     * 
+ */ + @SerializedName(value = "payer_total") + private Integer payerTotal; + /** + *
+     * 字段名:用户退款金额
+     * 变量名:payer_refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款给用户的金额,不包含所有优惠券金额
+     *  示例值:999
+     * 
+ */ + @SerializedName(value = "payer_refund") + private Integer payerRefund; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java index 976e7e2691..c3473ee465 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/notify/WxPayRefundNotifyV3Result.java @@ -14,7 +14,7 @@ */ @Data @NoArgsConstructor -public class WxPayRefundNotifyV3Result implements Serializable { +public class WxPayRefundNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result { private static final long serialVersionUID = -1L; /** * 源数据 diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java new file mode 100644 index 0000000000..89d0e11f58 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerRefundV3Request.java @@ -0,0 +1,55 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 微信支付服务商退款请求 + * 文档见:https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_9.shtml + * + * @author Guo Shuai + * @version 1.0 + * @date 2023/3/2 + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class WxPayPartnerRefundV3Request extends WxPayRefundV3Request implements Serializable { + private static final long serialVersionUID = -1L; + /** + *
+   * 字段名:子商户的商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string[1, 32]
+   * 描述:
+   *  子商户商户号,由微信支付生成并下发。
+   *  示例值:1230000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchId; + /** + *
+   * 字段名:退款资金来源
+   * 变量名:funds_account
+   * 是否必填:否
+   * 类型:string[1, 32]
+   * 描述:
+   *  若传递此参数则使用对应的资金账户退款,否则默认使用未结算资金退款(仅对老资金流商户适用)
+   *  示例值:
+   *    UNSETTLED : 未结算资金
+   *    AVAILABLE : 可用余额
+   *    UNAVAILABLE : 不可用余额
+   *    OPERATION : 运营户
+   *    BASIC : 基本账户(含可用余额和不可用余额)
+   * 
+ */ + @SerializedName(value = "funds_account") + private String fundsAccount; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerUnifiedOrderV3Request.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerUnifiedOrderV3Request.java new file mode 100644 index 0000000000..4e60f0693d --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayPartnerUnifiedOrderV3Request.java @@ -0,0 +1,610 @@ +package com.github.binarywang.wxpay.bean.request; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.List; + +/** + * 微信支付服务商下单请求对象 + * + * @author Guo Shuai + * @version 1.0 + * @date 2023/3/2 + */ +@Data +@NoArgsConstructor +@Accessors(chain = true) +public class WxPayPartnerUnifiedOrderV3Request implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+   * 字段名:服务商应用ID
+   * 变量名:spAppid
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  由微信生成的应用ID,全局唯一。请求统一下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的APPID
+   *  示例值:wxd678efh567hg6787
+   * 
+ */ + @SerializedName(value = "sp_appid") + protected String spAppid; + /** + *
+   * 字段名:服务商商户号
+   * 变量名:spMchid
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  服务商商户号,由微信支付生成并下发。
+   *  示例值:1230000109
+   * 
+ */ + @SerializedName(value = "sp_mchid") + protected String spMchId; + /** + *
+   * 字段名:子商户应用ID
+   * 变量名:subAppid
+   * 是否必填:否
+   * 类型:string[1,32]
+   * 描述:
+   *  由微信生成的应用ID,全局唯一。请求统一下单接口时请注意APPID的应用属性,例如公众号场景下,需使用应用属性为公众号的APPID
+   *  示例值:wxd678efh567hg6787
+   * 
+ */ + @SerializedName(value = "sub_appid") + protected String subAppid; + /** + *
+   * 字段名:子商户商户号
+   * 变量名:subMchid
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  子商户商户号,由微信支付生成并下发。
+   *  示例值:1230000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + protected String subMchId; + /** + *
+   * 字段名:商品描述
+   * 变量名:description
+   * 是否必填:是
+   * 类型:string[1,127]
+   * 描述:
+   *  商品描述
+   *  示例值:Image形象店-深圳腾大-QQ公仔
+   * 
+ */ + @SerializedName(value = "description") + protected String description; + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string[6,32]
+   * 描述:
+   *  商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_trade_no") + protected String outTradeNo; + /** + *
+   * 字段名:交易结束时间
+   * 变量名:time_expire
+   * 是否必填:是
+   * 类型:string[1,64]
+   * 描述:
+   *  订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   *  示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "time_expire") + protected String timeExpire; + /** + *
+   * 字段名:附加数据
+   * 变量名:attach
+   * 是否必填:否
+   * 类型:string[1,128]
+   * 描述:
+   *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+   *  示例值:自定义数据
+   * 
+ */ + @SerializedName(value = "attach") + protected String attach; + /** + *
+   * 字段名:通知地址
+   * 变量名:notify_url
+   * 是否必填:是
+   * 类型:string[1,256]
+   * 描述:
+   *  通知URL必须为直接可访问的URL,不允许携带查询串,要求必须为https地址。
+   *  格式:URL
+   *  示例值:https://www.weixin.qq.com/wxpay/pay.php
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; + /** + *
+   * 字段名:订单优惠标记
+   * 变量名:goods_tag
+   * 是否必填:否
+   * 类型:string[1,256]
+   * 描述:
+   *  订单优惠标记
+   *  示例值:WXG
+   * 
+ */ + @SerializedName(value = "goods_tag") + private String goodsTag; + /** + *
+   * 字段名:电子发票入口开放标识
+   * 变量名:support_fapiao
+   * 是否必填:否
+   * 类型:boolean
+   * 描述:传入true时,支付成功消息和支付详情页将出现开票入口。需要在微信支付商户平台或微信公众平台开通电子发票功能,传此字段才可生效。
+   * 
+ */ + @SerializedName(value = "support_fapiao") + private Boolean supportFapiao; + /** + *
+   * 字段名:订单金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  订单金额信息
+   * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + /** + *
+   * 字段名:支付者
+   * 变量名:payer
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  支付者信息
+   * 
+ */ + @SerializedName(value = "payer") + private Payer payer; + /** + *
+   * 字段名:优惠功能
+   * 变量名:detail
+   * 是否必填:否
+   * 类型:object
+   * 描述:
+   *  优惠功能
+   * 
+ */ + @SerializedName(value = "detail") + private Discount detail; + /** + *
+   * 字段名:场景信息
+   * 变量名:scene_info
+   * 是否必填:否
+   * 类型:object
+   * 描述:
+   *  支付场景描述
+   * 
+ */ + @SerializedName(value = "scene_info") + private SceneInfo sceneInfo; + /** + *
+   * 字段名:结算信息
+   * 变量名:settle_info
+   * 是否必填:否
+   * 类型:Object
+   * 描述:结算信息
+   * 
+ */ + @SerializedName(value = "settle_info") + private SettleInfo settleInfo; + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:总金额
+     * 变量名:total
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  订单总金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "total") + private Integer total; + /** + *
+     * 字段名:币类型
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string[1,16]
+     * 描述:
+     *  CNY:人民币,境内商户号仅支持人民币。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + } + + @Data + @NoArgsConstructor + public static class Payer implements Serializable { + private static final long serialVersionUID = -1L; + /** + *
+     * spOpenid 和 subOpenid二选一 参考官网
+     * 字段名:服务商用户标识
+     * 变量名:spOpenid
+     * 是否必填:是
+     * 类型:string[1,128]
+     * 描述:
+     *  用户在服务商appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "sp_openid") + private String spOpenid; + /** + *
+     * 字段名:子商户应用用户标识
+     * 变量名:subOpenid
+     * 是否必填:是
+     * 类型:string[1,128]
+     * 描述:
+     *  用户在子商户appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "sub_openid") + private String subOpenid; + } + + @Data + @NoArgsConstructor + public static class Discount implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:订单原价
+     * 变量名:cost_price
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  1、商户侧一张小票订单可能被分多次支付,订单原价用于记录整张小票的交易金额。
+     *  2、当订单原价与支付金额不相等,则不享受优惠。
+     *  3、该字段主要用于防止同一张小票分多次支付,以享受多次优惠的情况,正常支付订单不必上传此参数。
+     *  示例值:608800
+     * 
+ */ + @SerializedName(value = "cost_price") + private Integer costPrice; + /** + *
+     * 字段名:商品小票ID
+     * 变量名:invoice_id
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  商品小票ID
+     *  示例值:微信123
+     * 
+ */ + @SerializedName(value = "invoice_id") + private String invoiceId; + /** + *
+     * 字段名:单品列表
+     * 变量名:goods_detail
+     * 是否必填:否
+     * 类型:array
+     * 描述:
+     *  单品列表信息
+     *  条目个数限制:【1,6000】
+     * 
+ */ + @SerializedName(value = "goods_detail") + private List goodsDetails; + } + + @Data + @NoArgsConstructor + public static class GoodsDetail implements Serializable { + private static final long serialVersionUID = -1L; + /** + *
+     * 字段名:商户侧商品编码
+     * 变量名:merchant_goods_id
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  由半角的大小写字母、数字、中划线、下划线中的一种或几种组成。
+     *  示例值:商品编码
+     * 
+ */ + @SerializedName(value = "merchant_goods_id") + private String merchantGoodsId; + /** + *
+     * 字段名:微信侧商品编码
+     * 变量名:wechatpay_goods_id
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  微信支付定义的统一商品编号(没有可不传)
+     *  示例值:1001
+     * 
+ */ + @SerializedName(value = "wechatpay_goods_id") + private String wechatpayGoodsId; + /** + *
+     * 字段名:商品名称
+     * 变量名:goods_name
+     * 是否必填:否
+     * 类型:string[1,256]
+     * 描述:
+     *  商品的实际名称
+     *  示例值:iPhoneX 256G
+     * 
+ */ + @SerializedName(value = "goods_name") + private String goodsName; + /** + *
+     * 字段名:商品数量
+     * 变量名:quantity
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  用户购买的数量
+     *  示例值:1
+     * 
+ */ + @SerializedName(value = "quantity") + private Integer quantity; + /** + *
+     * 字段名:商品单价
+     * 变量名:unit_price
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  商品单价,单位为分
+     *  示例值:828800
+     * 
+ */ + @SerializedName(value = "unit_price") + private Integer unitPrice; + } + + @Data + @NoArgsConstructor + public static class SceneInfo implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:用户终端IP
+     * 变量名:payer_client_ip
+     * 是否必填:是
+     * 类型:string[1,45]
+     * 描述:
+     *  用户的客户端IP,支持IPv4和IPv6两种格式的IP地址。
+     *  示例值:14.23.150.211
+     * 
+ */ + @SerializedName(value = "payer_client_ip") + private String payerClientIp; + /** + *
+     * 字段名:商户端设备号
+     * 变量名:device_id
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述:
+     *  商户端设备号(门店号或收银设备ID)。
+     *  示例值:013467007045764
+     * 
+ */ + @SerializedName(value = "device_id") + private String deviceId; + /** + *
+     * 字段名:商户门店信息
+     * 变量名:store_info
+     * 是否必填:否
+     * 类型:object
+     * 描述:
+     *  商户门店信息
+     * 
+ */ + @SerializedName(value = "store_info") + private StoreInfo storeInfo; + /** + *
+     * 字段名:H5场景信息
+     * 变量名:h5_info
+     * 是否必填:否(H5支付必填)
+     * 类型:object
+     * 描述:
+     *  H5场景信息
+     * 
+ */ + @SerializedName(value = "h5_info") + private H5Info h5Info; + } + + @Data + @NoArgsConstructor + public static class StoreInfo implements Serializable { + private static final long serialVersionUID = -1L; + /** + *
+     * 字段名:门店编号
+     * 变量名:id
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  商户侧门店编号
+     *  示例值:0001
+     * 
+ */ + @SerializedName(value = "id") + private String id; + /** + *
+     * 字段名:门店名称
+     * 变量名:name
+     * 是否必填:否
+     * 类型:string[1,256]
+     * 描述:
+     *  商户侧门店名称
+     *  示例值:腾讯大厦分店
+     * 
+ */ + @SerializedName(value = "name") + private String name; + /** + *
+     * 字段名:地区编码
+     * 变量名:area_code
+     * 是否必填:否
+     * 类型:string[1,32]
+     * 描述: 地区编码, 详细请见省市区编号对照表。
+     * 示例值:440305
+     * 
+ */ + @SerializedName(value = "area_code") + private String areaCode; + /** + *
+     * 字段名:详细地址
+     * 变量名:address
+     * 是否必填:是
+     * 类型:string[1,512]
+     * 描述:
+     *  详细的商户门店地址
+     *  示例值:广东省深圳市南山区科技中一道10000号
+     * 
+ */ + @SerializedName(value = "address") + private String address; + } + + @Data + @NoArgsConstructor + public static class H5Info implements Serializable { + private static final long serialVersionUID = -1L; + /** + *
+     * 字段名:场景类型
+     * 变量名:type
+     * 是否必填:是
+     * 类型:string[1,32]
+     * 描述:
+     *  场景类型
+     *  示例值:iOS, Android, Wap
+     * 
+ */ + @SerializedName(value = "type") + private String type; + /** + *
+     * 字段名:应用名称
+     * 变量名:app_name
+     * 是否必填:否
+     * 类型:string[1,64]
+     * 描述:
+     *  应用名称
+     *  示例值:王者荣耀
+     * 
+ */ + @SerializedName(value = "app_name") + private String appName; + /** + *
+     * 字段名:网站URL
+     * 变量名:app_url
+     * 是否必填:否
+     * 类型:string[1,128]
+     * 描述:
+     *  网站URL
+     *  示例值:https://pay.qq.com
+     * 
+ */ + @SerializedName(value = "app_url") + private String appUrl; + /** + *
+     * 字段名:iOS平台BundleID
+     * 变量名:bundle_id
+     * 是否必填:否
+     * 类型:string[1,128]
+     * 描述:
+     *  iOS平台BundleID
+     *  示例值:com.tencent.wzryiOS
+     * 
+ */ + @SerializedName(value = "bundle_id") + private String bundleId; + /** + *
+     * 字段名:Android平台PackageName
+     * 变量名:package_name
+     * 是否必填:否
+     * 类型:string[1,128]
+     * 描述:
+     *  Android平台PackageName
+     *  示例值:com.tencent.tmgp.sgame
+     * 
+ */ + @SerializedName(value = "package_name") + private String packageName; + } + + @Data + @NoArgsConstructor + public static class SettleInfo implements Serializable { + private static final long serialVersionUID = 1L; + /** + *
+     * 字段名:是否指定分账
+     * 变量名:profit_sharing
+     * 是否必填:否
+     * 类型:boolean
+     * 描述:
+     *  是否指定分账
+     *  示例值:false
+     * 
+ */ + @SerializedName(value = "profit_sharing") + private Boolean profitSharing; + } +} 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 442c90afad..fd889cf601 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 @@ -169,7 +169,7 @@ public interface WxPayService { *

* 部分字段会包含敏感信息,所以在提交前需要在请求头中会包含"Wechatpay-Serial"信息 * - * @param url 请求地址 + * @param url 请求地址 * @return 返回请求结果字符串 string * @throws WxPayException the wx pay exception */ @@ -550,6 +550,27 @@ public interface WxPayService { */ T createOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException; + /** + * 服务商模式调用统一下单接口,并组装生成支付所需参数对象. + * + * @param 请使用{@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段 + * @param tradeType the trade type + * @param request 统一下单请求参数 + * @return 返回 {@link com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result}里的内部类或字段 + * @throws WxPayException the wx pay exception + */ + T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException; + + /** + * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" + * + * @param tradeType the trade type + * @param request 请求对象,注意一些参数如spAppid、spMchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置) + * @return the wx pay unified order result + * @throws WxPayException the wx pay exception + */ + WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException; + /** * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" * @@ -802,15 +823,39 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData, String signType) throws WxPayException; /** - * 解析支付结果v3通知. + * 解析支付结果v3通知. 直连商户模式 + * 详见https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml + * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return the wx pay order notify result + * @throws WxPayException the wx pay exception + */ + WxPayNotifyV3Result parseOrderNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException; + + /** + * 服务商模式解析支付结果v3通知. + * 详见https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_5.shtml + * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return the wx pay order notify result + * @throws WxPayException the wx pay exception + */ + WxPayPartnerNotifyV3Result parsePartnerOrderNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException; + + /** + * 支付服务商和直连商户两种模式 * 详见https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_5.shtml * * @param notifyData 通知数据 * @param header 通知头部数据,不传则表示不校验头 + * @param resultType 结果类型 + * @param dataType 结果数据类型 * @return the wx pay order notify result * @throws WxPayException the wx pay exception */ - WxPayOrderNotifyV3Result parseOrderNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException; + , E> T baseParseOrderNotifyV3Result(String notifyData, SignatureHeader header, Class resultType, Class dataType) throws WxPayException; /** *

@@ -836,7 +881,7 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
   WxPayRefundNotifyResult parseRefundNotifyResult(String xmlData) throws WxPayException;
 
   /**
-   * 解析退款结果通知
+   * 解析直连商户退款结果通知
    * 详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_16&index=9
    *
    * @param notifyData 通知数据
@@ -846,6 +891,17 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
    */
   WxPayRefundNotifyV3Result parseRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
 
+  /**
+   * 解析服务商模式退款结果通知
+   * 详见https://pay.weixin.qq.com/wiki/doc/apiv3_partner/apis/chapter4_1_11.shtml
+   *
+   * @param notifyData 通知数据
+   * @param header     通知头部数据,不传则表示不校验头
+   * @return the wx pay refund notify result
+   * @throws WxPayException the wx pay exception
+   */
+  WxPayPartnerRefundNotifyV3Result parsePartnerRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException;
+
   /**
    * 解析扫码支付回调通知
    * 详见https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
@@ -1380,6 +1436,7 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
 
   /**
    * 获取服务商支付分服务类
+   *
    * @return the partner pay score service
    */
   PartnerPayScoreService getPartnerPayScoreService();
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 728707af0f..a749f51fd9 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
@@ -31,6 +31,7 @@
 import lombok.Setter;
 import me.chanjar.weixin.common.error.WxRuntimeException;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.reflect.ConstructorUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -343,7 +344,17 @@ private boolean verifyNotifySign(SignatureHeader header, String data) {
   }
 
   @Override
-  public WxPayOrderNotifyV3Result parseOrderNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
+  public WxPayNotifyV3Result parseOrderNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
+    return baseParseOrderNotifyV3Result(notifyData, header, WxPayNotifyV3Result.class, WxPayNotifyV3Result.DecryptNotifyResult.class);
+  }
+
+  @Override
+  public WxPayPartnerNotifyV3Result parsePartnerOrderNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
+    return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayPartnerNotifyV3Result.class, WxPayPartnerNotifyV3Result.DecryptNotifyResult.class);
+  }
+
+  @Override
+  public , E> T baseParseOrderNotifyV3Result(String notifyData, SignatureHeader header, Class resultType, Class dataType) throws WxPayException {
     if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) {
       throw new WxPayException("非法请求,头部信息验证失败");
     }
@@ -355,12 +366,12 @@ public WxPayOrderNotifyV3Result parseOrderNotifyV3Result(String notifyData, Sign
     String apiV3Key = this.getConfig().getApiV3Key();
     try {
       String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
-      WxPayOrderNotifyV3Result.DecryptNotifyResult decryptNotifyResult = GSON.fromJson(result, WxPayOrderNotifyV3Result.DecryptNotifyResult.class);
-      WxPayOrderNotifyV3Result notifyResult = new WxPayOrderNotifyV3Result();
+      E decryptNotifyResult = GSON.fromJson(result, dataType);
+      T notifyResult = ConstructorUtils.invokeConstructor(resultType);
       notifyResult.setRawData(response);
       notifyResult.setResult(decryptNotifyResult);
       return notifyResult;
-    } catch (GeneralSecurityException | IOException e) {
+    } catch (Exception e) {
       throw new WxPayException("解析报文异常!", e);
     }
   }
@@ -409,25 +420,12 @@ public WxPayRefundNotifyResult parseRefundNotifyResult(String xmlData) throws Wx
 
   @Override
   public WxPayRefundNotifyV3Result parseRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
-    if (Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)) {
-      throw new WxPayException("非法请求,头部信息验证失败");
-    }
-    OriginNotifyResponse response = GSON.fromJson(notifyData, OriginNotifyResponse.class);
-    OriginNotifyResponse.Resource resource = response.getResource();
-    String cipherText = resource.getCiphertext();
-    String associatedData = resource.getAssociatedData();
-    String nonce = resource.getNonce();
-    String apiV3Key = this.getConfig().getApiV3Key();
-    try {
-      String result = AesUtils.decryptToString(associatedData, nonce, cipherText, apiV3Key);
-      WxPayRefundNotifyV3Result.DecryptNotifyResult decryptNotifyResult = GSON.fromJson(result, WxPayRefundNotifyV3Result.DecryptNotifyResult.class);
-      WxPayRefundNotifyV3Result notifyResult = new WxPayRefundNotifyV3Result();
-      notifyResult.setRawData(response);
-      notifyResult.setResult(decryptNotifyResult);
-      return notifyResult;
-    } catch (GeneralSecurityException | IOException e) {
-      throw new WxPayException("解析报文异常!", e);
-    }
+    return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayRefundNotifyV3Result.class, WxPayRefundNotifyV3Result.DecryptNotifyResult.class);
+  }
+
+  @Override
+  public WxPayPartnerRefundNotifyV3Result parsePartnerRefundNotifyV3Result(String notifyData, SignatureHeader header) throws WxPayException {
+    return this.baseParseOrderNotifyV3Result(notifyData, header, WxPayPartnerRefundNotifyV3Result.class, WxPayPartnerRefundNotifyV3Result.DecryptNotifyResult.class);
   }
 
   @Override
@@ -665,6 +663,37 @@ public  T createOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request r
     return result.getPayInfo(tradeType, request.getAppid(), request.getMchid(), this.getConfig().getPrivateKey());
   }
 
+  @Override
+  public  T createPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
+    WxPayUnifiedOrderV3Result result = this.unifiedPartnerOrderV3(tradeType, request);
+    //获取应用ID
+    String appId = StringUtils.isBlank(request.getSubAppid()) ? request.getSpAppid() : request.getSubAppid();
+    return result.getPayInfo(tradeType, appId, request.getSubMchId(), this.getConfig().getPrivateKey());
+  }
+
+  @Override
+  public WxPayUnifiedOrderV3Result unifiedPartnerOrderV3(TradeTypeEnum tradeType, WxPayPartnerUnifiedOrderV3Request request) throws WxPayException {
+    if (StringUtils.isBlank(request.getSpAppid())) {
+      request.setSpAppid(this.getConfig().getAppId());
+    }
+    if (StringUtils.isBlank(request.getSpMchId())) {
+      request.setSpMchId(this.getConfig().getMchId());
+    }
+    if (StringUtils.isBlank(request.getNotifyUrl())) {
+      request.setNotifyUrl(this.getConfig().getNotifyUrl());
+    }
+    if (StringUtils.isBlank(request.getSubAppid())) {
+      request.setSubAppid(this.getConfig().getSubAppId());
+    }
+    if (StringUtils.isBlank(request.getSubMchId())) {
+      request.setSubMchId(this.getConfig().getSubMchId());
+    }
+
+    String url = this.getPayBaseUrl() + tradeType.getPartnerUrl();
+    String response = this.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, WxPayUnifiedOrderV3Result.class);
+  }
+
   @Override
   public WxPayUnifiedOrderV3Result unifiedOrderV3(TradeTypeEnum tradeType, WxPayUnifiedOrderV3Request request) throws WxPayException {
     if (StringUtils.isBlank(request.getAppid())) {
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
index ad4a36962b..2effc6260a 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImplTest.java
@@ -796,7 +796,7 @@ public String testParseOrderNotifyV3Result(HttpServletRequest request, HttpServl
     log.info("请求头参数为:timestamp:{} nonce:{} serialNo:{} signature:{}", timestamp, nonce, serialNo, signature);
 
     // V2版本请参考com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResultTest里的单元测试
-    final WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = this.payService.parseOrderNotifyV3Result(RequestUtils.readData(request),
+    final WxPayNotifyV3Result wxPayOrderNotifyV3Result = this.payService.parseOrderNotifyV3Result(RequestUtils.readData(request),
       new SignatureHeader(timestamp, nonce, signature, serialNo));
     log.info(GSON.toJson(wxPayOrderNotifyV3Result));