🆕 #1768 微信支付增加电商收付通完结分账和退款接口

* 微信收付通增加完结分账和退款接口
This commit is contained in:
f00lish 2020-09-18 14:07:03 +08:00 committed by GitHub
parent b660bfd4a0
commit 115f910e3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 572 additions and 0 deletions

View File

@ -0,0 +1,81 @@
package com.github.binarywang.wxpay.bean.ecommerce;
import com.google.gson.annotations.SerializedName;
import lombok.*;
import java.io.Serializable;
/**
* 完结分账 对象
* <pre>
* 文档地址https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_5.shtml
* </pre>
* @author: f00lish
* @date: 2020/09/12
*/
@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class FinishOrderRequest implements Serializable {
private static final long serialVersionUID = -8662837652326828377L;
/**
* <pre>
* 字段名二级商户号
* 变量名sub_mchid
* 是否必填
* 类型string32
* 描述
* 分账出资的电商平台二级商户填写微信支付分配的商户号
* 示例值1900000109
* </pre>
*/
@SerializedName(value = "sub_mchid")
private String subMchid;
/**
* <pre>
* 字段名微信订单号
* 变量名transaction_id
* 是否必填
* 类型string32
* 描述
* 微信支付订单号
* 示例值4208450740201411110007820472
* </pre>
*/
@SerializedName(value = "transaction_id")
private String transactionId;
/**
* <pre>
* 字段名商户分账单号
* 变量名out_order_no
* 是否必填
* 类型string64
* 描述
* 商户系统内部的分账单号在商户系统内部唯一单次分账多次分账完结分账应使用不同的商户分账单号同一分账单号多次请求等同一次
* 示例值P20150806125346
* </pre>
*/
@SerializedName(value = "out_order_no")
private String outOrderNo;
/**
* <pre>
* 字段名分账描述
* 变量名description
* 是否必填
* 类型string80
* 描述
* 分账的原因描述分账账单中需要体现
* 示例值分给商户1900000109
* </pre>
*/
@SerializedName(value = "description")
private String description;
}

View File

@ -0,0 +1,206 @@
package com.github.binarywang.wxpay.bean.ecommerce;
/**
* @author: f00lish
* @date: 2020/09/17
*/
import com.google.gson.annotations.SerializedName;
import lombok.*;
import java.io.Serializable;
/**
* 退款申请
* * <pre>
* * 文档地址https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_1.shtml
* * </pre>
* @author: f00lish
* @date: 2020/09/14
*/
@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RefundsRequest implements Serializable {
private static final long serialVersionUID = -3186851559004865784L;
/**
* <pre>
* 字段名二级商户号
* 变量名sub_mchid
* 是否必填
* 类型string32
* 描述
* 微信支付分配二级商户的商户号
* 示例值1900000109
* </pre>
*/
@SerializedName(value = "sub_mchid")
private String subMchid;
/**
* <pre>
* 字段名电商平台APPID
* 变量名sp_appid
* 是否必填
* 类型string32
* 描述
* 电商平台在微信公众平台申请服务号对应的APPID申请商户功能的时候微信支付会配置绑定关系
* 示例值wx8888888888888888
* </pre>
*/
@SerializedName(value = "sp_appid")
private String spAppid;
/**
* <pre>
* 字段名二级商户APPID
* 变量名sub_appid
* 是否必填
* 类型string32
* 描述
* 二级商户在微信申请公众号成功后分配的帐号ID需要电商平台侧配置绑定关系才能传参
* 示例值wxd678efh567hg6999
* </pre>
*/
@SerializedName(value = "sub_appid")
private String subAppid;
/**
* <pre>
* 字段名微信订单号
* 变量名transaction_id
* 是否必填与out_order_no二选一
* 类型string32
* 描述
* 微信支付订单号
* 示例值4208450740201411110007820472
* </pre>
*/
@SerializedName(value = "transaction_id")
private String transactionId;
/**
* <pre>
* 字段名商户订单号
* 变量名out_order_no
* 是否必填与transaction_id二选一
* 类型string64
* 描述
* 原支付交易对应的商户订单号
* 示例值P20150806125346
* </pre>
*/
@SerializedName(value = "out_order_no")
private String outOrderNo;
/**
* <pre>
* 字段名商户退款单号
* 变量名out_refund_no
* 是否必填
* 类型string64
* 描述
* 商户系统内部的退款单号商户系统内部唯一只能是数字大小写字母_-|*@同一退款单号多次请求只退一笔
* 示例值1217752501201407033233368018
* </pre>
*/
@SerializedName(value = "out_refund_no")
private String outRefundNo;
/**
* <pre>
* 字段名退款原因
* 变量名reason
* 是否必填
* 类型string80
* 描述
* 若商户传入会在下发给用户的退款消息中体现退款原因
* 注意若订单退款金额1元且属于部分退款则不会在退款消息中体现退款原因
* 示例值商品已售完
* </pre>
*/
@SerializedName(value = "reason")
private String reason;
/**
* <pre>
* 字段名订单金额
* 变量名amount
* 是否必填
* 类型object
* 描述
* 订单金额信息
* </pre>
*/
@SerializedName(value = "amount")
private Amount amount;
/**
* <pre>
* 字段名退款结果回调url
* 变量名notify_url
* 是否必填
* 类型string256
* 描述
* 异步接收微信支付退款结果通知的回调地址通知url必须为外网可访问的url不能携带参数 如果参数中传了notify_url则商户平台上配置的回调地址将不会生效优先回调当前传的地址
* 示例值https://weixin.qq.com
* </pre>
*/
@SerializedName(value = "notify_url")
private String notifyUrl;
@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Amount implements Serializable {
private static final long serialVersionUID = 7383027142329410399L;
/**
* <pre>
* 字段名退款金额
* 变量名refund
* 是否必填
* 类型int
* 描述
* 退款金额币种的最小单位只能为整数不能超过原订单支付金额
* 示例值888
* </pre>
*/
@SerializedName(value = "refund")
private Integer refund;
/**
* <pre>
* 字段名原订单金额
* 变量名total
* 是否必填
* 类型int64
* 描述
* 订单总金额单位为分
* 示例值888
* </pre>
*/
@SerializedName(value = "total")
private Integer total;
/**
* <pre>
* 字段名币类型
* 变量名currency
* 是否必填
* 类型string(18)
* 描述
* 符合ISO 4217标准的三位字母代码目前只支持人民币CNY
* 示例值CNY
* </pre>
*/
@SerializedName(value = "currency")
private String currency;
}
}

