diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingQueryRequest.java new file mode 100644 index 000000000..c9e1aad2e --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingQueryRequest.java @@ -0,0 +1,54 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class ProfitSharingQueryRequest implements Serializable { + + /** + *
+ * 字段名:二级商户号 + * 变量名:sub_mchid + * 是否必填:是 + * 类型:string(32) + * 描述: + * 分账出资的电商平台二级商户,填写微信支付分配的商户号。 + * 示例值:1900000109 + *+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+ * 字段名:微信订单号 + * 变量名:transaction_id + * 是否必填:是 + * 类型:string(32) + * 描述: + * 微信支付订单号。 + * 示例值: 4208450740201411110007820472 + *+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+ * 字段名:商户分账单号 + * 变量名:out_order_no + * 是否必填:是 + * 类型:string(64) + * 描述: + * 商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。 + * 示例值:P20150806125346 + *+ */ + @SerializedName(value = "out_order_no") + private String outOrderNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingResult.java index 878311e6e..37ff86c25 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingResult.java @@ -5,6 +5,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; +import java.util.List; /** * 请求分账 结果响应 @@ -72,4 +73,209 @@ public class ProfitSharingResult implements Serializable { @SerializedName(value = "order_id") private String orderId; + /** + *
+ * 字段名:分账单状态 + * 变量名:status + * 是否必填:是 + * 类型:string (64) + * 描述: + * 分账单状态,枚举值: + * ACCEPTED:受理成功 + * PROCESSING:处理中 + * FINISHED:分账成功 + * CLOSED:处理失败,已关单 + * 示例值:FINISHED + *+ */ + @SerializedName(value = "status") + private String status; + + /** + *
+ * 字段名:分账接收方列表 + * 变量名:receivers + * 是否必填:否 + * 类型:array + * 描述: + * 分账接收方列表。当查询分账完结的执行结果时,不返回该字段 + *+ */ + @SerializedName(value = "receivers") + private List
+ * 字段名:关单原因 + * 变量名:close_reason + * 是否必填:否 + * 类型:string (32) + * 描述: + * 关单原因描述,当分账单状态status为CLOSED(处理失败,已关单)时,返回该字段。 + * 枚举值: + * NO_AUTH:分账授权已解除 + * 示例值:NO_AUTH + *+ */ + @SerializedName(value = "close_reason") + private String closeReason; + + /** + *
+ * 字段名:分账完结金额 + * 变量名:finish_amount + * 是否必填:否 + * 类型:int + * 描述: + * 分账完结的分账金额,单位为分, 仅当查询分账完结的执行结果时,存在本字段。 + * 示例值:100 + *+ */ + @SerializedName(value = "finish_amount") + private Integer finishAmount; + + /** + *
+ * 字段名:分账完结描述 + * 变量名:finish_description + * 是否必填:否 + * 类型:string (80) + * 描述: + * 分账完结的原因描述,仅当查询分账完结的执行结果时,存在本字段。 + * 示例值:分账完结 + *+ */ + @SerializedName(value = "finish_description") + private String finishDescription; + + @Data + @NoArgsConstructor + public static class Receiver implements Serializable { + + /** + *
+ * 字段名:分账接收商户号 + * 变量名:receiver_mchid + * 是否必填:是 + * 类型:string (32) + * 描述: + * 填写微信支付分配的商户号,仅支持通过添加分账接收方接口添加的接收方;电商平台商户已默认添加到分账接收方,无需重复添加。 + * 示例值:1900000109 + *+ */ + @SerializedName(value = "receiver_mchid") + private String receiverMchid; + + /** + *
+ * 字段名:分账金额 + * 变量名:amount + * 是否必填:否 + * 类型:int + * 描述: + * 分账金额,单位为分,只能为整数,不能超过原订单支付金额及最大分账比例金额。 + * 示例值: 4208450740201411110007820472 + *+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+ * 字段名:分账描述 + * 变量名:description + * 是否必填:是 + * 类型:string (80) + * 描述: + * 分账的原因描述,分账账单中需要体现。 + * 示例值:分帐1900000110 + *+ */ + @SerializedName(value = "description") + private String description; + + /** + *
+ * 字段名:分账结果 + * 变量名:result + * 是否必填:是 + * 类型:string (32) + * 描述: + * 分账结果,枚举值: + * PENDING:待分账 + * SUCCESS:分账成功 + * ADJUST:分账失败待调账 + * RETURNED:已转回分账方 + * CLOSED:已关闭 + * 示例值:SUCCESS + *+ */ + @SerializedName(value = "result") + private String result; + + /** + *
+ * 字段名:完成时间 + * 变量名:finish_time + * 是否必填:是 + * 类型:string (64) + * 描述: + * 分账完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE,YYYY-MM-DD表示年月日, + * T出现在字符串中,表示time元素的开头,HH:mm:ss.sss表示时分秒毫秒,TIMEZONE表示时区 + * (+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日 13点29分35秒。 + * 示例值: 2015-05-20T13:29:35.120+08:00 + *+ */ + @SerializedName(value = "finish_time") + private String finishTime; + + /** + *
+ * 字段名:分账失败原因 + * 变量名:fail_reason + * 是否必填:否 + * 类型:string (32) + * 描述: + * 分账失败原因,当分账结果result为RETURNED(已转回分账方)或CLOSED(已关闭)时,返回该字段 + * 枚举值: + * ACCOUNT_ABNORMAL:分账接收账户异常 + * NO_RELATION:分账关系已解除 + * RECEIVER_HIGH_RISK:高风险接收方 + * 示例值:NO_RELATION + *+ */ + @SerializedName(value = "fail_reason") + private String failReason; + + /** + *
+ * 字段名:分账接收方类型 + * 变量名:type + * 是否必填:是 + * 类型:string (32) + * 描述: + * 分账接收方类型,枚举值: + * MERCHANT_ID:商户 + * PERSONAL_OPENID:个人 + * 示例值:MERCHANT_ID + *+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+ * 字段名:分账接收方类型 + * 变量名:receiver_account + * 是否必填:是 + * 类型:string (64) + * 描述: + * 分账接收方账号: + * 类型是MERCHANT_ID时,是商户ID + * 类型是PERSONAL_OPENID时,是个人openid + * 示例值:1900000109 + *+ */ + @SerializedName(value = "receiver_account") + private String receiverAccount; + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundNotifyResult.java new file mode 100644 index 000000000..7deced625 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundNotifyResult.java @@ -0,0 +1,233 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 退款结果 查询结果 + *
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_3.shtml + *+ */ +@Data +@NoArgsConstructor +public class RefundNotifyResult implements Serializable { + + /** + * 源数据 + */ + private NotifyResponse rawData; + + /** + *
+ * 字段名:电商平台商户号 + * 变量名:sp_mchid + * 是否必填:是 + * 类型:string(32) + * 描述: + * 微信支付分配给电商平台的商户号 + * 示例值:1900000100 + *+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+ * 字段名:二级商户号 + * 变量名:sub_mchid + * 是否必填:是 + * 类型:string(32) + * 描述: + * 分账出资的电商平台二级商户,填写微信支付分配的商户号。 + * 示例值:1900000109 + *+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+ * 字段名:商户订单号 + * 变量名:out_trade_no + * 是否必填:是 + * 类型:string(32) + * 描述: + * 返回的商户订单号 + * 示例值: 1217752501201407033233368018 + *+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+ * 字段名:微信订单号 + * 变量名:transaction_id + * 是否必填:是 + * 类型:string(32) + * 描述: + * 微信支付订单号 + * 示例值: 1217752501201407033233368018 + *+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+ * 字段名:商户退款单号 + * 变量名:out_refund_no + * 是否必填:是 + * 类型:string(32) + * 描述: + * 商户退款单号 + * 示例值: 1217752501201407033233368018 + *+ */ + @SerializedName(value = "out_refund_no") + private String outRefundNo; + + /** + *
+ * 字段名:微信退款单号 + * 变量名:refund_id + * 是否必填:是 + * 类型:string(32) + * 描述: + * 微信退款单号 + * 示例值: 1217752501201407033233368018 + *+ */ + @SerializedName(value = "refund_id") + private String refundId; + + /** + *
+ * 字段名:退款状态 + * 变量名:refund_status + * 是否必填:是 + * 类型:string(16) + * 描述: + * 退款状态,枚举值: + * SUCCESS:退款成功 + * CLOSE:退款关闭 + * ABNORMAL:退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【服务商平台—>交易中心】,手动处理此笔退款 + * 示例值:SUCCESS + *+ */ + @SerializedName(value = "refund_status") + private String refundStatus; + + /** + *
+ * 字段名:退款成功时间 + * 变量名:success_time + * 是否必填:否 + * 类型:string(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(64) + * 描述: + * 取当前退款单的退款入账方。 + * 退回银行卡:{银行名称}{卡类型}{卡尾号} + * 退回支付用户零钱: 支付用户零钱 + * 退还商户: 商户基本账户、商户结算银行账户 + * 退回支付用户零钱通:支付用户零钱通 + * 示例值:招商银行信用卡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 { + /** + *
+ * 字段名:订单金额 + * 变量名:total + * 是否必填:是 + * 类型:int + * 描述: + * 订单总金额,单位为分,只能为整数,详见支付金额 + * 示例值:999 + *+ */ + @SerializedName(value = "total") + private Integer total; + + /** + *
+ * 字段名:退款金额 + * 变量名:refund + * 是否必填:是 + * 类型:int + * 描述: + * 退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额,如果有使用券,后台会按比例退。 + * 示例值:999 + *+ */ + @SerializedName(value = "refund") + private String refund; + + /** + *
+ * 字段名:用户支付金额 + * 变量名:payer_total + * 是否必填:是 + * 类型:int + * 描述: + * 用户支付金额,单位为分。 + * 示例值:100 + *+ */ + @SerializedName(value = "payer_total") + private Integer payerTotal; + + /** + *
+ * 字段名:用户退款金额 + * 变量名:payer_currency + * 是否必填:是 + * 类型:int + * 描述: + * 退款给用户的金额,不包含所有优惠券金额 + * 示例值:999 + *+ */ + @SerializedName(value = "payer_refund") + private String payerRefund; + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java index ee7811987..f2110cc5d 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java @@ -72,7 +72,7 @@ public class ReturnOrdersResult implements Serializable { * */ @SerializedName(value = "out_return_no") - private String R20190516001; + private String outReturnNo; /** *
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java index 262a233ac..849f17a90 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java @@ -225,6 +225,18 @@ public interface EcommerceService { */ ProfitSharingResult profitSharing(ProfitSharingRequest request) throws WxPayException; + /** + *+ * 查询分账结果API + * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_2.shtml + *+ * + * @param request 查询分账请求 + * @return 返回数据 profit sharing result + * @throws WxPayException the wx pay exception + */ + ProfitSharingResult queryProfitSharing(ProfitSharingQueryRequest request) throws WxPayException; + /** ** 请求分账回退API @@ -261,6 +273,19 @@ public interface EcommerceService { */ RefundsResult refunds(RefundsRequest request) throws WxPayException; + /** + *+ * 退款通知回调数据处理 + * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_3.shtml + *+ * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return 解密后通知数据 partner refund notify result + * @throws WxPayException the wx pay exception + */ + RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + /** ** 二级商户账户余额提现API diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java index 1ed6e0fe4..1fd339344 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java @@ -181,6 +181,14 @@ public class EcommerceServiceImpl implements EcommerceService { return GSON.fromJson(response, ProfitSharingResult.class); } + @Override + public ProfitSharingResult queryProfitSharing(ProfitSharingQueryRequest request) throws WxPayException { + String url = String.format("%s/v3/ecommerce/profitsharing/orders?sub_mchid=%s&transaction_id=%s&out_order_no=%s", + this.payService.getPayBaseUrl(), request.getSubMchid(), request.getTransactionId(), request.getOutOrderNo()); + String response = this.payService.getV3(URI.create(url)); + return GSON.fromJson(response, ProfitSharingResult.class); + } + @Override public ReturnOrdersResult returnOrders(ReturnOrdersRequest request) throws WxPayException { String url = String.format("%s/v3/ecommerce/profitsharing/returnorders", this.payService.getPayBaseUrl()); @@ -202,6 +210,27 @@ public class EcommerceServiceImpl implements EcommerceService { return GSON.fromJson(response, RefundsResult.class); } + @Override + public RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException { + if(Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)){ + throw new WxPayException("非法请求,头部信息验证失败"); + } + NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class); + NotifyResponse.Resource resource = response.getResource(); + String cipherText = resource.getCiphertext(); + String associatedData = resource.getAssociatedData(); + String nonce = resource.getNonce(); + String apiV3Key = this.payService.getConfig().getApiV3Key(); + try { + String result = AesUtils.decryptToString(associatedData, nonce,cipherText, apiV3Key); + RefundNotifyResult notifyResult = GSON.fromJson(result, RefundNotifyResult.class); + notifyResult.setRawData(response); + return notifyResult; + } catch (GeneralSecurityException | IOException e) { + throw new WxPayException("解析报文异常!", e); + } + } + @Override public SubWithdrawResult subWithdraw(SubWithdrawRequest request) throws WxPayException { String url = String.format("%s/v3/ecommerce/fund/withdraw", this.payService.getPayBaseUrl());