微信V3实现合单支付

This commit is contained in:
egzosn
2021-10-06 15:46:21 +08:00
parent d4096194f1
commit 6885cacfc5
15 changed files with 904 additions and 65 deletions

View File

@@ -0,0 +1,159 @@
package com.egzosn.pay.wx.v3.api;
import java.util.LinkedHashMap;
import java.util.Map;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.egzosn.pay.common.bean.CloseOrder;
import com.egzosn.pay.common.bean.Order;
import com.egzosn.pay.common.bean.OrderParaStructure;
import com.egzosn.pay.common.bean.PayMessage;
import com.egzosn.pay.common.bean.PayOrder;
import com.egzosn.pay.common.bean.result.PayException;
import com.egzosn.pay.common.exception.PayErrorException;
import com.egzosn.pay.common.http.HttpConfigStorage;
import com.egzosn.pay.common.util.DateUtils;
import com.egzosn.pay.common.util.MapGen;
import com.egzosn.pay.common.util.str.StringUtils;
import com.egzosn.pay.wx.v3.bean.WxTransactionType;
import com.egzosn.pay.wx.v3.bean.combine.CombinePayMessage;
import com.egzosn.pay.wx.v3.utils.WxConst;
/**
* 微信合单支付服务
*
* @author egan
* <pre>
* email egzosn@gmail.com
* date 2016-5-18 14:09:01
* </pre>
*/
public class WxCombinePayService extends WxPayService {
/**
* 创建支付服务
*
* @param payConfigStorage 微信对应的支付配置
*/
public WxCombinePayService(WxPayConfigStorage payConfigStorage) {
super(payConfigStorage);
}
/**
* 创建支付服务
*
* @param payConfigStorage 微信对应的支付配置
* @param configStorage 微信对应的网络配置包含代理配置、ssl证书配置
*/
public WxCombinePayService(WxPayConfigStorage payConfigStorage, HttpConfigStorage configStorage) {
super(payConfigStorage, configStorage);
}
/**
* 获取公共参数
*
* @return 公共参数
*/
public Map<String, Object> getPublicParameters() {
Map<String, Object> parameters = new LinkedHashMap<>();
parameters.put(WxConst.COMBINE_APPID, payConfigStorage.getAppId());
parameters.put(WxConst.COMBINE_MCH_ID, payConfigStorage.getMchId());
return parameters;
}
/**
* 初始化通知URL必须为直接可访问的URL不允许携带查询串要求必须为https地址。
*
* @param parameters 订单参数
* @param order 订单信息
* @return 订单参数
*/
public void initNotifyUrl(Map<String, Object> parameters, Order order) {
OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, payConfigStorage.getNotifyUrl());
OrderParaStructure.loadParameters(parameters, WxConst.NOTIFY_URL, order);
}
/**
* 微信统一下单接口
*
* @param order 支付订单集
* @return 下单结果
*/
public JSONObject unifiedOrder(PayOrder order) {
//统一下单
Map<String, Object> parameters = getPublicParameters();
// 订单号
parameters.put(WxConst.COMBINE_OUT_TRADE_NO, order.getOutTradeNo());
OrderParaStructure.loadDateParameters(parameters, WxConst.TIME_START, order, DateUtils.YYYY_MM_DD_T_HH_MM_SS);
OrderParaStructure.loadDateParameters(parameters, WxConst.TIME_EXPIRE, order, DateUtils.YYYY_MM_DD_T_HH_MM_SS);
initNotifyUrl(parameters, order);
//支付场景描述
OrderParaStructure.loadParameters(parameters, WxConst.SCENE_INFO, order);
//子单信息 最多支持子单条数50
OrderParaStructure.loadParameters(parameters, WxConst.SUB_ORDERS, order);
//支付者信息
if (StringUtils.isNotEmpty(order.getOpenid())) {
parameters.put("combine_payer_info", new MapGen<>("openid", order.getOpenid()).getAttr());
}
return getAssistService().doExecute(parameters, order);
}
/**
* 交易查询接口
*
* @param transactionId 微信支付平台订单号
* @param outTradeNo 商户单号
* @return 返回查询回来的结果集,支付方原值返回
*/
@Override
public Map<String, Object> query(String transactionId, String outTradeNo) {
return getAssistService().doExecute("", WxTransactionType.COMBINE_TRANSACTION, outTradeNo);
}
/**
* 交易关闭接口
*
* @param transactionId 支付平台订单号
* @param outTradeNo 商户单号
* @return 返回支付方交易关闭后的结果
*/
@Override
public Map<String, Object> close(String transactionId, String outTradeNo) {
throw new PayErrorException(new PayException("failure", "合单关闭必须要有子单"));
}
/**
* 交易关闭接口
*
* @param closeOrder 关闭订单
* @return 返回支付方交易关闭后的结果
*/
@Override
public Map<String, Object> close(CloseOrder closeOrder) {
Map<String, Object> parameters = new MapGen<String, Object>(WxConst.COMBINE_APPID, payConfigStorage.getAppId())
.keyValue(WxConst.SUB_ORDERS, closeOrder.getAttr(WxConst.SUB_ORDERS))
.getAttr();
String requestBody = JSON.toJSONString(parameters, SerializerFeature.WriteMapNullValue);
return getAssistService().doExecute(requestBody, WxTransactionType.COMBINE_CLOSE, closeOrder.getOutTradeNo());
}
/**
* 创建消息
*
* @param message 支付平台返回的消息
* @return 支付消息对象
*/
@Override
public PayMessage createMessage(Map<String, Object> message) {
return CombinePayMessage.create(message);
}
}