View File

@ -0,0 +1,247 @@
package com.github.binarywang.wxpay.bean.ecommerce;
/**
* @author: f00lish
* @date: 2020/09/17
*/
import com.google.gson.annotations.SerializedName;
import lombok.*;
import java.io.Serializable;
import java.util.Date;
/**
* 退款结果
* * <pre>
* * 文档地址https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_1.shtml
* * </pre>
* @author: f00lish
* @date: 2020/09/14
*/
@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class RefundsResult implements Serializable {
private static final long serialVersionUID = -3186851559004865784L;
/**
* <pre>
* 字段名微信退款单号
* 变量名refund_id
* 是否必填
* 类型string32
* 描述
* 微信支付退款订单号
* 示例值1217752501201407033233368018
* </pre>
*/
@SerializedName(value = "refund_id")
private String refundId;
/**
* <pre>
* 字段名商户订单号
* 变量名out_order_no
* 是否必填与transaction_id二选一
* 类型string64
* 描述
* 原支付交易对应的商户订单号
* 示例值P20150806125346
* </pre>
*/
@SerializedName(value = "out_order_no")
private String outOrderNo;
/**
* <pre>
* 字段名退款创建时间
* 变量名create_time
* 是否必填
* 类型string64
* 描述
* 退款受理时间遵循rfc3339标准格式格式为YYYY-MM-DDTHH:mm:ss+TIMEZONEYYYY-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
* </pre>
*/
@SerializedName(value = "create_time")
private Date createTime;
/**
* <pre>
* 字段名订单金额
* 变量名amount
* 是否必填
* 类型object
* 描述
* 订单金额信息
* </pre>
*/
@SerializedName(value = "amount")
private Amount amount;
/**
* <pre>
* 字段名优惠退款详情
* 变量名promotion_detail
* 是否必填
* 类型array
* 描述
* 优惠退款功能信息
* </pre>
*/
@SerializedName(value = "promotion_detail")
private PromotionDetail[] promotionDetail;
@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class Amount implements Serializable {
private static final long serialVersionUID = 7383027142329410399L;
/**
* <pre>
* 字段名退款金额
* 变量名refund
* 是否必填
* 类型int
* 描述
* 退款金额币种的最小单位只能为整数不能超过原订单支付金额
* 示例值888
* </pre>
*/
@SerializedName(value = "refund")
private Integer refund;
/**
* <pre>
* 字段名用户退款金额
* 变量名payer_refund
* 是否必填
* 类型int64
* 描述
* 退款给用户的金额不包含所有优惠券金额
* 示例值888
* </pre>
*/
@SerializedName(value = "payer_refund")
private Integer payerRefund;
/**
* <pre>
* 字段名优惠退款金额
* 变量名discount_refund
* 是否必填
* 类型int64
* 描述
* 优惠券的退款金额原支付单的优惠按比例退款
* 示例值888
* </pre>
*/
@SerializedName(value = "discount_refund")
private Integer discountRefund;
/**
* <pre>
* 字段名币类型
* 变量名currency
* 是否必填
* 类型string(18)
* 描述
* 符合ISO 4217标准的三位字母代码目前只支持人民币CNY
* 示例值CNY
* </pre>
*/
@SerializedName(value = "currency")
private String currency;
}
@Data
@Builder
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public static class PromotionDetail implements Serializable {
private static final long serialVersionUID = 7383027142329410399L;
/**
* <pre>
* 字段名券ID
* 变量名promotion_id
* 是否必填
* 类型string(32)
* 描述
* 券或者立减优惠id
* 示例值109519
* </pre>
*/
@SerializedName(value = "promotion_id")
private String promotionId;
/**
* <pre>
* 字段名优惠范围
* 变量名scope
* 是否必填
* 类型string(32)
* 描述
* 枚举值
* GLOBAL全场代金券
* SINGLE单品优惠
* 示例值SINGLE
* </pre>
*/
@SerializedName(value = "scope")
private String scope;
/**
* <pre>
* 字段名优惠类型
* 变量名type
* 是否必填
* 类型string(32)
* 描述
* 枚举值
* COUPON充值型代金券商户需要预先充值营销经费
* DISCOUNT免充值型优惠券商户不需要预先充值营销经费
* 示例值DISCOUNT
* </pre>
*/
@SerializedName(value = "type")
private String type;
/**
* <pre>
* 字段名优惠券面额
* 变量名amount
* 是否必填
* 类型int
* 描述
* 用户享受优惠的金额优惠券面额=微信出资金额+商家出资金额+其他出资方金额
* 示例值5
* </pre>
*/
@SerializedName(value = "amount")
private Integer amount;
/**
* <pre>
* 字段名优惠退款金额
* 变量名refund_amount
* 是否必填
* 类型int
* 描述
* 代金券退款金额<=退款金额退款金额-代金券或立减优惠退款金额为现金说明详见代金券或立减优惠https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_1
* 示例值CNY
* </pre>
*/
@SerializedName(value = "refund_amount")
private Integer refundAmount;
}
}

