diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundQueryRequest.java index d9befee6f..875a702b2 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundQueryRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundQueryRequest.java @@ -89,9 +89,8 @@ public class WxPayRefundQueryRequest extends BaseWxPayRequest { && StringUtils.isBlank(outRefundNo) && StringUtils.isBlank(refundId)) || (StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo) && StringUtils.isNotBlank(outRefundNo) && StringUtils.isNotBlank(refundId))) { - throw new WxPayException("transaction_id,out_trade_no,out_refund_no,refund_id 必须四选一"); + throw new WxPayException("transactionId,outRefundNo,transactionId,refundId 必须四选一"); } - } @Override diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java index 924d46ffd..e145644d9 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequest.java @@ -4,8 +4,11 @@ import com.github.binarywang.wxpay.config.WxPayConfig; import com.github.binarywang.wxpay.constant.WxPayConstants.RefundAccountSource; import com.github.binarywang.wxpay.exception.WxPayException; import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; import lombok.*; +import lombok.experimental.Accessors; import me.chanjar.weixin.common.annotation.Required; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -26,7 +29,10 @@ import java.util.Map; @NoArgsConstructor @AllArgsConstructor @XStreamAlias("xml") +@Accessors(chain = true) public class WxPayRefundRequest extends BaseWxPayRequest { + private static final long serialVersionUID = 522565152886671848L; + private static final String[] REFUND_ACCOUNT = new String[]{ RefundAccountSource.RECHARGE_FUNDS, RefundAccountSource.UNSETTLED_FUNDS}; @@ -127,7 +133,6 @@ public class WxPayRefundRequest extends BaseWxPayRequest { * 描述:操作员帐号, 默认为商户号 * */ - //@Required @XStreamAlias("op_user_id") private String opUserId; /** @@ -172,6 +177,54 @@ public class WxPayRefundRequest extends BaseWxPayRequest { @XStreamAlias("notify_url") private String notifyUrl; + /** + *
+ * 字段名:商品详情
+ * 变量名:detail
+ * 类型:否
+ * 示例值:String(6000)
+ * 退款包含的商品列表信息detail字段列表说明:
+ *
+ * 字段名 变量名 必填 类型 示例值 描述
+ * 商品列表 goods_detail 是 String 示例见下文 商品信息,使用Json数组格式提交
+ * 商品列表goods_detail字段列表说明:
+ *
+ * 字段名 变量名 必填 类型 示例值 描述
+ * 商品编码 goods_id 是 String(32) 商品编码 由半角的大小写字母、数字、中划线、下划线中的一种或几种组成
+ * 微信侧商品编码 wxpay_goods_id 否 String(32) 1001 微信支付定义的统一商品编号(没有可不传)
+ * 商品名称 goods_name 否 String(256) iPhone6s 16G 商品的实际名称
+ * 商品退款金额 refund_amount 是 int 528800 商品退款金额
+ * 商品退货数量 refund_quantity 是 int 1 单品的退款数量
+ * 商品单价 price 是 int 528800 单位为:分。如果商户有优惠,需传输商户优惠后的单价(例如:用户对一笔100元的订单使用了商场发的优惠券100-50,则活动商品的单价应为原单价-50)
+ * detail字段值举例如下:
+ *
+ * {
+ * "goods_detail": [
+ * {
+ * "goods_id": "商品编码",
+ * "wxpay_goods_id": "1001",
+ * "goods_name": "iPhone6s 16G",
+ * "refund_amount": 528800,
+ * "refund_quantity": 1,
+ * "price": 528800
+ * },
+ * {
+ * "goods_id": "商品编码",
+ * "wxpay_goods_id": "1001",
+ * "goods_name": "iPhone6s 16G",
+ * "refund_amount": 528800,
+ * "refund_quantity": 1,
+ * "price": 608800
+ * }
+ * ]
+ * }
+ * 描述:退款包含的商品列表信息,全额退款可不传,必须按照规范上传,JSON格式
+ *
+ */
+ @XStreamAlias("detail")
+ @XStreamConverter(value = XStreamCDataConverter.class)
+ private String detail;
+
@Override
public void checkAndSign(WxPayConfig config) throws WxPayException {
if (StringUtils.isBlank(this.getOpUserId())) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayRefundPromotionDetail.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayRefundPromotionDetail.java
new file mode 100644
index 000000000..414560b93
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayRefundPromotionDetail.java
@@ -0,0 +1,117 @@
+package com.github.binarywang.wxpay.bean.result;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 营销详情 .
+ *
+ * @author Binary Wang
+ * @date 2020-06-07
+ */
+@Data
+public class WxPayRefundPromotionDetail implements Serializable {
+ private static final long serialVersionUID = 2197712244944584263L;
+
+ /**
+ * 字段名:券ID
+ * 变量名:promotion_id
+ * 是否必填:是
+ * 类型:String(32)
+ * 示例例:109519
+ * 描述:券或者立减优惠id
+ */
+ @SerializedName("promotion_id")
+ private String promotionId;
+ /**
+ * 字段名:优惠范围
+ * 变量名:scope
+ * 是否必填:是
+ * 类型:String(32)
+ * 示例例:SINGLE
+ * 描述:GLOBAL- 全场代金券,SINGLE- 单品优惠
+ */
+ @SerializedName("scope")
+ private String scope;
+ /**
+ * 字段名:优惠类型
+ * 变量名:type
+ * 是否必填:是
+ * 类型:String(32)
+ * 示例例:DISCOUNT
+ * 描述:COUPON- 代金券,需要走结算资金的充值型代金券,(境外商户券币种与支付币种一致),DISCOUNT- 优惠券,不走结算资金的免充值型优惠券,(境外商户券币种与标价币种一致
+ */
+ @SerializedName("type")
+ private String type;
+ /**
+ * 字段名:代金券退款金额
+ * 变量名:refund_amount
+ * 是否必填:是
+ * 类型:Int
+ * 示例例:100
+ * 描述:代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见代金券或立减优惠
+ */
+ @SerializedName("refund_amount")
+ private Integer refundAmount;
+ /**
+ * 字段名:商品列表
+ * 变量名:goods_detail
+ * 是否必填:否
+ * 类型:String
+ * 示例例:见下文
+ * 描述:商品信息,使用Json格式
+ */
+ @SerializedName("goods_detail")
+ private List
* 微信支付-申请退款返回结果.
@@ -115,8 +120,35 @@ public class WxPayRefundResult extends BaseWxPayResult implements Serializable {
@XStreamAlias("coupon_refund_fee")
private Integer couponRefundFee;
+ /**
+ * 营销详情.
+ */
+ @XStreamAlias("promotion_detail")
+ private String promotionDetailString;
+
+ private List promotionDetails;
+
private List refundCoupons;
+ /**
+ * 组装生成营销详情信息.
+ */
+ public void composePromotionDetails() {
+ if (StringUtils.isEmpty(this.promotionDetailString)) {
+ return;
+ }
+
+ JsonElement tmpJsonElement = new JsonParser().parse(this.promotionDetailString);
+
+ final List promotionDetail = WxGsonBuilder.create()
+ .fromJson(tmpJsonElement.getAsJsonObject().get("promotion_detail"),
+ new TypeToken>() {
+ }.getType()
+ );
+
+ this.setPromotionDetails(promotionDetail);
+ }
+
/**
* 组装生成退款代金券信息.
*/
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 2c5ad12f7..b29f50129 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
@@ -256,6 +256,33 @@ public interface WxPayService {
*/
WxPayRefundResult refund(WxPayRefundRequest request) throws WxPayException;
+ /**
+ *
+ * 申请退款API(支持单品).
+ * 详见 https://pay.weixin.qq.com/wiki/doc/api/danpin.php?chapter=9_103&index=3
+ *
+ * 应用场景
+ * 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,微信支付将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。
+ *
+ * 注意:
+ * 1、交易时间超过一年的订单无法提交退款;
+ * 2、微信支付退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。申请退款总金额不能超过订单金额。 一笔退款失败后重新提交,请不要更换退款单号,请使用原商户退款单号。
+ * 3、请求频率限制:150qps,即每秒钟正常的申请退款请求次数不超过150次
+ * 错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款申请请求不超过6次
+ * 4、每个支付订单的部分退款次数不能超过50次
+ * 5、本接口支持单品优惠订单全额退款和单品优惠订单部分退款,推荐使用本接口,如果使用不支持单品优惠部分退款的历史接口,请看https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=9_4
+ *
+ * 接口地址
+ * https://api.mch.weixin.qq.com/secapi/pay/refundv2
+ * https://api2.mch.weixin.qq.com/secapi/pay/refundv2(备用域名)见跨城冗灾方案
+ *
+ *
+ * @param request 请求对象
+ * @return 退款操作结果 wx pay refund result
+ * @throws WxPayException the wx pay exception
+ */
+ WxPayRefundResult refundV2(WxPayRefundRequest request) throws WxPayException;
+
/**
*
* 微信支付-查询退款.
@@ -293,6 +320,29 @@ public interface WxPayService {
*/
WxPayRefundQueryResult refundQuery(WxPayRefundQueryRequest request) throws WxPayException;
+ /**
+ *
+ * 微信支付-查询退款API(支持单品).
+ * 应用场景
+ * 提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,银行卡支付的退款3个工作日后重新查询退款状态。
+ * 注意:
+ * 1、本接口支持查询单品优惠相关退款信息,且仅支持按微信退款单号或商户退款单号查询,若继续调用老查询退款接口,
+ * 请见https://pay.weixin.qq.com/wiki/doc/api/jsapi_sl.php?chapter=9_5
+ * 2、请求频率限制:300qps,即每秒钟正常的退款查询请求次数不超过300次
+ * 3、错误或无效请求频率限制:6qps,即每秒钟异常或错误的退款查询请求不超过6次
+ *
+ * 接口地址
+ * https://api.mch.weixin.qq.com/pay/refundqueryv2
+ * https://api2.mch.weixin.qq.com/pay/refundqueryv2(备用域名)见跨城冗灾方案
+ * 详见 https://pay.weixin.qq.com/wiki/doc/api/danpin.php?chapter=9_104&index=4
+ *
+ *
+ * @param request 微信退款单号
+ * @return 退款信息 wx pay refund query result
+ * @throws WxPayException the wx pay exception
+ */
+ WxPayRefundQueryResult refundQueryV2(WxPayRefundQueryRequest request) throws WxPayException;
+
/**
* 解析支付结果通知.
* 详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
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 81082a3aa..909b78f25 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
@@ -128,6 +128,22 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
return result;
}
+ @Override
+ public WxPayRefundResult refundV2(WxPayRefundRequest request) throws WxPayException {
+ request.checkAndSign(this.getConfig());
+
+ String url = this.getPayBaseUrl() + "/secapi/pay/refundv2";
+ if (this.getConfig().isUseSandboxEnv()) {
+ url = this.getConfig().getPayBaseUrl() + "/sandboxnew/pay/refundv2";
+ }
+
+ String responseContent = this.post(url, request.toXML(), true);
+ WxPayRefundResult result = BaseWxPayResult.fromXML(responseContent, WxPayRefundResult.class);
+ result.composePromotionDetails();
+ result.checkResult(this, request.getSignType(), true);
+ return result;
+ }
+
@Override
public WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, String outRefundNo, String refundId)
throws WxPayException {
@@ -152,6 +168,18 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
return result;
}
+ @Override
+ public WxPayRefundQueryResult refundQueryV2(WxPayRefundQueryRequest request) throws WxPayException {
+ request.checkAndSign(this.getConfig());
+
+ String url = this.getPayBaseUrl() + "/pay/refundqueryv2";
+ String responseContent = this.post(url, request.toXML(), false);
+ WxPayRefundQueryResult result = BaseWxPayResult.fromXML(responseContent, WxPayRefundQueryResult.class);
+ result.composePromotionDetails();
+ result.checkResult(this, request.getSignType(), true);
+ return result;
+ }
+
@Override
public WxPayOrderNotifyResult parseOrderNotifyResult(String xmlData) throws WxPayException {
return this.parseOrderNotifyResult(xmlData, null);
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequestTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequestTest.java
new file mode 100644
index 000000000..1118edea8
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/request/WxPayRefundRequestTest.java
@@ -0,0 +1,47 @@
+package com.github.binarywang.wxpay.bean.request;
+
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Binary Wang
+ * @date 2020-06-07
+ */
+public class WxPayRefundRequestTest {
+
+ @Test
+ public void testToXML() {
+ WxPayRefundRequest refundRequest = new WxPayRefundRequest();
+ refundRequest.setAppid("wx2421b1c4370ec43b");
+ refundRequest.setMchId("10000100");
+ refundRequest.setNonceStr("6cefdb308e1e2e8aabd48cf79e546a02");
+ refundRequest.setNotifyUrl("https://weixin.qq.com/");
+ refundRequest.setOutRefundNo("1415701182");
+ refundRequest.setOutTradeNo("1415757673");
+ refundRequest.setRefundFee(1);
+ refundRequest.setTotalFee(1);
+ refundRequest.setTransactionId("");
+ refundRequest.setDetail("{\"goods_detail\":[{\"goods_id\":\"商品编码\",\"wxpay_goods_id\":\"1001\",\"goods_name\":\"iPhone6s\n" +
+ "16G\",\"refund_amount\":528800,\"refund_quantity\":1,\"price\":528800},{\"goods_id\":\"商品编码\",\"wxpay_goods_id\":\"1001\",\"goods_name\":\"iPhone6s\n" +
+ "16G\",\"refund_amount\"\":528800,\"refund_quantity\":1,\"price\":608800}]}");
+ refundRequest.setSign("FE56DD4AA85C0EECA82C35595A69E153");
+
+ assertThat(refundRequest.toXML())
+ .isEqualTo("\n" +
+ " wx2421b1c4370ec43b \n" +
+ " 10000100 \n" +
+ " 6cefdb308e1e2e8aabd48cf79e546a02 \n" +
+ " FE56DD4AA85C0EECA82C35595A69E153 \n" +
+ " \n" +
+ " 1415757673 \n" +
+ " 1415701182 \n" +
+ " 1 \n" +
+ " 1 \n" +
+ " https://weixin.qq.com/ \n" +
+ " \n" +
+ " ");
+ }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayRefundResultTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayRefundResultTest.java
index e03028ad4..3df177f2c 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayRefundResultTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/bean/result/WxPayRefundResultTest.java
@@ -13,7 +13,6 @@ import static org.assertj.core.api.Assertions.assertThat;
* @author Binary Wang
*/
public class WxPayRefundResultTest {
-
@Test
public void testFromXML() {
/*
@@ -49,6 +48,49 @@ public class WxPayRefundResultTest {
assertThat(result.getRefundCoupons().get(0).getCouponRefundFee()).isEqualTo(1);
}
+ @Test
+ public void testFromXML_danpin() {
+ //样例来自:https://pay.weixin.qq.com/wiki/doc/api/danpin.php?chapter=9_103&index=3
+ String xmlString = "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "1 \n" +
+ "1 \n" +
+ "1 \n" +
+ "1 \n" +
+ "{\"promotion_detail\":[{\"promotion_id\":\"109519\",\"scope\":\"SINGLE\",\"type\":\"DISCOUNT\",\"refund_amount\":5,\"goods_detail\":[{\"goods_id\":\"a_goods1\",\"refund_quantity\":7,\"price\":1,\"refund_amount\":4},{\"goods_id\":\"a_goods2\",\"refund_quantity\":1,\"price\":2,\"refund_amount\":1}]}]} \n" +
+ " ";
+
+ WxPayRefundResult result = BaseWxPayResult.fromXML(xmlString, WxPayRefundResult.class);
+ result.composePromotionDetails();
+ assertThat(result.getPromotionDetails()).isNotEmpty();
+ assertThat(result.getPromotionDetails().get(0).getPromotionId()).isEqualTo("109519");
+ assertThat(result.getPromotionDetails().get(0).getRefundAmount()).isEqualTo(5);
+ assertThat(result.getPromotionDetails().get(0).getScope()).isEqualTo("SINGLE");
+ assertThat(result.getPromotionDetails().get(0).getType()).isEqualTo("DISCOUNT");
+
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails()).isNotEmpty();
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(0).getGoodsId()).isEqualTo("a_goods1");
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(0).getRefundQuantity()).isEqualTo(7);
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(0).getRefundAmount()).isEqualTo(4);
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(0).getPrice()).isEqualTo(1);
+
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(1).getGoodsId()).isEqualTo("a_goods2");
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(1).getRefundQuantity()).isEqualTo(1);
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(1).getRefundAmount()).isEqualTo(1);
+ assertThat(result.getPromotionDetails().get(0).getGoodsDetails().get(1).getPrice()).isEqualTo(2);
+ }
+
@Test
public void testFromXMLFastMode() {
/*
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 9e129bcbe..c12c38195 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
@@ -315,6 +315,18 @@ public class BaseWxPayServiceImplTest {
log.info(result.toString());
}
+ @Test
+ public void testRefundV2() throws WxPayException {
+ WxPayRefundResult result = this.payService.refundV2(
+ WxPayRefundRequest.newBuilder()
+ .outRefundNo("aaa")
+ .outTradeNo("1111")
+ .totalFee(1222)
+ .refundFee(111)
+ .build());
+ log.info(result.toString());
+ }
+
/**
* Test method for {@link WxPayService#refundQuery(String, String, String, String)} .
*
@@ -341,6 +353,11 @@ public class BaseWxPayServiceImplTest {
log.info(result.toString());
}
+ @Test
+ public void testRefundQueryV2() throws WxPayException {
+ this.payService.refundQueryV2(WxPayRefundQueryRequest.newBuilder().outRefundNo("1").build());
+ }
+
/**
* Test parse refund notify result.
*
@@ -686,4 +703,5 @@ public class BaseWxPayServiceImplTest {
assertThat(result).isNotNull();
System.out.println(result);
}
+
}