View File

@@ -22,6 +22,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.egzosn.pay.common.api.BasePayService;
import com.egzosn.pay.common.bean.BillType;
import com.egzosn.pay.common.bean.CloseOrder;
import com.egzosn.pay.common.bean.CurType;
import com.egzosn.pay.common.bean.MethodType;
import com.egzosn.pay.common.bean.NoticeParams;
@@ -81,27 +82,13 @@ public class WxPayService extends BasePayService<WxPayConfigStorage> {
/**
* 辅助api
*/
private volatile WxPayAssistService wxPayAssistService;
private volatile WxPayAssistService assistService;
/**
* 微信参数构造器
*/
private volatile WxParameterStructure wxParameterStructure;
public WxPayAssistService getAssistService() {
if (null == wxPayAssistService) {
wxPayAssistService = new DefaultWxPayAssistService(this);
//在这预先进行初始化
wxPayAssistService.refreshCertificate();
}
return wxPayAssistService;
}
public void setAssistService(WxPayAssistService wxPayAssistService) {
this.wxPayAssistService = wxPayAssistService;
}
/**
* 创建支付服务
*
@@ -123,26 +110,24 @@ public class WxPayService extends BasePayService<WxPayConfigStorage> {
/**
* 初始化之后执行
* 辅助api
* @return 辅助api
*/
@Override
protected void initAfter() {
new Thread(() -> {
payConfigStorage.loadCertEnvironment();
wxParameterStructure = new WxParameterStructure(payConfigStorage);
public WxPayAssistService getAssistService() {
if (null == assistService) {
assistService = new DefaultWxPayAssistService(this);
//在这预先进行初始化
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
}
getAssistService();
}).start();
assistService.refreshCertificate();
}
return assistService;
}
public void setAssistService(WxPayAssistService assistService) {
this.assistService = assistService;
}
/**
* 设置api服务器地址
*
@@ -154,6 +139,14 @@ public class WxPayService extends BasePayService<WxPayConfigStorage> {
return this;
}
public String getApiServerUrl() {
return apiServerUrl;
}
public WxParameterStructure getWxParameterStructure() {
return wxParameterStructure;
}
/**
* 根据交易类型获取url
*
@@ -323,6 +316,7 @@ public class WxPayService extends BasePayService<WxPayConfigStorage> {
* @param is 请求流
* @return 获得回调的请求参数
*/
@Deprecated
@Override
public Map<String, Object> getParameter2Map(Map<String, String[]> parameterMap, InputStream is) {
throw new PayErrorException(new WxPayError(FAILURE, "微信V3不支持方式"));
@@ -350,7 +344,7 @@ public class WxPayService extends BasePayService<WxPayConfigStorage> {
noticeParams.setBody(JSON.parseObject(data));
}
catch (IOException e) {
LOG.error("获取回调参数异常", e);
throw new PayErrorException(new WxPayError(FAILURE, "获取回调参数异常"), e);
}
Map<String, List<String>> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
@@ -457,8 +451,20 @@ public class WxPayService extends BasePayService<WxPayConfigStorage> {
*/
@Override
public Map<String, Object> close(String transactionId, String outTradeNo) {
return close(new CloseOrder(outTradeNo));
}
/**
* 交易关闭接口
*
* @param closeOrder 关闭订单
* @return 返回支付方交易关闭后的结果
*/
@Override
public Map<String, Object> close(CloseOrder closeOrder) {
String parameters = wxParameterStructure.getSpParameters();
return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, outTradeNo);
return getAssistService().doExecute(parameters, WxTransactionType.CLOSE, closeOrder.getOutTradeNo());
}
/**

View File

@@ -25,11 +25,14 @@ public enum WxTransactionType implements TransactionType {
/**
* 获取证书.
*/
CERT("certificates", MethodType.GET),
CERT("/v3/certificates", MethodType.GET),
//-----------------------------------------------------------------
//以下为直连与服务商支付方式
/**
* 公众号支付
* 微信公众号支付或者小程序支付
*/
JSAPI("pay{partner}/transactions/jsapi", MethodType.POST) {
JSAPI("/v3/pay{partner}/transactions/jsapi", MethodType.POST) {
@Override
public void setAttribute(Map<String, Object> parameters, PayOrder order) {
String key = parameters.containsKey("sub_mchid") ? "sub_openid" : "openid";
@@ -40,15 +43,15 @@ public enum WxTransactionType implements TransactionType {
/**
* 二维码支付
*/
NATIVE("pay{partner}/transactions/native", MethodType.POST, true),
NATIVE("/v3/pay{partner}/transactions/native", MethodType.POST, true),
/**
* 移动支付
*/
APP("pay{partner}/transactions/app", MethodType.POST),
APP("/v3/pay{partner}/transactions/app", MethodType.POST),
/**
* H5支付
*/
H5("pay{partner}/transactions/h5", MethodType.POST, true) {
H5("/v3/pay{partner}/transactions/h5", MethodType.POST, true) {
@Override
public void setAttribute(Map<String, Object> parameters, PayOrder order) {
Object sceneInfoObj = parameters.get(WxConst.SCENE_INFO);
@@ -79,7 +82,7 @@ public enum WxTransactionType implements TransactionType {
* 兼容 后期会抛弃
*/
@Deprecated
MWEB("pay{partner}/transactions/h5", MethodType.POST, true) {
MWEB("/v3/pay{partner}/transactions/h5", MethodType.POST, true) {
@Override
public void setAttribute(Map<String, Object> parameters, PayOrder order) {
H5.setAttribute(parameters, order);
@@ -91,35 +94,64 @@ public enum WxTransactionType implements TransactionType {
* 查询订单
* 兼容V2的方式通过入参来决定
*/
QUERY("pay{partner}/transactions/", MethodType.GET),
QUERY("/v3/pay{partner}/transactions/", MethodType.GET),
/**
* 微信支付订单号查询
*/
QUERY_TRANSACTION_ID("pay{partner}/transactions/id/{transaction_id}", MethodType.GET),
QUERY_TRANSACTION_ID("/v3/pay{partner}/transactions/id/{transaction_id}", MethodType.GET),
/**
* 商户订单号查询
*/
QUERY_OUT_TRADE_NO("pay{partner}/transactions/out-trade-no/{out_trade_no}", MethodType.GET),
QUERY_OUT_TRADE_NO("/v3/pay{partner}/transactions/out-trade-no/{out_trade_no}", MethodType.GET),
/**
* 关闭订单
*/
CLOSE("pay{partner}/transactions/out-trade-no/{out_trade_no}/close", MethodType.POST),
CLOSE("/v3/pay{partner}/transactions/out-trade-no/{out_trade_no}/close", MethodType.POST),
/**
* 申请退款
*/
REFUND("refund/domestic/refunds", MethodType.POST),
REFUND("/v3/refund/domestic/refunds", MethodType.POST),
/**
* 查询退款
*/
REFUND_QUERY("refund/domestic/refunds/{out_refund_no}", MethodType.GET),
REFUND_QUERY("/v3/refund/domestic/refunds/{out_refund_no}", MethodType.GET),
/**
* 申请交易账单
*/
TRADE_BILL("bill/tradebill", MethodType.GET),
TRADE_BILL("/v3/bill/tradebill", MethodType.GET),
/**
* 申请资金账单
*/
FUND_FLOW_BILL("bill/fundflowbill", MethodType.GET)
FUND_FLOW_BILL("/v3/bill/fundflowbill", MethodType.GET),
//-----------------------------------------------------------------
//以下为合并支付
/**
* 合单下单-APP支付API.
*/
COMBINE_APP("/v3/combine-transactions/app", MethodType.POST),
/**
* 合单下单-微信公众号支付或者小程序支付.
*/
COMBINE_JSAPI("/v3/combine-transactions/jsapi", MethodType.POST),
/**
* 合单下单-H5支付API.
*/
COMBINE_H5("/v3/combine-transactions/h5", MethodType.POST, true),
/**
* 合单下单-Native支付API.
*/
COMBINE_NATIVE("/v3/combine-transactions/native", MethodType.POST, true),
/**
* 合单查询订单API.
*/
COMBINE_TRANSACTION("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}", MethodType.GET),
/**
* 合单关闭订单API.
*/
COMBINE_CLOSE("/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close", MethodType.POST),
;
WxTransactionType(String type, MethodType method) {

View File

@@ -0,0 +1,34 @@
package com.egzosn.pay.wx.v3.bean.combine;
import com.alibaba.fastjson.annotation.JSONField;
import com.egzosn.pay.wx.v3.bean.response.order.Amount;
/**
* 合单支付订单金额信息.
*
* @author Egan
* <pre>
* email egan@egzosn.com
* date 2021/10/5
* </pre>
*/
public class CombineAmount extends Amount {
/**
* 子单金额,单位为分,必填
* 境外场景下标价金额要超过商户结算币种的最小单位金额例如结算币种为美元则标价金额必须大于1美分
*/
@JSONField(name = "total_amount")
private Integer totalAmount;
public Integer getTotalAmount() {
return totalAmount;
}
public void setTotalAmount(Integer totalAmount) {
this.totalAmount = totalAmount;
}
}

View File

@@ -0,0 +1,30 @@
package com.egzosn.pay.wx.v3.bean.combine;
import java.util.List;
import com.egzosn.pay.common.bean.CloseOrder;
import com.egzosn.pay.wx.v3.utils.WxConst;
/**
* 微信合单关闭订单
* @author Egan
* @email egan@egzosn.com
* @date 2021/10/6
*/
public class CombineCloseOrder extends CloseOrder {
/**
* 子单信息必填最多50单
*/
private List<CombineSubOrder> subOrders;
public List<CombineSubOrder> getSubOrders() {
return subOrders;
}
public void setSubOrders(List<CombineSubOrder> subOrders) {
this.subOrders = subOrders;
addAttr(WxConst.SUB_ORDERS, subOrders);
}
}

View File

@@ -0,0 +1,115 @@
package com.egzosn.pay.wx.v3.bean.combine;
import java.util.List;
import java.util.Map;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.annotation.JSONField;
import com.egzosn.pay.common.bean.PayMessage;
import com.egzosn.pay.wx.v3.bean.order.SceneInfo;
import com.egzosn.pay.wx.v3.bean.response.order.Payer;
import com.egzosn.pay.wx.v3.utils.WxConst;
/**
* 合单支付回调消息,兼容退款回调
* @author Egan
* <pre>
* email egan@egzosn.com
* date 2021/10/4
* </pre>
*/
public class CombinePayMessage extends PayMessage {
/**
* 合单商户appid即合单发起方的appid
*/
@JSONField(name = WxConst.COMBINE_APPID)
private String combineAppid;
/**
* 合单商户号.
*/
@JSONField(name = WxConst.COMBINE_MCH_ID)
private String combineMchid;
/**
* 合单商户订单号.
*/
@JSONField(name = WxConst.COMBINE_OUT_TRADE_NO)
private String combineOutTradeNo;
/**
* 支付者信息
*/
@JSONField(name = "combine_payer_info")
private Payer combinePayerInfo;
/**
* 场景信息合单支付回调只返回device_id
*/
@JSONField(name = WxConst.SCENE_INFO)
private SceneInfo sceneInfo;
/**
* 合单支付回调子订单.
*/
@JSONField(name = WxConst.SUB_ORDERS)
private List<CombineSubOrder> subOrders;
public String getCombineAppid() {
return combineAppid;
}
public void setCombineAppid(String combineAppid) {
this.combineAppid = combineAppid;
}
public String getCombineMchid() {
return combineMchid;
}
public void setCombineMchid(String combineMchid) {
this.combineMchid = combineMchid;
}
public String getCombineOutTradeNo() {
return combineOutTradeNo;
}
public void setCombineOutTradeNo(String combineOutTradeNo) {
this.combineOutTradeNo = combineOutTradeNo;
}
public Payer getCombinePayerInfo() {
return combinePayerInfo;
}
public void setCombinePayerInfo(Payer combinePayerInfo) {
this.combinePayerInfo = combinePayerInfo;
}
public SceneInfo getSceneInfo() {
return sceneInfo;
}
public void setSceneInfo(SceneInfo sceneInfo) {
this.sceneInfo = sceneInfo;
}
public List<CombineSubOrder> getSubOrders() {
return subOrders;
}
public void setSubOrders(List<CombineSubOrder> subOrders) {
this.subOrders = subOrders;
}
public static final CombinePayMessage create(Map<String, Object> message) {
CombinePayMessage payMessage = new JSONObject(message).toJavaObject(CombinePayMessage.class);
// payMessage.setPayType("");
payMessage.setPayMessage(message);
return payMessage;
}
}

View File

@@ -0,0 +1,92 @@
package com.egzosn.pay.wx.v3.bean.combine;
import java.util.Date;
import java.util.List;
import com.egzosn.pay.common.bean.PayOrder;
import com.egzosn.pay.wx.v3.bean.order.SceneInfo;
import com.egzosn.pay.wx.v3.bean.order.SubOrder;
import com.egzosn.pay.wx.v3.utils.WxConst;
/**
* 合并支付订单
*
* @author Egan
* <pre>
* email egan@egzosn.com
* date 2021/10/5
* </pre>
*/
public class CombinePayOrder extends PayOrder {
/**
* 子单信息必填最多50单
*/
private List<SubOrder> subOrders;
/**
* 交易起始时间,选填
*/
private Date timeStart;
/**
* 交易结束时间,选填
*/
private Date timeExpire;
/**
* 支付场景信息描述
*/
private SceneInfo sceneInfo;
public List<SubOrder> getSubOrders() {
return subOrders;
}
public void setSubOrders(List<SubOrder> subOrders) {
this.subOrders = subOrders;
addAttr(WxConst.SUB_ORDERS, subOrders);
}
public Date getTimeStart() {
return timeStart;
}
public void setTimeStart(Date timeStart) {
this.timeStart = timeStart;
addAttr(WxConst.TIME_START, timeStart);
}
public Date getTimeExpire() {
return timeExpire;
}
public void setTimeExpire(Date timeExpire) {
this.timeExpire = timeExpire;
addAttr(WxConst.TIME_EXPIRE, timeExpire);
}
/**
* 合单支付总订单号要求32个字符内只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一 。
* @return 合单支付总订单号
*/
public String getCombineOutTradeNo() {
return getOutTradeNo();
}
/**
* 合单支付总订单号要求32个字符内只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一 。
* @param combineOutTradeNo 合单支付总订单号
*/
public void setCombineOutTradeNo(String combineOutTradeNo) {
setOutTradeNo(combineOutTradeNo);
}
public SceneInfo getSceneInfo() {
return sceneInfo;
}
public void setSceneInfo(SceneInfo sceneInfo) {
this.sceneInfo = sceneInfo;
addAttr(WxConst.SCENE_INFO, sceneInfo);
}
}

View File

@@ -0,0 +1,42 @@
package com.egzosn.pay.wx.v3.bean.combine;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 子单信息最多50单.
* @author Egan
* <pre>
* email egan@egzosn.com
* date 2021/10/5
* </pre>
*/
public class CombineSubOrder {
/**
* 子单发起方商户号必填必须与发起方appid有绑定关系。
*/
private String mchid;
/**
* 商户系统内部订单号要求32个字符内只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
* 示例值20150806125346
*/
@JSONField(name = "out_trade_no")
private String outTradeNo;
public String getMchid() {
return mchid;
}
public void setMchid(String mchid) {
this.mchid = mchid;
}
public String getOutTradeNo() {
return outTradeNo;
}
public void setOutTradeNo(String outTradeNo) {
this.outTradeNo = outTradeNo;
}
}

View File

@@ -0,0 +1,45 @@
package com.egzosn.pay.wx.v3.bean.order;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 结算信息
* @author Egan
* <pre>
* email egan@egzosn.com
* date 2021/10/5
* </pre>
*/
public class SettleInfo {
/**
* 是否指定分账,枚举值
* true
* false
* 示例值true
*/
@JSONField(name = "profit_sharing")
private Boolean profitSharing;
/**
* 补差金额
* SettleInfo.profit_sharing为true时该金额才生效。
* 注意单笔订单最高补差金额为5000元
*/
@JSONField(name = "subsidy_amount")
private Integer subsidyAmount;
public Boolean getProfitSharing() {
return profitSharing;
}
public void setProfitSharing(Boolean profitSharing) {
this.profitSharing = profitSharing;
}
public Integer getSubsidyAmount() {
return subsidyAmount;
}
public void setSubsidyAmount(Integer subsidyAmount) {
this.subsidyAmount = subsidyAmount;
}
}

View File

@@ -0,0 +1,187 @@
package com.egzosn.pay.wx.v3.bean.order;
import java.util.Date;
import java.util.List;
import com.alibaba.fastjson.annotation.JSONField;
import com.egzosn.pay.wx.v3.bean.WxTransactionType;
import com.egzosn.pay.wx.v3.bean.combine.CombineAmount;
import com.egzosn.pay.wx.v3.bean.combine.CombineSubOrder;
import com.egzosn.pay.wx.v3.bean.response.order.PromotionDetail;
import com.egzosn.pay.wx.v3.bean.response.order.TradeState;
/**
* 子单信息最多50单.
*
* @author Egan
* <pre>
* email egan@egzosn.com
* date 2021/10/5
* </pre>
*/
public class SubOrder extends CombineSubOrder {
/**
* 合单支付订单金额信息,必填。
*/
private CombineAmount amount;
/**
* 商品描述必填需传入应用市场上的APP名字-实际商品名称,例如:天天爱消除-游戏充值。
*/
private String description;
/**
* 二级商户商户号,由微信支付生成并下发。
* <p>
* 服务商子商户的商户号,被合单方。
* <p>
* 直连商户不用传二级商户号。
*/
@JSONField(name = "sub_mchid")
private String subMchid;
/**
* 结算信息,选填
*/
@JSONField(name = "settle_info")
private SettleInfo settleInfo;
/**
* 银行类型,采用字符串类型的银行标识。
* 银行标识请参考 <a target= "_blank" href= "https://pay.weixin.qq.com/wiki/doc/apiv3/terms_definition/chapter1_1_3.shtml#part-6">《银行类型对照表》</a>
* 示例值CMC
*/
@JSONField(name = "bank_type")
private String bankType;
/**
* 附加数据在查询API和支付通知中原样返回可作为自定义参数使用
*/
private String attach;
/**
* 支付完成时间|| 退款完成时间遵循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
*/
@JSONField(name = "success_time", format = "yyyy-MM-dd'T'HH:mm:ssXXX")
private Date successTime;
/**
* 交易状态
*/
@JSONField(name = "trade_state")
private TradeState tradeState;
/**
* 交易类型
*/
@JSONField(name = "trade_type")
private WxTransactionType tradeType;
/**
* 微信支付侧订单号
*/
@JSONField(name = "transaction_id")
private String transactionId;
/**
* 优惠功能,子单有核销优惠券时有返回
*/
@JSONField(name = "promotion_detail")
private List<PromotionDetail> promotionDetail;
public CombineAmount getAmount() {
return amount;
}
public void setAmount(CombineAmount amount) {
this.amount = amount;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getSubMchid() {
return subMchid;
}
public void setSubMchid(String subMchid) {
this.subMchid = subMchid;
}
public SettleInfo getSettleInfo() {
return settleInfo;
}
public void setSettleInfo(SettleInfo settleInfo) {
this.settleInfo = settleInfo;
}
public String getBankType() {
return bankType;
}
public void setBankType(String bankType) {
this.bankType = bankType;
}
public String getAttach() {
return attach;
}
public void setAttach(String attach) {
this.attach = attach;
}
public Date getSuccessTime() {
return successTime;
}
public void setSuccessTime(Date successTime) {
this.successTime = successTime;
}
public TradeState getTradeState() {
return tradeState;
}
public void setTradeState(TradeState tradeState) {
this.tradeState = tradeState;
}
public WxTransactionType getTradeType() {
return tradeType;
}
public void setTradeType(WxTransactionType tradeType) {
this.tradeType = tradeType;
}
public String getTransactionId() {
return transactionId;
}
public void setTransactionId(String transactionId) {
this.transactionId = transactionId;
}
public List<PromotionDetail> getPromotionDetail() {
return promotionDetail;
}
public void setPromotionDetail(List<PromotionDetail> promotionDetail) {
this.promotionDetail = promotionDetail;
}
}

View File

@@ -16,7 +16,7 @@ import com.egzosn.pay.wx.v3.bean.response.order.PromotionDetail;
import com.egzosn.pay.wx.v3.bean.response.order.TradeState;
/**
* 支付回调消息
* 支付回调消息,兼容退款回调
* @author Egan
* <pre>
* email egan@egzosn.com
@@ -99,8 +99,36 @@ public class WxPayMessage extends PayMessage {
*/
@JSONField(name = "trade_state")
private TradeState tradeState;
/**
* 商户退款单号
*/
@JSONField(name = "out_refund_no")
private String outRefundNo;
/**
* 微信退款单号
*/
@JSONField(name = "refund_id")
private String refundId;
/**
* 退款状态,枚举值:
* SUCCESS退款成功
* CLOSE退款关闭
* ABNORMAL退款异常退款到银行发现用户的卡作废或者冻结了导致原路退款银行卡失败可前往【商户平台—>交易中心】,手动处理此笔退款
* 示例值SUCCESS
*/
@JSONField(name = "refund_status")
private TradeState refundStatus;
/**
* 退款入账账户
* 取当前退款单的退款入账方。
* 1、退回银行卡{银行名称}{卡类型}{卡尾号}
* 2、退回支付用户零钱: 支付用户零钱
* 3、退还商户: 商户基本账户、商户结算银行账户
* 4、退回支付用户零钱通支付用户零钱通
* 示例值招商银行信用卡0403
*/
@JSONField(name = "user_received_account")
private String userReceivedAccount;
/**
* 交易状态描述
* 示例值:支付成功
@@ -121,7 +149,7 @@ public class WxPayMessage extends PayMessage {
private String attach;
/**
* 支付完成时间遵循rfc3339标准格式
* 支付完成时间|| 退款完成时间遵循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秒。
@@ -301,6 +329,37 @@ public class WxPayMessage extends PayMessage {
return BigDecimal.valueOf(getAmount().getTotal());
}
public String getOutRefundNo() {
return outRefundNo;
}
public void setOutRefundNo(String outRefundNo) {
this.outRefundNo = outRefundNo;
}
public String getRefundId() {
return refundId;
}
public void setRefundId(String refundId) {
this.refundId = refundId;
}
public TradeState getRefundStatus() {
return refundStatus;
}
public void setRefundStatus(TradeState refundStatus) {
this.refundStatus = refundStatus;
}
public String getUserReceivedAccount() {
return userReceivedAccount;
}
public void setUserReceivedAccount(String userReceivedAccount) {
this.userReceivedAccount = userReceivedAccount;
}
public static final WxPayMessage create(Map<String, Object> message) {
WxPayMessage payMessage = new JSONObject(message).toJavaObject(WxPayMessage.class);

View File

@@ -2,25 +2,41 @@ package com.egzosn.pay.wx.v3.bean.response.order;
import java.math.BigDecimal;
import com.alibaba.fastjson.annotation.JSONField;
/**
* 回调中的订单金额信息
*
* @author Egan
* <pre>
* email egan@egzosn.com
* date 2021/10/4
* </pre>
*/
public class Amount extends com.egzosn.pay.wx.v3.bean.order.Amount{
public class Amount extends com.egzosn.pay.wx.v3.bean.order.Amount {
/**
* 用户支付金额,单位为分。
*/
@JSONField(name = "payer_total")
private BigDecimal payerTotal;
/**
* 用户支付币种
*/
@JSONField(name = "payer_currency")
private String payerCurrency;
/**
* 退款金额,单位为分
*/
private Integer refund;
/**
* 退款给用户的金额,单位为分,不包含所有优惠券金额
*/
@JSONField(name = "payer_refund")
private Integer payerRefund;
public BigDecimal getPayerTotal() {
return payerTotal;
}
@@ -36,4 +52,20 @@ public class Amount extends com.egzosn.pay.wx.v3.bean.order.Amount{
public void setPayerCurrency(String payerCurrency) {
this.payerCurrency = payerCurrency;
}
public Integer getRefund() {
return refund;
}
public void setRefund(Integer refund) {
this.refund = refund;
}
public Integer getPayerRefund() {
return payerRefund;
}
public void setPayerRefund(Integer payerRefund) {
this.payerRefund = payerRefund;
}
}

View File

@@ -11,6 +11,7 @@ package com.egzosn.pay.wx.v3.bean.response.order;
public class Payer {
/**
* 用户在直连商户appid下的唯一标识。
* 使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。
*/
private String openid;

View File

@@ -1,7 +1,8 @@
package com.egzosn.pay.wx.v3.bean.response.order;
/**
* 微信侧返回交易状态
* 微信侧返回交易状态,兼容退款状态
*
* @author Egan
* <pre>
* email egan@egzosn.com
@@ -11,31 +12,28 @@ package com.egzosn.pay.wx.v3.bean.response.order;
public enum TradeState {
/**
* 支付成功
*
*
* 退款成功
*/
SUCCESS,
/**
* 转入退款
*
*
*/
REFUND,
/**
* 未支付
*
*
*/
NOTPAY,
/**
* 已关闭
*
*
* 退款关闭
*/
CLOSED,
/**
* 退款异常.
*/
ABNORMAL,
/**
* 已撤销(付款码支付)
*
*/
REVOKED,
/**
@@ -48,7 +46,6 @@ public enum TradeState {
PAYERROR,
/**
* 已接收,等待扣款
*
*/
ACCEPT,
}

View File

@@ -17,7 +17,7 @@ public final class WxConst {
/**
* 微信默认请求地址
*/
public static final String URI = "https://api.mch.weixin.qq.com/v3/";
public static final String URI = "https://api.mch.weixin.qq.com";
/**
* 证书别名
*/
@@ -31,12 +31,20 @@ public final class WxConst {
* 沙箱
*/
public static final String SANDBOXNEW = "sandboxnew/";
public static final String COMBINE = "combine_";
public static final String APPID = "appid";
public static final String COMBINE_APPID = COMBINE + APPID;
public static final String MCH_ID = "mchid";
public static final String COMBINE_MCH_ID = COMBINE + MCH_ID;
public static final String SUB_MCH_ID = "sub_mchid";
public static final String SP_MCH_ID = "sp_mchid";
public static final String OUT_TRADE_NO = "out_trade_no";
public static final String COMBINE_OUT_TRADE_NO = COMBINE + OUT_TRADE_NO;
public static final String NOTIFY_URL = "notify_url";
public static final String TIME_START = "time_start";
public static final String TIME_EXPIRE = "time_expire";
public static final String SUB_ORDERS = "sub_orders";
public static final String TOKEN_PATTERN = "mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"";