View File

@ -213,4 +213,28 @@ public interface EcommerceService {
*/
ReturnOrdersResult returnOrders(ReturnOrdersRequest request) throws WxPayException;
/**
* <pre>
* 完结分账API
* 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_5.shtml
* </pre>
*
* @param request 完结分账请求
* @return 返回数据 return orders result
* @throws WxPayException the wx pay exception
*/
ProfitSharingResult finishOrder(FinishOrderRequest request) throws WxPayException;
/**
* <pre>
* 退款申请API
* 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_1.shtml
* </pre>
*
* @param request 退款请求
* @return 返回数据 return refunds result
* @throws WxPayException the wx pay exception
*/
RefundsResult refunds(RefundsRequest request) throws WxPayException;
}

View File

@ -162,6 +162,20 @@ public class EcommerceServiceImpl implements EcommerceService {
return GSON.fromJson(response, ReturnOrdersResult.class);
}
@Override
public ProfitSharingResult finishOrder(FinishOrderRequest request) throws WxPayException {
String url = String.format("%s/v3/ecommerce/profitsharing/finish-order", this.payService.getPayBaseUrl());
String response = this.payService.postV3(url, GSON.toJson(request));
return GSON.fromJson(response, ProfitSharingResult.class);
}
@Override
public RefundsResult refunds(RefundsRequest request) throws WxPayException {
String url = String.format("%s/v3/ecommerce/refunds/apply", this.payService.getPayBaseUrl());
String response = this.payService.postV3(url, GSON.toJson(request));
return GSON.fromJson(response, RefundsResult.class);
}
private boolean verifyNotifySign(SignatureHeader header, String data) {
String beforeSign = String.format("%s\n%s\n%s\n",
header.getTimeStamp(),