├── src ├── main │ └── java │ │ └── com │ │ └── jeequan │ │ └── jeepay │ │ ├── response │ │ ├── ChannelUserResponse.java │ │ ├── PayOrderQueryResponse.java │ │ ├── PayOrderCloseResponse.java │ │ ├── RefundOrderQueryResponse.java │ │ ├── PayOrderDivisionExecResponse.java │ │ ├── DivisionReceiverBindResponse.java │ │ ├── RefundOrderCreateResponse.java │ │ ├── PayOrderCreateResponse.java │ │ ├── TransferOrderQueryResponse.java │ │ ├── TransferOrderCreateResponse.java │ │ └── JeepayResponse.java │ │ ├── model │ │ ├── JeepayObject.java │ │ ├── PayOrderCloseResModel.java │ │ ├── ChannelUserReqModel.java │ │ ├── PayOrderDivisionExecResModel.java │ │ ├── TransferOrderQueryReqModel.java │ │ ├── PayOrderQueryReqModel.java │ │ ├── PayOrderCloseReqModel.java │ │ ├── RefundOrderQueryReqModel.java │ │ ├── PayOrderCreateResModel.java │ │ ├── RefundOrderCreateResModel.java │ │ ├── PayOrderDivisionExecReqModel.java │ │ ├── TransferOrderCreateResModel.java │ │ ├── RefundOrderCreateReqModel.java │ │ ├── DivisionReceiverBindReqModel.java │ │ ├── TransferOrderCreateReqModel.java │ │ ├── PayOrderCreateReqModel.java │ │ ├── RefundOrderQueryResModel.java │ │ ├── PayOrderQueryResModel.java │ │ ├── DivisionReceiverBindResModel.java │ │ └── TransferOrderQueryResModel.java │ │ ├── exception │ │ ├── APIException.java │ │ ├── InvalidRequestException.java │ │ ├── APIConnectionException.java │ │ └── JeepayException.java │ │ ├── ApiField.java │ │ ├── ApiListField.java │ │ ├── request │ │ ├── JeepayRequest.java │ │ ├── ChannelUserRequest.java │ │ ├── PayOrderQueryRequest.java │ │ ├── PayOrderCloseRequest.java │ │ ├── PayOrderCreateRequest.java │ │ ├── RefundOrderCreateRequest.java │ │ ├── PayOrderDivisionExecRequest.java │ │ ├── TransferOrderCreateRequest.java │ │ ├── TransferOrderQueryRequest.java │ │ ├── DivisionReceiverBindRequest.java │ │ └── RefundOrderQueryRequest.java │ │ ├── util │ │ ├── StreamUtils.java │ │ ├── StringUtils.java │ │ ├── CaseInsensitiveMap.java │ │ ├── JeepayKit.java │ │ └── JSONWriter.java │ │ ├── net │ │ ├── HttpContent.java │ │ ├── APIJeepayResponse.java │ │ ├── HttpHeaders.java │ │ ├── HttpURLConnectionClient.java │ │ ├── APIResource.java │ │ ├── HttpClient.java │ │ ├── RequestOptions.java │ │ └── APIJeepayRequest.java │ │ ├── Jeepay.java │ │ └── JeepayClient.java └── test │ └── java │ └── com │ └── jeequan │ └── jeepay │ ├── JeepayTestData.java │ ├── DivisionReceiverBindTest.java │ ├── PayOrderDivisionReceiverExecTest.java │ ├── RefundOrderTest.java │ ├── TransferOrderTest.java │ └── PayOrderTest.java ├── .gitignore ├── LICENSE ├── README.md └── pom.xml /src/main/java/com/jeequan/jeepay/response/ChannelUserResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | public class ChannelUserResponse extends JeepayResponse { 4 | 5 | private static final long serialVersionUID = -2095904267879203841L; 6 | 7 | public ChannelUserResponse() { 8 | } 9 | 10 | public Object get() { 11 | return null; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/JeepayObject.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Jeepay对象 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-08 11:00 10 | */ 11 | public class JeepayObject implements Serializable { 12 | 13 | private static final long serialVersionUID = -988525997852495276L; 14 | 15 | public JeepayObject() { 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # [ IDEA ] 26 | .idea/ 27 | *.iml 28 | 29 | # [ MAVEN ] 30 | target/ 31 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/exception/APIException.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.exception; 2 | 3 | /** 4 | * API异常 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:00 8 | */ 9 | public class APIException extends JeepayException { 10 | 11 | private static final long serialVersionUID = -2753719317464278319L; 12 | 13 | public APIException(String message, String type, String code, int statusCode, Throwable e) { 14 | super(message, statusCode, e); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/exception/InvalidRequestException.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.exception; 2 | 3 | /** 4 | * 无效请求异常 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:00 8 | */ 9 | public class InvalidRequestException extends JeepayException { 10 | 11 | private static final long serialVersionUID = 3163726141488238321L; 12 | 13 | public InvalidRequestException(String message, int statusCode, Throwable e) { 14 | super(message, statusCode, e); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/ApiField.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 数据结构属性注解 10 | * @author jmdhappy 11 | * @site https://www.jeepay.vip 12 | * @date 2021-06-08 11:00 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(value = {ElementType.FIELD}) 16 | public @interface ApiField { 17 | 18 | /** 19 | * JSON属性映射名称 20 | **/ 21 | String value() default ""; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/exception/APIConnectionException.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.exception; 2 | 3 | /** 4 | * API连接异常 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:31 8 | */ 9 | public class APIConnectionException extends JeepayException { 10 | 11 | private static final long serialVersionUID = -8764189839522042543L; 12 | 13 | public APIConnectionException(String message) { 14 | super(message); 15 | } 16 | 17 | public APIConnectionException(String message, Throwable e) { 18 | super(message, e); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/ApiListField.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 数据结构列表属性注解 10 | * @author jmdhappy 11 | * @site https://www.jeepay.vip 12 | * @date 2021-06-08 11:00 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(value = {ElementType.FIELD}) 16 | public @interface ApiListField { 17 | 18 | /** 19 | * JSON列表属性映射名称 20 | **/ 21 | String value() default ""; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/com/jeequan/jeepay/JeepayTestData.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | public class JeepayTestData { 4 | 5 | public static String getApiBase() { 6 | return "https://jeepay.natapp4.cc"; 7 | } 8 | 9 | public static String getApiKey() { 10 | return "moUifKdz95rkLUIcCoi85OWbQKRSZ8LVJBLambzTzqV4UPXquJVXneq7lYEGfC39vl0JLaXTxR54efgMoLCHc8WxE2adRBwLLO2PHaWLM72YNLCIuBCGY3ohBQIRlhab"; 11 | } 12 | 13 | public static String getMchNo() { 14 | return "M1625209046"; 15 | } 16 | 17 | public static String getAppId() { return "60deb8d6c6104c854e2346e4";} 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/PayOrderQueryResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.PayOrderQueryResModel; 4 | 5 | /** 6 | * Jeepay支付查单响应实现 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-08 11:00 10 | */ 11 | public class PayOrderQueryResponse extends JeepayResponse { 12 | 13 | private static final long serialVersionUID = 7654172640802954221L; 14 | 15 | public PayOrderQueryResModel get() { 16 | if(getData() == null) return new PayOrderQueryResModel(); 17 | return getData().toJavaObject(PayOrderQueryResModel.class); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/PayOrderCloseResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.PayOrderCloseResModel; 4 | 5 | /** 6 | * Jeepay支付 关闭订单响应实现 7 | * 8 | * @author xiaoyu 9 | * @site https://www.jeequan.com 10 | * @date 2022/1/25 9:56 11 | */ 12 | public class PayOrderCloseResponse extends JeepayResponse { 13 | 14 | private static final long serialVersionUID = 7654172640802954221L; 15 | 16 | public PayOrderCloseResModel get() { 17 | if(getData() == null) return new PayOrderCloseResModel(); 18 | return getData().toJavaObject(PayOrderCloseResModel.class); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/RefundOrderQueryResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.RefundOrderQueryResModel; 4 | 5 | /** 6 | * Jeepay退款查单响应实现 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-18 12:00 10 | */ 11 | public class RefundOrderQueryResponse extends JeepayResponse { 12 | 13 | private static final long serialVersionUID = 7654172640802954221L; 14 | 15 | public RefundOrderQueryResModel get() { 16 | if(getData() == null) return new RefundOrderQueryResModel(); 17 | return getData().toJavaObject(RefundOrderQueryResModel.class); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderCloseResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /** 4 | * 关闭订单响应结果 5 | * 6 | * @author xiaoyu 7 | * @site https://www.jeequan.com 8 | * @date 2022/1/25 9:55 9 | */ 10 | public class PayOrderCloseResModel extends JeepayObject { 11 | 12 | /** 13 | * 支付渠道错误码 14 | */ 15 | private String errCode; 16 | 17 | /** 18 | * 支付渠道错误信息 19 | */ 20 | private String errMsg; 21 | 22 | public String getErrCode() { 23 | return errCode; 24 | } 25 | 26 | public void setErrCode(String errCode) { 27 | this.errCode = errCode; 28 | } 29 | 30 | public String getErrMsg() { 31 | return errMsg; 32 | } 33 | 34 | public void setErrMsg(String errMsg) { 35 | this.errMsg = errMsg; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/PayOrderDivisionExecResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.PayOrderDivisionExecResModel; 4 | 5 | 6 | /*** 7 | * 发起分账响应实现 8 | * 9 | * @author terrfly 10 | * @site https://www.jeepay.vip 11 | * @date 2021/8/27 10:22 12 | */ 13 | public class PayOrderDivisionExecResponse extends JeepayResponse { 14 | 15 | private static final long serialVersionUID = 7419683269497002904L; 16 | 17 | public PayOrderDivisionExecResModel get() { 18 | if(getData() == null) return new PayOrderDivisionExecResModel(); 19 | return getData().toJavaObject(PayOrderDivisionExecResModel.class); 20 | } 21 | 22 | @Override 23 | public boolean isSuccess(String apiKey) { 24 | if(super.isSuccess(apiKey)) { 25 | int state = get().getState(); 26 | return state == 1; 27 | } 28 | return false; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/DivisionReceiverBindResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.DivisionReceiverBindResModel; 4 | 5 | 6 | /*** 7 | * 分账账号的绑定响应实现 8 | * 9 | * @author terrfly 10 | * @site https://www.jeepay.vip 11 | * @date 2021/8/25 10:35 12 | */ 13 | public class DivisionReceiverBindResponse extends JeepayResponse { 14 | 15 | private static final long serialVersionUID = 7419683269497002904L; 16 | 17 | public DivisionReceiverBindResModel get() { 18 | if(getData() == null) return new DivisionReceiverBindResModel(); 19 | return getData().toJavaObject(DivisionReceiverBindResModel.class); 20 | } 21 | 22 | @Override 23 | public boolean isSuccess(String apiKey) { 24 | if(super.isSuccess(apiKey)) { 25 | int state = get().getBindState(); 26 | return state == 1; 27 | } 28 | return false; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/RefundOrderCreateResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.RefundOrderCreateResModel; 4 | 5 | /** 6 | * Jeepay退款响应实现 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-18 09:00 10 | */ 11 | public class RefundOrderCreateResponse extends JeepayResponse { 12 | 13 | private static final long serialVersionUID = 7419683269497002904L; 14 | 15 | public RefundOrderCreateResModel get() { 16 | if(getData() == null) return new RefundOrderCreateResModel(); 17 | return getData().toJavaObject(RefundOrderCreateResModel.class); 18 | } 19 | 20 | @Override 21 | public boolean isSuccess(String apiKey) { 22 | if(super.isSuccess(apiKey)) { 23 | int state = get().getState(); 24 | return state == 0 || state == 1 || state == 2; 25 | } 26 | return false; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/PayOrderCreateResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.PayOrderCreateResModel; 4 | 5 | /** 6 | * Jeepay支付下单响应实现 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-08 11:00 10 | */ 11 | public class PayOrderCreateResponse extends JeepayResponse { 12 | 13 | private static final long serialVersionUID = 7419683269497002904L; 14 | 15 | public PayOrderCreateResModel get() { 16 | if(getData() == null) return new PayOrderCreateResModel(); 17 | return getData().toJavaObject(PayOrderCreateResModel.class); 18 | } 19 | 20 | @Override 21 | public boolean isSuccess(String apiKey) { 22 | if(super.isSuccess(apiKey)) { 23 | int orderState = get().getOrderState(); 24 | return orderState == 0 || orderState == 1 || orderState == 2; 25 | } 26 | return false; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/TransferOrderQueryResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.TransferOrderQueryResModel; 4 | 5 | /*** 6 | * Jeepay转账查单响应实现 7 | * 8 | * @author terrfly 9 | * @site https://www.jeepay.vip 10 | * @date 2021/8/16 16:25 11 | */ 12 | public class TransferOrderQueryResponse extends JeepayResponse { 13 | 14 | private static final long serialVersionUID = 7419683269497002904L; 15 | 16 | public TransferOrderQueryResModel get() { 17 | if(getData() == null) { 18 | return new TransferOrderQueryResModel(); 19 | } 20 | return getData().toJavaObject(TransferOrderQueryResModel.class); 21 | } 22 | 23 | @Override 24 | public boolean isSuccess(String apiKey) { 25 | if(super.isSuccess(apiKey)) { 26 | int state = get().getState(); 27 | return state == 0 || state == 1 || state == 2; 28 | } 29 | return false; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/TransferOrderCreateResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.jeequan.jeepay.model.TransferOrderCreateResModel; 4 | 5 | /*** 6 | * Jeepay转账响应实现 7 | * 8 | * @author terrfly 9 | * @site https://www.jeepay.vip 10 | * @date 2021/8/13 16:25 11 | */ 12 | public class TransferOrderCreateResponse extends JeepayResponse { 13 | 14 | private static final long serialVersionUID = 7419683269497002904L; 15 | 16 | public TransferOrderCreateResModel get() { 17 | if(getData() == null) { 18 | return new TransferOrderCreateResModel(); 19 | } 20 | return getData().toJavaObject(TransferOrderCreateResModel.class); 21 | } 22 | 23 | @Override 24 | public boolean isSuccess(String apiKey) { 25 | if(super.isSuccess(apiKey)) { 26 | int state = get().getState(); 27 | return state == 0 || state == 1 || state == 2; 28 | } 29 | return false; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/JeepayRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.model.JeepayObject; 4 | import com.jeequan.jeepay.net.RequestOptions; 5 | import com.jeequan.jeepay.response.JeepayResponse; 6 | 7 | /** 8 | * Jeepay请求接口 9 | * @author jmdhappy 10 | * @site https://www.jeepay.vip 11 | * @date 2021-06-08 11:00 12 | */ 13 | public interface JeepayRequest { 14 | 15 | /** 16 | * 获取当前接口的路径 17 | * @return 18 | */ 19 | String getApiUri(); 20 | 21 | /** 22 | * 获取当前接口的版本 23 | * @return 24 | */ 25 | String getApiVersion(); 26 | 27 | /** 28 | * 设置当前接口的版本 29 | * @return 30 | */ 31 | void setApiVersion(String apiVersion); 32 | 33 | RequestOptions getRequestOptions(); 34 | 35 | void setRequestOptions(RequestOptions options); 36 | 37 | JeepayObject getBizModel(); 38 | 39 | void setBizModel(JeepayObject bizModel); 40 | 41 | Class getResponseClass(); 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/exception/JeepayException.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.exception; 2 | 3 | /** 4 | * Jeepay异常抽象类 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:00 8 | */ 9 | public abstract class JeepayException extends Exception { 10 | 11 | private static final long serialVersionUID = 2566087783987900120L; 12 | 13 | private int statusCode; 14 | 15 | public JeepayException(String message) { 16 | super(message, null); 17 | } 18 | 19 | public JeepayException(String message, Throwable e) { 20 | super(message, e); 21 | } 22 | 23 | public JeepayException(String message, int statusCode, Throwable e) { 24 | super(message, e); 25 | this.statusCode = statusCode; 26 | } 27 | 28 | public int getStatusCode() { 29 | return statusCode; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | StringBuffer sb = new StringBuffer(); 35 | sb.append(super.toString()); 36 | return sb.toString(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/util/StreamUtils.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.util; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.InputStreamReader; 6 | import java.io.Reader; 7 | import java.nio.charset.Charset; 8 | 9 | import static java.util.Objects.requireNonNull; 10 | 11 | /** 12 | * 流工具类 13 | * @author jmdhappy 14 | * @site https://www.jeepay.vip 15 | * @date 2021-06-08 11:00 16 | */ 17 | public class StreamUtils { 18 | private static final int DEFAULT_BUF_SIZE = 1024; 19 | 20 | public static String readToEnd(InputStream stream, Charset charset) throws IOException { 21 | requireNonNull(stream); 22 | requireNonNull(charset); 23 | 24 | final StringBuilder sb = new StringBuilder(); 25 | final char[] buffer = new char[DEFAULT_BUF_SIZE]; 26 | 27 | try (Reader in = new InputStreamReader(stream, charset)) { 28 | int charsRead = 0; 29 | while ((charsRead = in.read(buffer, 0, buffer.length)) > 0) { 30 | sb.append(buffer, 0, charsRead); 31 | } 32 | } 33 | 34 | return sb.toString(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-2031, 河北计全科技有限公司 (https://www.jeequan.com & jeequan@126.com). 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/HttpContent.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | 5 | import java.util.Map; 6 | 7 | import static java.util.Objects.requireNonNull; 8 | 9 | /** 10 | * Http请求内容 11 | * @author jmdhappy 12 | * @site https://www.jeepay.vip 13 | * @date 2021-06-08 11:00 14 | */ 15 | public class HttpContent { 16 | 17 | byte[] byteArrayContent; 18 | 19 | String contentType; 20 | 21 | private HttpContent(byte[] byteArrayContent, String contentType) { 22 | this.byteArrayContent = byteArrayContent; 23 | this.contentType = contentType; 24 | } 25 | 26 | public String stringContent() { 27 | return new String(this.byteArrayContent, APIResource.CHARSET); 28 | } 29 | 30 | public static HttpContent buildJSONContent(Map params) { 31 | requireNonNull(params); 32 | 33 | return new HttpContent( 34 | createJSONString(params).getBytes(APIResource.CHARSET), 35 | String.format("application/json; charset=%s", APIResource.CHARSET)); 36 | } 37 | 38 | private static String createJSONString(Map params) { 39 | return JSON.toJSONString(params); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/ChannelUserReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | public class ChannelUserReqModel extends JeepayObject { 6 | 7 | private static final long serialVersionUID = -5120697539129675241L; 8 | @ApiField("ifCode") 9 | String ifCode; 10 | @ApiField("redirectUrl") 11 | String redirectUrl; 12 | @ApiField("mchNo") 13 | private String mchNo; 14 | @ApiField("appId") 15 | private String appId; 16 | 17 | public ChannelUserReqModel() { 18 | } 19 | 20 | public String getIfCode() { 21 | return ifCode; 22 | } 23 | 24 | public void setIfCode(String ifCode) { 25 | this.ifCode = ifCode; 26 | } 27 | 28 | public String getRedirectUrl() { 29 | return redirectUrl; 30 | } 31 | 32 | public void setRedirectUrl(String redirectUrl) { 33 | this.redirectUrl = redirectUrl; 34 | } 35 | 36 | public String getMchNo() { 37 | return mchNo; 38 | } 39 | 40 | public void setMchNo(String mchNo) { 41 | this.mchNo = mchNo; 42 | } 43 | 44 | public String getAppId() { 45 | return appId; 46 | } 47 | 48 | public void setAppId(String appId) { 49 | this.appId = appId; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderDivisionExecResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /*** 4 | * 分账响应结果 5 | * 6 | * @author terrfly 7 | * @site https://www.jeepay.vip 8 | * @date 2021/8/27 10:19 9 | */ 10 | public class PayOrderDivisionExecResModel extends JeepayObject { 11 | 12 | /** 13 | * 分账状态 1-分账成功, 2-分账失败 14 | */ 15 | private Byte state; 16 | 17 | /** 18 | * 上游分账批次号 19 | */ 20 | private String channelBatchOrderId; 21 | 22 | /** 23 | * 支付渠道错误码 24 | */ 25 | private String errCode; 26 | 27 | /** 28 | * 支付渠道错误信息 29 | */ 30 | private String errMsg; 31 | 32 | 33 | public Byte getState() { 34 | return state; 35 | } 36 | 37 | public void setState(Byte state) { 38 | this.state = state; 39 | } 40 | 41 | public String getChannelBatchOrderId() { 42 | return channelBatchOrderId; 43 | } 44 | 45 | public void setChannelBatchOrderId(String channelBatchOrderId) { 46 | this.channelBatchOrderId = channelBatchOrderId; 47 | } 48 | 49 | public String getErrCode() { 50 | return errCode; 51 | } 52 | 53 | public void setErrCode(String errCode) { 54 | this.errCode = errCode; 55 | } 56 | 57 | public String getErrMsg() { 58 | return errMsg; 59 | } 60 | 61 | public void setErrMsg(String errMsg) { 62 | this.errMsg = errMsg; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.util; 2 | 3 | import java.util.List; 4 | import java.util.regex.Pattern; 5 | 6 | import static java.util.Objects.requireNonNull; 7 | 8 | /** 9 | * 字符串工具类 10 | * @author jmdhappy 11 | * @site https://www.jeepay.vip 12 | * @date 2021-06-08 11:00 13 | */ 14 | public class StringUtils { 15 | 16 | private static Pattern whitespacePattern = Pattern.compile("\\s"); 17 | 18 | public static boolean containsWhitespace(String str) { 19 | requireNonNull(str); 20 | return whitespacePattern.matcher(str).find(); 21 | } 22 | 23 | public static String join(String separator, List input) { 24 | 25 | if (input == null || input.size() <= 0) return ""; 26 | 27 | StringBuilder sb = new StringBuilder(); 28 | for (int i = 0; i < input.size(); i++) { 29 | sb.append(input.get(i)); 30 | 31 | // if not the last item 32 | if (i != input.size() - 1) { 33 | sb.append(separator); 34 | } 35 | } 36 | 37 | return sb.toString(); 38 | } 39 | 40 | public static String genUrl(String url, String uri) { 41 | if(!url.endsWith("/")) url += "/"; 42 | return url += uri; 43 | } 44 | 45 | public static Boolean isEmpty(String str) { 46 | if(str == null) return true; 47 | return "".equals(str.trim()); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/TransferOrderQueryReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /*** 6 | * 转账查单请求实体类 7 | * 8 | * @author terrfly 9 | * @site https://www.jeepay.vip 10 | * @date 2021/8/16 16:08 11 | */ 12 | public class TransferOrderQueryReqModel extends JeepayObject { 13 | 14 | private static final long serialVersionUID = -3998573128290306948L; 15 | 16 | @ApiField("mchNo") 17 | private String mchNo; // 商户号 18 | @ApiField("appId") 19 | private String appId; // 应用ID 20 | @ApiField("mchOrderNo") 21 | String mchOrderNo; // 商户订单号 22 | @ApiField("transferId") 23 | String transferId; // 支付平台订单号 24 | 25 | public TransferOrderQueryReqModel() { 26 | } 27 | 28 | public String getMchNo() { 29 | return mchNo; 30 | } 31 | 32 | public void setMchNo(String mchNo) { 33 | this.mchNo = mchNo; 34 | } 35 | 36 | public String getAppId() { 37 | return appId; 38 | } 39 | 40 | public void setAppId(String appId) { 41 | this.appId = appId; 42 | } 43 | 44 | public String getMchOrderNo() { 45 | return mchOrderNo; 46 | } 47 | 48 | public void setMchOrderNo(String mchOrderNo) { 49 | this.mchOrderNo = mchOrderNo; 50 | } 51 | 52 | public String getTransferId() { 53 | return transferId; 54 | } 55 | 56 | public void setTransferId(String transferId) { 57 | this.transferId = transferId; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/ChannelUserRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.model.JeepayObject; 4 | import com.jeequan.jeepay.net.RequestOptions; 5 | import com.jeequan.jeepay.response.ChannelUserResponse; 6 | 7 | /** 8 | * 仅仅用于查询渠道用户信息,编译出具体 URL 并不会产生实际请求 9 | */ 10 | public class ChannelUserRequest implements JeepayRequest { 11 | 12 | private final String apiUri = "api/channelUserId/jump"; 13 | private String apiVersion = "1.0"; 14 | private RequestOptions options; 15 | private JeepayObject bizModel = null; 16 | 17 | public ChannelUserRequest() { 18 | } 19 | 20 | public String getApiUri() { 21 | return this.apiUri; 22 | } 23 | 24 | public String getApiVersion() { 25 | return this.apiVersion; 26 | } 27 | 28 | public void setApiVersion(String apiVersion) { 29 | this.apiVersion = apiVersion; 30 | } 31 | 32 | public RequestOptions getRequestOptions() { 33 | return this.options; 34 | } 35 | 36 | public void setRequestOptions(RequestOptions options) { 37 | this.options = options; 38 | } 39 | 40 | public JeepayObject getBizModel() { 41 | return this.bizModel; 42 | } 43 | 44 | public void setBizModel(JeepayObject bizModel) { 45 | this.bizModel = bizModel; 46 | } 47 | 48 | public Class getResponseClass() { 49 | return ChannelUserResponse.class; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderQueryReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /** 6 | * 支付查单请求实体类 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-08 11:00 10 | */ 11 | public class PayOrderQueryReqModel extends JeepayObject{ 12 | 13 | private static final long serialVersionUID = -5184554341263929245L; 14 | 15 | /** 16 | * 商户号 17 | */ 18 | @ApiField("mchNo") 19 | private String mchNo; 20 | /** 21 | * 应用ID 22 | */ 23 | @ApiField("appId") 24 | private String appId; 25 | /** 26 | * 商户订单号 27 | */ 28 | @ApiField("mchOrderNo") 29 | String mchOrderNo; 30 | /** 31 | * 支付订单号 32 | */ 33 | @ApiField("payOrderId") 34 | String payOrderId; 35 | 36 | public String getMchNo() { 37 | return mchNo; 38 | } 39 | 40 | public void setMchNo(String mchNo) { 41 | this.mchNo = mchNo; 42 | } 43 | 44 | public String getAppId() { 45 | return appId; 46 | } 47 | 48 | public void setAppId(String appId) { 49 | this.appId = appId; 50 | } 51 | 52 | public String getMchOrderNo() { 53 | return mchOrderNo; 54 | } 55 | 56 | public void setMchOrderNo(String mchOrderNo) { 57 | this.mchOrderNo = mchOrderNo; 58 | } 59 | 60 | public String getPayOrderId() { 61 | return payOrderId; 62 | } 63 | 64 | public void setPayOrderId(String payOrderId) { 65 | this.payOrderId = payOrderId; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderCloseReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /** 6 | * 支付关闭请求实体类 7 | * 8 | * @author xiaoyu 9 | * @site https://www.jeequan.com 10 | * @date 2022/1/25 9:53 11 | */ 12 | public class PayOrderCloseReqModel extends JeepayObject{ 13 | 14 | private static final long serialVersionUID = -5184554341263929245L; 15 | 16 | /** 17 | * 商户号 18 | */ 19 | @ApiField("mchNo") 20 | private String mchNo; 21 | /** 22 | * 应用ID 23 | */ 24 | @ApiField("appId") 25 | private String appId; 26 | /** 27 | * 商户订单号 28 | */ 29 | @ApiField("mchOrderNo") 30 | String mchOrderNo; 31 | /** 32 | * 支付订单号 33 | */ 34 | @ApiField("payOrderId") 35 | String payOrderId; 36 | 37 | public String getMchNo() { 38 | return mchNo; 39 | } 40 | 41 | public void setMchNo(String mchNo) { 42 | this.mchNo = mchNo; 43 | } 44 | 45 | public String getAppId() { 46 | return appId; 47 | } 48 | 49 | public void setAppId(String appId) { 50 | this.appId = appId; 51 | } 52 | 53 | public String getMchOrderNo() { 54 | return mchOrderNo; 55 | } 56 | 57 | public void setMchOrderNo(String mchOrderNo) { 58 | this.mchOrderNo = mchOrderNo; 59 | } 60 | 61 | public String getPayOrderId() { 62 | return payOrderId; 63 | } 64 | 65 | public void setPayOrderId(String payOrderId) { 66 | this.payOrderId = payOrderId; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/RefundOrderQueryReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /** 6 | * 退款查单请求实体类 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-18 10:00 10 | */ 11 | public class RefundOrderQueryReqModel extends JeepayObject{ 12 | 13 | private static final long serialVersionUID = -5184554341263929245L; 14 | 15 | /** 16 | * 商户号 17 | */ 18 | @ApiField("mchNo") 19 | private String mchNo; 20 | /** 21 | * 应用ID 22 | */ 23 | @ApiField("appId") 24 | private String appId; 25 | /** 26 | * 商户退款单号 27 | */ 28 | @ApiField("mchRefundNo") 29 | String mchRefundNo; 30 | /** 31 | * 支付系统退款订单号 32 | */ 33 | @ApiField("refundOrderId") 34 | String refundOrderId; 35 | 36 | public String getMchNo() { 37 | return mchNo; 38 | } 39 | 40 | public void setMchNo(String mchNo) { 41 | this.mchNo = mchNo; 42 | } 43 | 44 | public String getAppId() { 45 | return appId; 46 | } 47 | 48 | public void setAppId(String appId) { 49 | this.appId = appId; 50 | } 51 | 52 | public String getMchRefundNo() { 53 | return mchRefundNo; 54 | } 55 | 56 | public void setMchRefundNo(String mchRefundNo) { 57 | this.mchRefundNo = mchRefundNo; 58 | } 59 | 60 | public String getRefundOrderId() { 61 | return refundOrderId; 62 | } 63 | 64 | public void setRefundOrderId(String refundOrderId) { 65 | this.refundOrderId = refundOrderId; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/APIJeepayResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | /** 4 | * API响应 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:00 8 | */ 9 | public class APIJeepayResponse { 10 | 11 | private int responseCode; 12 | private String responseBody; 13 | private HttpHeaders responseHeaders; 14 | 15 | private int numRetries; 16 | 17 | public APIJeepayResponse(int responseCode, String responseBody) { 18 | this.responseCode = responseCode; 19 | this.responseBody = responseBody; 20 | this.responseHeaders = null; 21 | } 22 | 23 | public APIJeepayResponse(int responseCode, String responseBody, HttpHeaders responseHeaders) { 24 | this.responseCode = responseCode; 25 | this.responseBody = responseBody; 26 | this.responseHeaders = responseHeaders; 27 | } 28 | 29 | public int getResponseCode() { 30 | return responseCode; 31 | } 32 | 33 | public void setResponseCode(int responseCode) { 34 | this.responseCode = responseCode; 35 | } 36 | 37 | public String getResponseBody() { 38 | return responseBody; 39 | } 40 | 41 | public void setResponseBody(String responseBody) { 42 | this.responseBody = responseBody; 43 | } 44 | 45 | public HttpHeaders getResponseHeaders() { 46 | return responseHeaders; 47 | } 48 | 49 | public int getNumRetries() { 50 | return numRetries; 51 | } 52 | 53 | public void setNumRetries(int numRetries) { 54 | this.numRetries = numRetries; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/PayOrderQueryRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.PayOrderQueryResponse; 7 | 8 | /** 9 | * Jeepay支付查单请求实现 10 | * @author jmdhappy 11 | * @site https://www.jeepay.vip 12 | * @date 2021-06-08 11:00 13 | */ 14 | public class PayOrderQueryRequest implements JeepayRequest { 15 | 16 | private String apiVersion = Jeepay.VERSION; 17 | private String apiUri = "api/pay/query"; 18 | private RequestOptions options; 19 | private JeepayObject bizModel = null; 20 | 21 | @Override 22 | public String getApiUri() { 23 | return this.apiUri; 24 | } 25 | 26 | @Override 27 | public String getApiVersion() { 28 | return this.apiVersion; 29 | } 30 | 31 | @Override 32 | public void setApiVersion(String apiVersion) { 33 | this.apiVersion = apiVersion; 34 | } 35 | 36 | @Override 37 | public RequestOptions getRequestOptions() { 38 | return this.options; 39 | } 40 | 41 | @Override 42 | public void setRequestOptions(RequestOptions options) { 43 | this.options = options; 44 | } 45 | 46 | @Override 47 | public JeepayObject getBizModel() { 48 | return this.bizModel; 49 | } 50 | 51 | @Override 52 | public void setBizModel(JeepayObject bizModel) { 53 | this.bizModel = bizModel; 54 | } 55 | 56 | @Override 57 | public Class getResponseClass() { 58 | return PayOrderQueryResponse.class; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/PayOrderCloseRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.PayOrderCloseResponse; 7 | 8 | /** 9 | * Jeepay支付 订单关闭请求实现 10 | * 11 | * @author xiaoyu 12 | * @site https://www.jeequan.com 13 | * @date 2022/1/25 9:56 14 | */ 15 | public class PayOrderCloseRequest implements JeepayRequest { 16 | 17 | private String apiVersion = Jeepay.VERSION; 18 | private String apiUri = "api/pay/close"; 19 | private RequestOptions options; 20 | private JeepayObject bizModel = null; 21 | 22 | @Override 23 | public String getApiUri() { 24 | return this.apiUri; 25 | } 26 | 27 | @Override 28 | public String getApiVersion() { 29 | return this.apiVersion; 30 | } 31 | 32 | @Override 33 | public void setApiVersion(String apiVersion) { 34 | this.apiVersion = apiVersion; 35 | } 36 | 37 | @Override 38 | public RequestOptions getRequestOptions() { 39 | return this.options; 40 | } 41 | 42 | @Override 43 | public void setRequestOptions(RequestOptions options) { 44 | this.options = options; 45 | } 46 | 47 | @Override 48 | public JeepayObject getBizModel() { 49 | return this.bizModel; 50 | } 51 | 52 | @Override 53 | public void setBizModel(JeepayObject bizModel) { 54 | this.bizModel = bizModel; 55 | } 56 | 57 | @Override 58 | public Class getResponseClass() { 59 | return PayOrderCloseResponse.class; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/PayOrderCreateRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.PayOrderCreateResponse; 7 | 8 | /** 9 | * Jeepay支付下单请求实现 10 | * @author jmdhappy 11 | * @site https://www.jeepay.vip 12 | * @date 2021-06-08 11:00 13 | */ 14 | public class PayOrderCreateRequest implements JeepayRequest { 15 | 16 | private String apiVersion = Jeepay.VERSION; 17 | private String apiUri = "api/pay/unifiedOrder"; 18 | private RequestOptions options; 19 | private JeepayObject bizModel = null; 20 | 21 | @Override 22 | public String getApiUri() { 23 | return this.apiUri; 24 | } 25 | 26 | @Override 27 | public String getApiVersion() { 28 | return this.apiVersion; 29 | } 30 | 31 | @Override 32 | public void setApiVersion(String apiVersion) { 33 | this.apiVersion = apiVersion; 34 | } 35 | 36 | @Override 37 | public RequestOptions getRequestOptions() { 38 | return this.options; 39 | } 40 | 41 | @Override 42 | public void setRequestOptions(RequestOptions options) { 43 | this.options = options; 44 | } 45 | 46 | @Override 47 | public JeepayObject getBizModel() { 48 | return this.bizModel; 49 | } 50 | 51 | @Override 52 | public void setBizModel(JeepayObject bizModel) { 53 | this.bizModel = bizModel; 54 | } 55 | 56 | @Override 57 | public Class getResponseClass() { 58 | return PayOrderCreateResponse.class; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/RefundOrderCreateRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.RefundOrderCreateResponse; 7 | 8 | /** 9 | * Jeepay退款请求实现 10 | * @author jmdhappy 11 | * @site https://www.jeepay.vip 12 | * @date 2021-06-18 09:00 13 | */ 14 | public class RefundOrderCreateRequest implements JeepayRequest { 15 | 16 | private String apiVersion = Jeepay.VERSION; 17 | private String apiUri = "api/refund/refundOrder"; 18 | private RequestOptions options; 19 | private JeepayObject bizModel = null; 20 | 21 | @Override 22 | public String getApiUri() { 23 | return this.apiUri; 24 | } 25 | 26 | @Override 27 | public String getApiVersion() { 28 | return this.apiVersion; 29 | } 30 | 31 | @Override 32 | public void setApiVersion(String apiVersion) { 33 | this.apiVersion = apiVersion; 34 | } 35 | 36 | @Override 37 | public RequestOptions getRequestOptions() { 38 | return this.options; 39 | } 40 | 41 | @Override 42 | public void setRequestOptions(RequestOptions options) { 43 | this.options = options; 44 | } 45 | 46 | @Override 47 | public JeepayObject getBizModel() { 48 | return this.bizModel; 49 | } 50 | 51 | @Override 52 | public void setBizModel(JeepayObject bizModel) { 53 | this.bizModel = bizModel; 54 | } 55 | 56 | @Override 57 | public Class getResponseClass() { 58 | return RefundOrderCreateResponse.class; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/PayOrderDivisionExecRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.PayOrderDivisionExecResponse; 7 | 8 | /*** 9 | * 分账发起 10 | * 11 | * @author terrfly 12 | * @site https://www.jeepay.vip 13 | * @date 2021/8/27 10:19 14 | */ 15 | public class PayOrderDivisionExecRequest implements JeepayRequest { 16 | 17 | private String apiVersion = Jeepay.VERSION; 18 | private String apiUri = "api/division/exec"; 19 | private RequestOptions options; 20 | private JeepayObject bizModel = null; 21 | 22 | @Override 23 | public String getApiUri() { 24 | return this.apiUri; 25 | } 26 | 27 | @Override 28 | public String getApiVersion() { 29 | return this.apiVersion; 30 | } 31 | 32 | @Override 33 | public void setApiVersion(String apiVersion) { 34 | this.apiVersion = apiVersion; 35 | } 36 | 37 | @Override 38 | public RequestOptions getRequestOptions() { 39 | return this.options; 40 | } 41 | 42 | @Override 43 | public void setRequestOptions(RequestOptions options) { 44 | this.options = options; 45 | } 46 | 47 | @Override 48 | public JeepayObject getBizModel() { 49 | return this.bizModel; 50 | } 51 | 52 | @Override 53 | public void setBizModel(JeepayObject bizModel) { 54 | this.bizModel = bizModel; 55 | } 56 | 57 | @Override 58 | public Class getResponseClass() { 59 | return PayOrderDivisionExecResponse.class; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/TransferOrderCreateRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.TransferOrderCreateResponse; 7 | 8 | /*** 9 | * Jeepay转账请求实现 10 | * 11 | * @author terrfly 12 | * @site https://www.jeepay.vip 13 | * @date 2021/8/13 16:26 14 | */ 15 | public class TransferOrderCreateRequest implements JeepayRequest { 16 | 17 | private String apiVersion = Jeepay.VERSION; 18 | private String apiUri = "api/transferOrder"; 19 | private RequestOptions options; 20 | private JeepayObject bizModel = null; 21 | 22 | @Override 23 | public String getApiUri() { 24 | return this.apiUri; 25 | } 26 | 27 | @Override 28 | public String getApiVersion() { 29 | return this.apiVersion; 30 | } 31 | 32 | @Override 33 | public void setApiVersion(String apiVersion) { 34 | this.apiVersion = apiVersion; 35 | } 36 | 37 | @Override 38 | public RequestOptions getRequestOptions() { 39 | return this.options; 40 | } 41 | 42 | @Override 43 | public void setRequestOptions(RequestOptions options) { 44 | this.options = options; 45 | } 46 | 47 | @Override 48 | public JeepayObject getBizModel() { 49 | return this.bizModel; 50 | } 51 | 52 | @Override 53 | public void setBizModel(JeepayObject bizModel) { 54 | this.bizModel = bizModel; 55 | } 56 | 57 | @Override 58 | public Class getResponseClass() { 59 | return TransferOrderCreateResponse.class; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/TransferOrderQueryRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.TransferOrderQueryResponse; 7 | 8 | /*** 9 | * Jeepay转账查询请求实现 10 | * 11 | * @author terrfly 12 | * @site https://www.jeepay.vip 13 | * @date 2021/8/16 16:26 14 | */ 15 | public class TransferOrderQueryRequest implements JeepayRequest { 16 | 17 | private String apiVersion = Jeepay.VERSION; 18 | private String apiUri = "api/transfer/query"; 19 | private RequestOptions options; 20 | private JeepayObject bizModel = null; 21 | 22 | @Override 23 | public String getApiUri() { 24 | return this.apiUri; 25 | } 26 | 27 | @Override 28 | public String getApiVersion() { 29 | return this.apiVersion; 30 | } 31 | 32 | @Override 33 | public void setApiVersion(String apiVersion) { 34 | this.apiVersion = apiVersion; 35 | } 36 | 37 | @Override 38 | public RequestOptions getRequestOptions() { 39 | return this.options; 40 | } 41 | 42 | @Override 43 | public void setRequestOptions(RequestOptions options) { 44 | this.options = options; 45 | } 46 | 47 | @Override 48 | public JeepayObject getBizModel() { 49 | return this.bizModel; 50 | } 51 | 52 | @Override 53 | public void setBizModel(JeepayObject bizModel) { 54 | this.bizModel = bizModel; 55 | } 56 | 57 | @Override 58 | public Class getResponseClass() { 59 | return TransferOrderQueryResponse.class; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/DivisionReceiverBindRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.DivisionReceiverBindResponse; 7 | 8 | /*** 9 | * 分账绑定接口 10 | * 11 | * @author terrfly 12 | * @site https://www.jeepay.vip 13 | * @date 2021/8/25 10:34 14 | */ 15 | public class DivisionReceiverBindRequest implements JeepayRequest { 16 | 17 | private String apiVersion = Jeepay.VERSION; 18 | private String apiUri = "api/division/receiver/bind"; 19 | private RequestOptions options; 20 | private JeepayObject bizModel = null; 21 | 22 | @Override 23 | public String getApiUri() { 24 | return this.apiUri; 25 | } 26 | 27 | @Override 28 | public String getApiVersion() { 29 | return this.apiVersion; 30 | } 31 | 32 | @Override 33 | public void setApiVersion(String apiVersion) { 34 | this.apiVersion = apiVersion; 35 | } 36 | 37 | @Override 38 | public RequestOptions getRequestOptions() { 39 | return this.options; 40 | } 41 | 42 | @Override 43 | public void setRequestOptions(RequestOptions options) { 44 | this.options = options; 45 | } 46 | 47 | @Override 48 | public JeepayObject getBizModel() { 49 | return this.bizModel; 50 | } 51 | 52 | @Override 53 | public void setBizModel(JeepayObject bizModel) { 54 | this.bizModel = bizModel; 55 | } 56 | 57 | @Override 58 | public Class getResponseClass() { 59 | return DivisionReceiverBindResponse.class; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/request/RefundOrderQueryRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.request; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.model.JeepayObject; 5 | import com.jeequan.jeepay.net.RequestOptions; 6 | import com.jeequan.jeepay.response.PayOrderQueryResponse; 7 | import com.jeequan.jeepay.response.RefundOrderQueryResponse; 8 | 9 | /** 10 | * Jeepay退款查单请求实现 11 | * @author jmdhappy 12 | * @site https://www.jeepay.vip 13 | * @date 2021-06-18 12:00 14 | */ 15 | public class RefundOrderQueryRequest implements JeepayRequest { 16 | 17 | private String apiVersion = Jeepay.VERSION; 18 | private String apiUri = "api/refund/query"; 19 | private RequestOptions options; 20 | private JeepayObject bizModel = null; 21 | 22 | @Override 23 | public String getApiUri() { 24 | return this.apiUri; 25 | } 26 | 27 | @Override 28 | public String getApiVersion() { 29 | return this.apiVersion; 30 | } 31 | 32 | @Override 33 | public void setApiVersion(String apiVersion) { 34 | this.apiVersion = apiVersion; 35 | } 36 | 37 | @Override 38 | public RequestOptions getRequestOptions() { 39 | return this.options; 40 | } 41 | 42 | @Override 43 | public void setRequestOptions(RequestOptions options) { 44 | this.options = options; 45 | } 46 | 47 | @Override 48 | public JeepayObject getBizModel() { 49 | return this.bizModel; 50 | } 51 | 52 | @Override 53 | public void setBizModel(JeepayObject bizModel) { 54 | this.bizModel = bizModel; 55 | } 56 | 57 | @Override 58 | public Class getResponseClass() { 59 | return RefundOrderQueryResponse.class; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/response/JeepayResponse.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.response; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.jeequan.jeepay.util.JeepayKit; 5 | import com.jeequan.jeepay.util.StringUtils; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Jeepay响应抽象类 11 | * @author jmdhappy 12 | * @site https://www.jeepay.vip 13 | * @date 2021-06-08 11:00 14 | */ 15 | public abstract class JeepayResponse implements Serializable { 16 | 17 | private static final long serialVersionUID = -2637191198247207952L; 18 | 19 | private Integer code; 20 | private String msg; 21 | private String sign; 22 | private JSONObject data; 23 | 24 | /** 25 | * 校验响应数据签名是否正确 26 | * @param apiKey 27 | * @return 28 | */ 29 | public boolean checkSign(String apiKey) { 30 | if(data == null && StringUtils.isEmpty(getSign())) return true; 31 | return sign.equals(JeepayKit.getSign(getData(), apiKey)); 32 | } 33 | 34 | /** 35 | * 校验是否成功(只判断code为0,具体业务要看实际情况) 36 | * @param apiKey 37 | * @return 38 | */ 39 | public boolean isSuccess(String apiKey) { 40 | if(StringUtils.isEmpty(apiKey)) return code == 0; 41 | return code == 0 && checkSign(apiKey); 42 | } 43 | 44 | public static long getSerialVersionUID() { 45 | return serialVersionUID; 46 | } 47 | 48 | public Integer getCode() { 49 | return code; 50 | } 51 | 52 | public void setCode(Integer code) { 53 | this.code = code; 54 | } 55 | 56 | public String getMsg() { 57 | return msg; 58 | } 59 | 60 | public void setMsg(String msg) { 61 | this.msg = msg; 62 | } 63 | 64 | public String getSign() { 65 | return sign; 66 | } 67 | 68 | public void setSign(String sign) { 69 | this.sign = sign; 70 | } 71 | 72 | public JSONObject getData() { 73 | return data; 74 | } 75 | 76 | public void setData(JSONObject data) { 77 | this.data = data; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return JSONObject.toJSONString(this); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderCreateResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /** 4 | * 支付下单响应实体类 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:00 8 | */ 9 | public class PayOrderCreateResModel extends JeepayObject { 10 | 11 | /** 12 | * 支付单号(网关生成) 13 | */ 14 | private String payOrderId; 15 | 16 | /** 17 | * 商户单号(商户系统生成) 18 | */ 19 | private String mchOrderNo; 20 | 21 | /** 22 | * 订单状态 23 | * 0-订单生成 24 | * 1-支付中 25 | * 2-支付成功 26 | * 3-支付失败 27 | * 4-已撤销 28 | * 5-已退款 29 | * 6-订单关闭 30 | */ 31 | private Integer orderState; 32 | 33 | /** 34 | * 支付参数类型 35 | */ 36 | private String payDataType; 37 | 38 | /** 39 | * 支付参数 40 | */ 41 | private String payData; 42 | 43 | /** 44 | * 支付渠道错误码 45 | */ 46 | private String errCode; 47 | 48 | /** 49 | * 支付渠道错误信息 50 | */ 51 | private String errMsg; 52 | 53 | public String getPayOrderId() { 54 | return payOrderId; 55 | } 56 | 57 | public void setPayOrderId(String payOrderId) { 58 | this.payOrderId = payOrderId; 59 | } 60 | 61 | public String getMchOrderNo() { 62 | return mchOrderNo; 63 | } 64 | 65 | public void setMchOrderNo(String mchOrderNo) { 66 | this.mchOrderNo = mchOrderNo; 67 | } 68 | 69 | public Integer getOrderState() { 70 | return orderState; 71 | } 72 | 73 | public void setOrderState(Integer orderState) { 74 | this.orderState = orderState; 75 | } 76 | 77 | public String getPayDataType() { 78 | return payDataType; 79 | } 80 | 81 | public void setPayDataType(String payDataType) { 82 | this.payDataType = payDataType; 83 | } 84 | 85 | public String getPayData() { 86 | return payData; 87 | } 88 | 89 | public void setPayData(String payData) { 90 | this.payData = payData; 91 | } 92 | 93 | public String getErrCode() { 94 | return errCode; 95 | } 96 | 97 | public void setErrCode(String errCode) { 98 | this.errCode = errCode; 99 | } 100 | 101 | public String getErrMsg() { 102 | return errMsg; 103 | } 104 | 105 | public void setErrMsg(String errMsg) { 106 | this.errMsg = errMsg; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/jeequan/jeepay/DivisionReceiverBindTest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import com.jeequan.jeepay.exception.JeepayException; 4 | import com.jeequan.jeepay.model.DivisionReceiverBindReqModel; 5 | import com.jeequan.jeepay.request.DivisionReceiverBindRequest; 6 | import com.jeequan.jeepay.response.DivisionReceiverBindResponse; 7 | import org.junit.jupiter.api.BeforeAll; 8 | import org.junit.jupiter.api.Test; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | class DivisionReceiverBindTest { 13 | 14 | final static Logger _log = LoggerFactory.getLogger(DivisionReceiverBindTest.class); 15 | 16 | @BeforeAll 17 | public static void initApiKey() { 18 | Jeepay.setApiBase(JeepayTestData.getApiBase()); 19 | Jeepay.apiKey = JeepayTestData.getApiKey(); 20 | Jeepay.mchNo = JeepayTestData.getMchNo(); 21 | Jeepay.appId = JeepayTestData.getAppId(); 22 | } 23 | 24 | @Test 25 | public void testDivisionReceiverBind() { 26 | // 分账接口文档:https://docs.jeequan.com/docs/jeepay/division_api 27 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, Jeepay.getApiBase()); 28 | DivisionReceiverBindRequest request = new DivisionReceiverBindRequest(); 29 | DivisionReceiverBindReqModel model = new DivisionReceiverBindReqModel(); 30 | model.setMchNo(Jeepay.mchNo); // 商户号 31 | model.setAppId(jeepayClient.getAppId()); // 应用ID 32 | model.setIfCode("shengpay"); 33 | model.setReceiverAlias("计全"); 34 | model.setReceiverGroupId(100003L); 35 | model.setAccType((byte)1); 36 | model.setAccNo("32617592"); 37 | model.setAccName("骏易科技"); 38 | model.setRelationType("SERVICE_PROVIDER"); 39 | model.setRelationTypeName("服务商"); 40 | model.setDivisionProfit("0.10"); 41 | request.setBizModel(model); 42 | 43 | try { 44 | DivisionReceiverBindResponse response = jeepayClient.execute(request); 45 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 46 | // 判断转账发起是否成功(并不代表转账成功) 47 | if(response.isSuccess(Jeepay.apiKey)) { 48 | _log.info("accNo:{}, 绑定成功", response.get().getAccNo()); 49 | }else { 50 | _log.info("绑定失败:accNo:{}", model.getAccNo()); 51 | _log.info("通道错误码:{}", response.get().getErrCode()); 52 | _log.info("通道错误信息:{}", response.get().getErrMsg()); 53 | } 54 | } catch (JeepayException e) { 55 | _log.error(e.getMessage()); 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/RefundOrderCreateResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /** 4 | * 退款下单响应实体类 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-18 09:00 8 | */ 9 | public class RefundOrderCreateResModel extends JeepayObject { 10 | 11 | /** 12 | * 退款单号(网关生成) 13 | */ 14 | private String refundOrderId; 15 | 16 | /** 17 | * 商户发起的退款订单号 18 | */ 19 | private String mchRefundNo; 20 | 21 | /** 22 | * 订单支付金额 23 | */ 24 | private Long payAmount; 25 | 26 | /** 27 | * 申请退款金额 28 | */ 29 | private Long refundAmount; 30 | 31 | /** 32 | * 退款状态 33 | * 0-订单生成 34 | * 1-退款中 35 | * 2-退款成功 36 | * 3-退款失败 37 | * 4-退款关闭 38 | */ 39 | private Integer state; 40 | 41 | /** 42 | * 渠道退款单号 43 | */ 44 | private String channelOrderNo; 45 | 46 | /** 47 | * 支付渠道错误码 48 | */ 49 | private String errCode; 50 | 51 | /** 52 | * 支付渠道错误信息 53 | */ 54 | private String errMsg; 55 | 56 | public String getRefundOrderId() { 57 | return refundOrderId; 58 | } 59 | 60 | public void setRefundOrderId(String refundOrderId) { 61 | this.refundOrderId = refundOrderId; 62 | } 63 | 64 | public String getMchRefundNo() { 65 | return mchRefundNo; 66 | } 67 | 68 | public void setMchRefundNo(String mchRefundNo) { 69 | this.mchRefundNo = mchRefundNo; 70 | } 71 | 72 | public Long getPayAmount() { 73 | return payAmount; 74 | } 75 | 76 | public void setPayAmount(Long payAmount) { 77 | this.payAmount = payAmount; 78 | } 79 | 80 | public Long getRefundAmount() { 81 | return refundAmount; 82 | } 83 | 84 | public void setRefundAmount(Long refundAmount) { 85 | this.refundAmount = refundAmount; 86 | } 87 | 88 | public Integer getState() { 89 | return state; 90 | } 91 | 92 | public void setState(Integer state) { 93 | this.state = state; 94 | } 95 | 96 | public String getChannelOrderNo() { 97 | return channelOrderNo; 98 | } 99 | 100 | public void setChannelOrderNo(String channelOrderNo) { 101 | this.channelOrderNo = channelOrderNo; 102 | } 103 | 104 | public String getErrCode() { 105 | return errCode; 106 | } 107 | 108 | public void setErrCode(String errCode) { 109 | this.errCode = errCode; 110 | } 111 | 112 | public String getErrMsg() { 113 | return errMsg; 114 | } 115 | 116 | public void setErrMsg(String errMsg) { 117 | this.errMsg = errMsg; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderDivisionExecReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /*** 6 | * 发起分账 7 | * 8 | * @author terrfly 9 | * @site https://www.jeepay.vip 10 | * @date 2021/8/27 10:16 11 | */ 12 | public class PayOrderDivisionExecReqModel extends JeepayObject { 13 | 14 | private static final long serialVersionUID = -3998573128290306948L; 15 | 16 | @ApiField("mchNo") 17 | private String mchNo; // 商户号 18 | 19 | @ApiField("appId") 20 | private String appId; // 应用ID 21 | 22 | /** 商户订单号 **/ 23 | @ApiField("mchOrderNo") 24 | private String mchOrderNo; 25 | 26 | /** 支付系统订单号 **/ 27 | @ApiField("payOrderId") 28 | private String payOrderId; 29 | 30 | /** 31 | * 是否使用系统配置的自动分账组: 0-否 1-是 32 | **/ 33 | @ApiField("useSysAutoDivisionReceivers") 34 | private Byte useSysAutoDivisionReceivers; 35 | 36 | /** 接收者账号列表(JSONArray 转换为字符串类型) 37 | * 仅当useSysAutoDivisionReceivers=0 时有效。 38 | * 39 | * 参考: 40 | * 41 | * 方式1: 按账号纬度 42 | * [{ 43 | * receiverId: 800001, 44 | * divisionProfit: 0.1 (若不填入则使用系统默认配置值) 45 | * }] 46 | * 47 | * 方式2: 按组纬度 48 | * [{ 49 | * receiverGroupId: 100001, (该组所有 当前订单的渠道账号并且可用状态的全部参与分账) 50 | * divisionProfit: 0.1 (每个账号的分账比例, 若不填入则使用系统默认配置值, 建议不填写) 51 | * }] 52 | * 53 | * **/ 54 | @ApiField("receivers") 55 | private String receivers; 56 | 57 | public String getMchNo() { 58 | return mchNo; 59 | } 60 | 61 | public void setMchNo(String mchNo) { 62 | this.mchNo = mchNo; 63 | } 64 | 65 | public String getAppId() { 66 | return appId; 67 | } 68 | 69 | public void setAppId(String appId) { 70 | this.appId = appId; 71 | } 72 | 73 | public String getMchOrderNo() { 74 | return mchOrderNo; 75 | } 76 | 77 | public void setMchOrderNo(String mchOrderNo) { 78 | this.mchOrderNo = mchOrderNo; 79 | } 80 | 81 | public String getPayOrderId() { 82 | return payOrderId; 83 | } 84 | 85 | public void setPayOrderId(String payOrderId) { 86 | this.payOrderId = payOrderId; 87 | } 88 | 89 | public Byte getUseSysAutoDivisionReceivers() { 90 | return useSysAutoDivisionReceivers; 91 | } 92 | 93 | public void setUseSysAutoDivisionReceivers(Byte useSysAutoDivisionReceivers) { 94 | this.useSysAutoDivisionReceivers = useSysAutoDivisionReceivers; 95 | } 96 | 97 | public String getReceivers() { 98 | return receivers; 99 | } 100 | 101 | public void setReceivers(String receivers) { 102 | this.receivers = receivers; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/HttpHeaders.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | import com.jeequan.jeepay.util.CaseInsensitiveMap; 4 | 5 | import java.util.*; 6 | 7 | import static java.util.Objects.requireNonNull; 8 | 9 | /** 10 | * Http请求头 11 | * @author jmdhappy 12 | * @site https://www.jeepay.vip 13 | * @date 2021-06-08 11:00 14 | */ 15 | public class HttpHeaders { 16 | private CaseInsensitiveMap> headerMap; 17 | 18 | private HttpHeaders(CaseInsensitiveMap> headerMap) { 19 | this.headerMap = headerMap; 20 | } 21 | 22 | public static HttpHeaders of(Map> headerMap) { 23 | requireNonNull(headerMap); 24 | return new HttpHeaders(CaseInsensitiveMap.of(headerMap)); 25 | } 26 | 27 | public HttpHeaders withAdditionalHeader(String name, String value) { 28 | requireNonNull(name); 29 | requireNonNull(value); 30 | return this.withAdditionalHeader(name, Arrays.asList(value)); 31 | } 32 | 33 | public HttpHeaders withAdditionalHeader(String name, List values) { 34 | requireNonNull(name); 35 | requireNonNull(values); 36 | Map> headerMap = new HashMap<>(); 37 | headerMap.put(name, values); 38 | return this.withAdditionalHeaders(headerMap); 39 | } 40 | 41 | public HttpHeaders withAdditionalHeaders(Map> headerMap) { 42 | requireNonNull(headerMap); 43 | Map> newHeaderMap = new HashMap<>(this.map()); 44 | newHeaderMap.putAll(headerMap); 45 | return HttpHeaders.of(newHeaderMap); 46 | } 47 | 48 | public List allValues(String name) { 49 | if (this.headerMap.containsKey(name)) { 50 | List values = this.headerMap.get(name); 51 | if ((values != null) && (values.size() > 0)) { 52 | return Collections.unmodifiableList(values); 53 | } 54 | } 55 | return Collections.emptyList(); 56 | } 57 | 58 | public Optional firstValue(String name) { 59 | if (this.headerMap.containsKey(name)) { 60 | List values = this.headerMap.get(name); 61 | if ((values != null) && (values.size() > 0)) { 62 | return Optional.of(values.get(0)); 63 | } 64 | } 65 | return Optional.empty(); 66 | } 67 | 68 | public Map> map() { 69 | return Collections.unmodifiableMap(this.headerMap); 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | StringBuilder sb = new StringBuilder(); 75 | sb.append(super.toString()); 76 | sb.append(" { "); 77 | sb.append(map()); 78 | sb.append(" }"); 79 | return sb.toString(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/com/jeequan/jeepay/PayOrderDivisionReceiverExecTest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.jeequan.jeepay.exception.JeepayException; 6 | import com.jeequan.jeepay.model.PayOrderDivisionExecReqModel; 7 | import com.jeequan.jeepay.request.PayOrderDivisionExecRequest; 8 | import com.jeequan.jeepay.response.PayOrderDivisionExecResponse; 9 | import org.junit.jupiter.api.BeforeAll; 10 | import org.junit.jupiter.api.Test; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.lang.reflect.Array; 15 | 16 | class PayOrderDivisionReceiverExecTest { 17 | 18 | final static Logger _log = LoggerFactory.getLogger(PayOrderDivisionReceiverExecTest.class); 19 | 20 | @BeforeAll 21 | public static void initApiKey() { 22 | Jeepay.setApiBase(JeepayTestData.getApiBase()); 23 | Jeepay.apiKey = JeepayTestData.getApiKey(); 24 | Jeepay.mchNo = JeepayTestData.getMchNo(); 25 | Jeepay.appId = JeepayTestData.getAppId(); 26 | } 27 | 28 | @Test 29 | public void testPayOrderDivisionExec() { 30 | // 分账接口文档:https://docs.jeequan.com/docs/jeepay/division_api 31 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, Jeepay.getApiBase()); 32 | PayOrderDivisionExecRequest request = new PayOrderDivisionExecRequest(); 33 | PayOrderDivisionExecReqModel model = new PayOrderDivisionExecReqModel(); 34 | request.setBizModel(model); 35 | model.setMchNo(Jeepay.mchNo); // 商户号 36 | model.setAppId(jeepayClient.getAppId()); // 应用ID 37 | model.setPayOrderId("P1470667876906389505"); 38 | model.setUseSysAutoDivisionReceivers((byte) 0); 39 | 40 | JSONArray receviers = new JSONArray(); 41 | receviers.add(JSONObject.parseObject("{receiverId: '800004', receiverGroupId: '', divisionProfit: '0.1'}")); 42 | receviers.add(JSONObject.parseObject("{receiverId: '800005', receiverGroupId: '', divisionProfit: '0.2'}")); 43 | 44 | model.setReceivers(receviers.toJSONString()); 45 | 46 | try { 47 | PayOrderDivisionExecResponse response = jeepayClient.execute(request); 48 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 49 | // 判断转账发起是否成功(并不代表转账成功) 50 | if(response.isSuccess(Jeepay.apiKey)) { 51 | _log.info("渠道分账订单号:{}, 分账成功", response.get().getChannelBatchOrderId()); 52 | }else { 53 | _log.info("分账失败:payOrderId:{}", model.getPayOrderId()); 54 | _log.info("通道错误码:{}", response.get().getErrCode()); 55 | _log.info("通道错误信息:{}", response.get().getErrMsg()); 56 | } 57 | } catch (JeepayException e) { 58 | _log.error(e.getMessage()); 59 | } 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/util/CaseInsensitiveMap.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.util; 2 | 3 | import java.io.Serializable; 4 | import java.util.*; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * 不区分大小写转换Map 9 | * @author jmdhappy 10 | * @site https://www.jeepay.vip 11 | * @date 2021-06-08 11:00 12 | */ 13 | public class CaseInsensitiveMap extends AbstractMap 14 | implements Map, Cloneable, Serializable { 15 | 16 | private static final long serialVersionUID = -1121409735905111733L; 17 | 18 | private Map> store; 19 | 20 | public CaseInsensitiveMap() { 21 | this.store = new HashMap>(); 22 | } 23 | 24 | public static CaseInsensitiveMap of(Map map) { 25 | if (map == null) { 26 | return null; 27 | } 28 | CaseInsensitiveMap ciMap = new CaseInsensitiveMap<>(); 29 | ciMap.putAll(map); 30 | return ciMap; 31 | } 32 | 33 | @Override 34 | public boolean containsKey(Object key) { 35 | String keyLower = convertKey(key); 36 | return this.store.containsKey(keyLower); 37 | } 38 | 39 | @Override 40 | public boolean containsValue(Object value) { 41 | return this.values().contains(value); 42 | } 43 | 44 | @Override 45 | public V get(Object key) { 46 | String keyLower = convertKey(key); 47 | Entry entry = this.store.get(keyLower); 48 | if (entry == null) { 49 | return null; 50 | } 51 | return entry.getValue(); 52 | } 53 | 54 | @Override 55 | public V put(String key, V value) { 56 | String keyLower = convertKey(key); 57 | this.store.put(keyLower, new SimpleEntry(key, value)); 58 | return value; 59 | } 60 | 61 | @Override 62 | public V remove(Object key) { 63 | String keyLower = convertKey(key); 64 | Entry entry = this.store.remove(keyLower); 65 | if (entry == null) { 66 | return null; 67 | } 68 | return entry.getValue(); 69 | } 70 | 71 | @Override 72 | public void clear() { 73 | this.store.clear(); 74 | } 75 | 76 | @Override 77 | public Set keySet() { 78 | return this.store.values().stream().map(entry -> entry.getKey()).collect(Collectors.toSet()); 79 | } 80 | 81 | @Override 82 | public Collection values() { 83 | return this.store.values().stream().map(entry -> entry.getValue()).collect(Collectors.toList()); 84 | } 85 | 86 | @Override 87 | public Set> entrySet() { 88 | return this.store.values().stream().collect(Collectors.toSet()); 89 | } 90 | 91 | private static String convertKey(Object key) { 92 | if (key == null) { 93 | return null; 94 | } else if (key instanceof String) { 95 | return ((String) key).toLowerCase(); 96 | } 97 | throw new IllegalArgumentException("key must be a String"); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/TransferOrderCreateResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /*** 4 | * 转账下单响应实体类 5 | * 6 | * @author terrfly 7 | * @site https://www.jeepay.vip 8 | * @date 2021/8/13 16:08 9 | */ 10 | public class TransferOrderCreateResModel extends JeepayObject { 11 | 12 | /** 13 | * 转账单号(网关生成) 14 | */ 15 | private String transferId; 16 | 17 | /** 18 | * 商户发起的转账订单号 19 | */ 20 | private String mchOrderNo; 21 | 22 | /** 23 | * 订单转账金额 24 | */ 25 | private Long amount; 26 | 27 | /** 28 | * 收款账号 29 | */ 30 | private String accountNo; 31 | 32 | /** 33 | * 收款人姓名 34 | */ 35 | private String accountName; 36 | 37 | /** 38 | * 收款人开户行名称 39 | */ 40 | private String bankName; 41 | 42 | 43 | /** 44 | * 转账状态 45 | * 0-订单生成 46 | * 1-转账中 47 | * 2-转账成功 48 | * 3-转账失败 49 | * 4-转账关闭 50 | */ 51 | private Integer state; 52 | 53 | /** 54 | * 渠道转账单号 55 | */ 56 | private String channelOrderNo; 57 | 58 | /** 59 | * 渠道响应数据(如微信确认数据包) 60 | */ 61 | private String channelResData; 62 | 63 | /** 64 | * 支付渠道错误码 65 | */ 66 | private String errCode; 67 | 68 | /** 69 | * 支付渠道错误信息 70 | */ 71 | private String errMsg; 72 | 73 | 74 | public String getTransferId() { 75 | return transferId; 76 | } 77 | 78 | public void setTransferId(String transferId) { 79 | this.transferId = transferId; 80 | } 81 | 82 | public String getMchOrderNo() { 83 | return mchOrderNo; 84 | } 85 | 86 | public void setMchOrderNo(String mchOrderNo) { 87 | this.mchOrderNo = mchOrderNo; 88 | } 89 | 90 | public Long getAmount() { 91 | return amount; 92 | } 93 | 94 | public void setAmount(Long amount) { 95 | this.amount = amount; 96 | } 97 | 98 | public String getAccountNo() { 99 | return accountNo; 100 | } 101 | 102 | public void setAccountNo(String accountNo) { 103 | this.accountNo = accountNo; 104 | } 105 | 106 | public String getAccountName() { 107 | return accountName; 108 | } 109 | 110 | public void setAccountName(String accountName) { 111 | this.accountName = accountName; 112 | } 113 | 114 | public String getBankName() { 115 | return bankName; 116 | } 117 | 118 | public void setBankName(String bankName) { 119 | this.bankName = bankName; 120 | } 121 | 122 | public Integer getState() { 123 | return state; 124 | } 125 | 126 | public void setState(Integer state) { 127 | this.state = state; 128 | } 129 | 130 | public String getChannelOrderNo() { 131 | return channelOrderNo; 132 | } 133 | 134 | public void setChannelOrderNo(String channelOrderNo) { 135 | this.channelOrderNo = channelOrderNo; 136 | } 137 | 138 | public String getChannelResData() { 139 | return channelResData; 140 | } 141 | 142 | public void setChannelResData(String channelResData) { 143 | this.channelResData = channelResData; 144 | } 145 | 146 | public String getErrCode() { 147 | return errCode; 148 | } 149 | 150 | public void setErrCode(String errCode) { 151 | this.errCode = errCode; 152 | } 153 | 154 | public String getErrMsg() { 155 | return errMsg; 156 | } 157 | 158 | public void setErrMsg(String errMsg) { 159 | this.errMsg = errMsg; 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/HttpURLConnectionClient.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | import com.jeequan.jeepay.exception.APIConnectionException; 4 | import com.jeequan.jeepay.util.StreamUtils; 5 | import com.jeequan.jeepay.util.StringUtils; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.net.HttpURLConnection; 10 | import java.util.Collections; 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | /** 16 | * HttpURL连接客户端 17 | * @author jmdhappy 18 | * @site https://www.jeepay.vip 19 | * @date 2021-06-08 11:00 20 | */ 21 | public class HttpURLConnectionClient extends HttpClient { 22 | 23 | public HttpURLConnectionClient() { 24 | super(); 25 | } 26 | 27 | @Override 28 | public APIJeepayResponse request(APIJeepayRequest request) throws APIConnectionException { 29 | HttpURLConnection conn = null; 30 | 31 | try { 32 | conn = createJeepayConnection(request); 33 | 34 | // trigger the request 35 | int responseCode = conn.getResponseCode(); 36 | HttpHeaders headers = HttpHeaders.of(conn.getHeaderFields()); 37 | String responseBody; 38 | 39 | if (responseCode >= 200 && responseCode < 300) { 40 | responseBody = StreamUtils.readToEnd(conn.getInputStream(), APIResource.CHARSET); 41 | } else { 42 | responseBody = StreamUtils.readToEnd(conn.getErrorStream(), APIResource.CHARSET); 43 | } 44 | 45 | return new APIJeepayResponse(responseCode, responseBody, headers); 46 | } catch (IOException e) { 47 | throw new APIConnectionException( 48 | String.format( 49 | "请求Jeepay(%s)异常,请检查网络或重试.异常信息:%s", 50 | request.getUrl(), e.getMessage()), 51 | e); 52 | } finally { 53 | if (conn != null) { 54 | conn.disconnect(); 55 | } 56 | } 57 | } 58 | 59 | static HttpHeaders getHeaders(APIJeepayRequest request) { 60 | Map> userAgentHeadersMap = new HashMap<>(); 61 | 62 | userAgentHeadersMap.put("User-Agent", Collections.singletonList(buildUserAgentString(request.getOptions().getVersion()))); 63 | userAgentHeadersMap.put( 64 | "X-Jeepay-Client-User-Agent", Collections.singletonList(buildXJeepayClientUserAgentString(request.getOptions().getVersion()))); 65 | 66 | return request.getHeaders().withAdditionalHeaders(userAgentHeadersMap); 67 | } 68 | 69 | private static HttpURLConnection createJeepayConnection(APIJeepayRequest request) 70 | throws IOException { 71 | HttpURLConnection conn = (HttpURLConnection) request.url.openConnection(); 72 | 73 | conn.setConnectTimeout(request.options.getConnectTimeout()); 74 | conn.setReadTimeout(request.options.getReadTimeout()); 75 | conn.setUseCaches(false); 76 | for (Map.Entry> entry : getHeaders(request).map().entrySet()) { 77 | conn.setRequestProperty(entry.getKey(), StringUtils.join(",", entry.getValue())); 78 | } 79 | 80 | conn.setRequestMethod(request.method.name()); 81 | 82 | // 如有其他业务参数,可在此处增加 83 | 84 | if (request.content != null) { 85 | conn.setDoOutput(true); 86 | conn.setRequestProperty("Content-Type", request.content.contentType); 87 | 88 | try (OutputStream output = conn.getOutputStream()) { 89 | output.write(request.content.byteArrayContent); 90 | } 91 | } 92 | 93 | return conn; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/Jeepay.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | /** 4 | * Jeepay数据对象 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:00 8 | */ 9 | public abstract class Jeepay { 10 | 11 | public static final String LIVE_API_BASE = "https://pay.jeepay.vip"; 12 | public static final String VERSION = "1.0"; 13 | public static final String DEFAULT_SIGN_TYPE = "MD5"; 14 | public static final String API_VERSION_NAME = "version"; 15 | public static final String API_SIGN_TYPE_NAME = "signType"; 16 | public static final String API_SIGN_NAME = "sign"; 17 | public static final String API_REQ_TIME_NAME = "reqTime"; 18 | 19 | /** 20 | * 默认时间格式 21 | **/ 22 | public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; 23 | 24 | /** 25 | * Date默认时区 26 | **/ 27 | public static final String DATE_TIMEZONE = "GMT+8"; 28 | 29 | public static String acceptLanguage = "zh-CN"; 30 | 31 | public static volatile String mchNo; 32 | 33 | public static volatile String appId; 34 | 35 | /** 36 | * 私钥 37 | */ 38 | public static volatile String apiKey; 39 | 40 | /** 41 | * API 地址 42 | */ 43 | private static volatile String apiBase = LIVE_API_BASE; 44 | 45 | public static volatile String privateKey; 46 | public static volatile String privateKeyPath; 47 | 48 | public static Boolean DEBUG = false; 49 | 50 | public static final int DEFAULT_CONNECT_TIMEOUT = 30 * 1000; 51 | public static final int DEFAULT_READ_TIMEOUT = 80 * 1000; 52 | 53 | private static volatile int connectTimeout = -1; 54 | private static volatile int readTimeout = -1; 55 | 56 | private static volatile int maxNetworkRetries = 1; 57 | 58 | public static void overrideApiBase(final String overriddenApiBase) { 59 | apiBase = overriddenApiBase; 60 | } 61 | 62 | public static String getApiBase() { 63 | return apiBase; 64 | } 65 | 66 | public static void setApiBase(String apiBase) { 67 | Jeepay.apiBase = apiBase; 68 | } 69 | 70 | /** 71 | * 网络连接超时时间 72 | * @return 73 | */ 74 | public static int getConnectTimeout() { 75 | if (connectTimeout == -1) { 76 | return DEFAULT_CONNECT_TIMEOUT; 77 | } 78 | return connectTimeout; 79 | } 80 | 81 | /** 82 | * 设置网络连接超时时间 (毫秒) 83 | * @param timeout 84 | */ 85 | public static void setConnectTimeout(final int timeout) { 86 | connectTimeout = timeout; 87 | } 88 | 89 | /** 90 | * 数据读取超时时间 91 | * @return 92 | */ 93 | public static int getReadTimeout() { 94 | if (readTimeout == -1) { 95 | return DEFAULT_READ_TIMEOUT; 96 | } 97 | return readTimeout; 98 | } 99 | 100 | /** 101 | * 设置数据读取超时时间 (毫秒) 102 | * 不同接口的耗时时间不一样,部分接口的耗时可能比较长。 103 | * @param timeout 104 | */ 105 | public static void setReadTimeout(final int timeout) { 106 | readTimeout = timeout; 107 | } 108 | 109 | /** 110 | * 连接失败时的最大重试次数 111 | * @return 112 | */ 113 | public static int getMaxNetworkRetries() { 114 | return maxNetworkRetries; 115 | } 116 | 117 | /** 118 | * 设置连接失败时的最大重试次数 119 | * @param numRetries 120 | */ 121 | public static void setMaxNetworkRetries(final int numRetries) { 122 | maxNetworkRetries = numRetries; 123 | } 124 | 125 | public static String getAcceptLanguage() { 126 | return acceptLanguage; 127 | } 128 | 129 | public static void setAcceptLanguage(String acceptLanguage) { 130 | Jeepay.acceptLanguage = acceptLanguage; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/RefundOrderCreateReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /** 6 | * 退款下单请求实体类 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-18 09:00 10 | */ 11 | public class RefundOrderCreateReqModel extends JeepayObject { 12 | 13 | private static final long serialVersionUID = -3998573128290306948L; 14 | 15 | @ApiField("mchNo") 16 | private String mchNo; // 商户号 17 | @ApiField("appId") 18 | private String appId; // 应用ID 19 | @ApiField("mchOrderNo") 20 | String mchOrderNo; // 商户订单号 21 | @ApiField("payOrderId") 22 | String payOrderId; // 支付系统订单号 23 | @ApiField("mchRefundNo") 24 | String mchRefundNo; // 退款单号 25 | @ApiField("refundAmount") 26 | Long refundAmount; // 退款金额 27 | @ApiField("currency") 28 | String currency; // 货币代码,当前只支持cny 29 | @ApiField("refundReason") 30 | String refundReason; // 退款原因 31 | @ApiField("clientIp") 32 | String clientIp; // 客户端IP 33 | @ApiField("notifyUrl") 34 | String notifyUrl; // 异步通知地址 35 | @ApiField("channelExtra") 36 | String channelExtra; // 特定渠道额外支付参数 37 | @ApiField("extParam") 38 | String extParam; // 商户扩展参数 39 | 40 | public RefundOrderCreateReqModel() { 41 | } 42 | 43 | public String getMchNo() { 44 | return mchNo; 45 | } 46 | 47 | public void setMchNo(String mchNo) { 48 | this.mchNo = mchNo; 49 | } 50 | 51 | public String getAppId() { 52 | return appId; 53 | } 54 | 55 | public void setAppId(String appId) { 56 | this.appId = appId; 57 | } 58 | 59 | public String getMchOrderNo() { 60 | return mchOrderNo; 61 | } 62 | 63 | public void setMchOrderNo(String mchOrderNo) { 64 | this.mchOrderNo = mchOrderNo; 65 | } 66 | 67 | public String getPayOrderId() { 68 | return payOrderId; 69 | } 70 | 71 | public void setPayOrderId(String payOrderId) { 72 | this.payOrderId = payOrderId; 73 | } 74 | 75 | public String getMchRefundNo() { 76 | return mchRefundNo; 77 | } 78 | 79 | public void setMchRefundNo(String mchRefundNo) { 80 | this.mchRefundNo = mchRefundNo; 81 | } 82 | 83 | public Long getRefundAmount() { 84 | return refundAmount; 85 | } 86 | 87 | public void setRefundAmount(Long refundAmount) { 88 | this.refundAmount = refundAmount; 89 | } 90 | 91 | public String getCurrency() { 92 | return currency; 93 | } 94 | 95 | public void setCurrency(String currency) { 96 | this.currency = currency; 97 | } 98 | 99 | public String getRefundReason() { 100 | return refundReason; 101 | } 102 | 103 | public void setRefundReason(String refundReason) { 104 | this.refundReason = refundReason; 105 | } 106 | 107 | public String getClientIp() { 108 | return clientIp; 109 | } 110 | 111 | public void setClientIp(String clientIp) { 112 | this.clientIp = clientIp; 113 | } 114 | 115 | public String getNotifyUrl() { 116 | return notifyUrl; 117 | } 118 | 119 | public void setNotifyUrl(String notifyUrl) { 120 | this.notifyUrl = notifyUrl; 121 | } 122 | 123 | public String getChannelExtra() { 124 | return channelExtra; 125 | } 126 | 127 | public void setChannelExtra(String channelExtra) { 128 | this.channelExtra = channelExtra; 129 | } 130 | 131 | public String getExtParam() { 132 | return extParam; 133 | } 134 | 135 | public void setExtParam(String extParam) { 136 | this.extParam = extParam; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/util/JeepayKit.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.util; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import java.security.MessageDigest; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.util.ArrayList; 10 | import java.util.Arrays; 11 | import java.util.Map; 12 | 13 | /** 14 | * Jeepay签名工具类 15 | * @author jmdhappy 16 | * @site https://www.jeepay.vip 17 | * @date 2021-06-08 11:00 18 | */ 19 | public class JeepayKit { 20 | 21 | private static String encodingCharset = "UTF-8"; 22 | 23 | private static final Logger _log = LoggerFactory.getLogger(JeepayKit.class); 24 | 25 | /** 26 | * 获取签名串 27 | * @param map 28 | * @return urlParam.append(key).append("=").append( paraMap.get(key) == null ? "" : paraMap.get(key) ); 29 | */ 30 | public static String getStrSort(Map map){ 31 | ArrayList list = new ArrayList(); 32 | for(Map.Entry entry:map.entrySet()){ 33 | if(null != entry.getValue() && !"".equals(entry.getValue())){ 34 | list.add(entry.getKey() + "=" + entry.getValue() + "&"); 35 | } 36 | } 37 | int size = list.size(); 38 | String [] arrayToSort = list.toArray(new String[size]); 39 | Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER); 40 | StringBuilder sb = new StringBuilder(); 41 | for(int i = 0; i < size; i ++) { 42 | sb.append(arrayToSort[i]); 43 | } 44 | return sb.toString(); 45 | } 46 | 47 | /** 48 | *

Description: 计算签名摘要 49 | *

2018年9月30日 上午11:32:46 50 | * @param map 参数Map 51 | * @param key 商户秘钥 52 | * @return 53 | */ 54 | public static String getSign(Map map, String key){ 55 | String result = getStrSort(map); 56 | result += "key=" + key; 57 | if(_log.isDebugEnabled()) _log.debug("signStr:{}", result); 58 | result = md5(result, encodingCharset).toUpperCase(); 59 | if(_log.isDebugEnabled()) _log.debug("signValue:{}", result); 60 | return result; 61 | } 62 | 63 | public static String getSign(String signStr, String key){ 64 | signStr += "key=" + key; 65 | String result = md5(signStr, encodingCharset).toUpperCase(); 66 | return result; 67 | } 68 | 69 | public static String getSign(String signStr){ 70 | return md5(signStr, encodingCharset).toUpperCase(); 71 | } 72 | 73 | /** 74 | *

Description: MD5 75 | *

2018年9月30日 上午11:33:19 76 | * @param value 77 | * @param charset 78 | * @return 79 | */ 80 | public static String md5(String value, String charset) { 81 | MessageDigest md = null; 82 | try { 83 | byte[] data = value.getBytes(charset); 84 | md = MessageDigest.getInstance("MD5"); 85 | byte[] digestData = md.digest(data); 86 | return toHex(digestData); 87 | } catch (NoSuchAlgorithmException e) { 88 | e.printStackTrace(); 89 | return null; 90 | } catch (UnsupportedEncodingException e) { 91 | e.printStackTrace(); 92 | return null; 93 | } 94 | } 95 | 96 | public static String toHex(byte input[]) { 97 | if (input == null) 98 | return null; 99 | StringBuffer output = new StringBuffer(input.length * 2); 100 | for (int i = 0; i < input.length; i++) { 101 | int current = input[i] & 0xff; 102 | if (current < 16) 103 | output.append("0"); 104 | output.append(Integer.toString(current, 16)); 105 | } 106 | 107 | return output.toString(); 108 | } 109 | 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/DivisionReceiverBindReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | 6 | /*** 7 | * 分账账号的绑定 8 | * 9 | * @author terrfly 10 | * @site https://www.jeepay.vip 11 | * @date 2021/8/25 10:36 12 | */ 13 | public class DivisionReceiverBindReqModel extends JeepayObject { 14 | 15 | private static final long serialVersionUID = -3998573128290306948L; 16 | 17 | @ApiField("mchNo") 18 | private String mchNo; // 商户号 19 | 20 | @ApiField("appId") 21 | private String appId; // 应用ID 22 | 23 | /** 支付接口代码 **/ 24 | @ApiField("ifCode") 25 | private String ifCode; 26 | 27 | /** 接收者账号别名 **/ 28 | @ApiField("receiverAlias") 29 | private String receiverAlias; 30 | 31 | /** 组ID **/ 32 | @ApiField("receiverGroupId") 33 | private Long receiverGroupId; 34 | 35 | /** 分账接收账号类型: 0-个人(对私) 1-商户(对公) **/ 36 | @ApiField("accType") 37 | private Byte accType; 38 | 39 | /** 分账接收账号 **/ 40 | @ApiField("accNo") 41 | private String accNo; 42 | 43 | /** 分账接收账号名称 **/ 44 | @ApiField("accName") 45 | private String accName; 46 | 47 | /** 分账关系类型(参考微信), 如: SERVICE_PROVIDER 服务商等 **/ 48 | @ApiField("relationType") 49 | private String relationType; 50 | 51 | /** 当选择自定义时,需要录入该字段。 否则为对应的名称 **/ 52 | @ApiField("relationTypeName") 53 | private String relationTypeName; 54 | 55 | /** 渠道特殊信息 */ 56 | @ApiField("channelExtInfo") 57 | private String channelExtInfo; 58 | 59 | /** 分账比例 **/ 60 | @ApiField("divisionProfit") 61 | private String divisionProfit; 62 | 63 | public String getMchNo() { 64 | return mchNo; 65 | } 66 | 67 | public void setMchNo(String mchNo) { 68 | this.mchNo = mchNo; 69 | } 70 | 71 | public String getAppId() { 72 | return appId; 73 | } 74 | 75 | public void setAppId(String appId) { 76 | this.appId = appId; 77 | } 78 | 79 | public String getIfCode() { 80 | return ifCode; 81 | } 82 | 83 | public void setIfCode(String ifCode) { 84 | this.ifCode = ifCode; 85 | } 86 | 87 | public String getReceiverAlias() { 88 | return receiverAlias; 89 | } 90 | 91 | public void setReceiverAlias(String receiverAlias) { 92 | this.receiverAlias = receiverAlias; 93 | } 94 | 95 | public Long getReceiverGroupId() { 96 | return receiverGroupId; 97 | } 98 | 99 | public void setReceiverGroupId(Long receiverGroupId) { 100 | this.receiverGroupId = receiverGroupId; 101 | } 102 | 103 | public Byte getAccType() { 104 | return accType; 105 | } 106 | 107 | public void setAccType(Byte accType) { 108 | this.accType = accType; 109 | } 110 | 111 | public String getAccNo() { 112 | return accNo; 113 | } 114 | 115 | public void setAccNo(String accNo) { 116 | this.accNo = accNo; 117 | } 118 | 119 | public String getAccName() { 120 | return accName; 121 | } 122 | 123 | public void setAccName(String accName) { 124 | this.accName = accName; 125 | } 126 | 127 | public String getRelationType() { 128 | return relationType; 129 | } 130 | 131 | public void setRelationType(String relationType) { 132 | this.relationType = relationType; 133 | } 134 | 135 | public String getRelationTypeName() { 136 | return relationTypeName; 137 | } 138 | 139 | public void setRelationTypeName(String relationTypeName) { 140 | this.relationTypeName = relationTypeName; 141 | } 142 | 143 | public String getChannelExtInfo() { 144 | return channelExtInfo; 145 | } 146 | 147 | public void setChannelExtInfo(String channelExtInfo) { 148 | this.channelExtInfo = channelExtInfo; 149 | } 150 | 151 | public String getDivisionProfit() { 152 | return divisionProfit; 153 | } 154 | 155 | public void setDivisionProfit(String divisionProfit) { 156 | this.divisionProfit = divisionProfit; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/TransferOrderCreateReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /*** 6 | * 转账下单请求实体类 7 | * 8 | * @author terrfly 9 | * @site https://www.jeepay.vip 10 | * @date 2021/8/13 16:08 11 | */ 12 | public class TransferOrderCreateReqModel extends JeepayObject { 13 | 14 | private static final long serialVersionUID = -3998573128290306948L; 15 | 16 | @ApiField("mchNo") 17 | private String mchNo; // 商户号 18 | @ApiField("appId") 19 | private String appId; // 应用ID 20 | @ApiField("mchOrderNo") 21 | String mchOrderNo; // 商户订单号 22 | @ApiField("ifCode") 23 | String ifCode; // 支付接口代码 24 | @ApiField("entryType") 25 | String entryType; // 入账方式 26 | @ApiField("amount") 27 | Long amount; // 转账金额 28 | @ApiField("currency") 29 | String currency; // 货币代码,当前只支持cny 30 | @ApiField("accountNo") 31 | String accountNo; // 收款账号 32 | @ApiField("accountName") 33 | String accountName; // 收款人姓名 34 | @ApiField("bankName") 35 | String bankName; // 收款人开户行名称 36 | @ApiField("clientIp") 37 | String clientIp; // 客户端IP 38 | @ApiField("transferDesc") 39 | String transferDesc; // 转账备注信息 40 | @ApiField("notifyUrl") 41 | String notifyUrl; // 异步通知地址 42 | @ApiField("channelExtra") 43 | String channelExtra; // 特定渠道额外支付参数 44 | @ApiField("extParam") 45 | String extParam; // 商户扩展参数 46 | 47 | public TransferOrderCreateReqModel() { 48 | } 49 | 50 | public String getMchNo() { 51 | return mchNo; 52 | } 53 | 54 | public void setMchNo(String mchNo) { 55 | this.mchNo = mchNo; 56 | } 57 | 58 | public String getAppId() { 59 | return appId; 60 | } 61 | 62 | public void setAppId(String appId) { 63 | this.appId = appId; 64 | } 65 | 66 | public String getMchOrderNo() { 67 | return mchOrderNo; 68 | } 69 | 70 | public void setMchOrderNo(String mchOrderNo) { 71 | this.mchOrderNo = mchOrderNo; 72 | } 73 | 74 | public String getIfCode() { 75 | return ifCode; 76 | } 77 | 78 | public void setIfCode(String ifCode) { 79 | this.ifCode = ifCode; 80 | } 81 | 82 | public String getEntryType() { 83 | return entryType; 84 | } 85 | 86 | public void setEntryType(String entryType) { 87 | this.entryType = entryType; 88 | } 89 | 90 | public Long getAmount() { 91 | return amount; 92 | } 93 | 94 | public void setAmount(Long amount) { 95 | this.amount = amount; 96 | } 97 | 98 | public String getCurrency() { 99 | return currency; 100 | } 101 | 102 | public void setCurrency(String currency) { 103 | this.currency = currency; 104 | } 105 | 106 | public String getAccountNo() { 107 | return accountNo; 108 | } 109 | 110 | public void setAccountNo(String accountNo) { 111 | this.accountNo = accountNo; 112 | } 113 | 114 | public String getAccountName() { 115 | return accountName; 116 | } 117 | 118 | public void setAccountName(String accountName) { 119 | this.accountName = accountName; 120 | } 121 | 122 | public String getBankName() { 123 | return bankName; 124 | } 125 | 126 | public void setBankName(String bankName) { 127 | this.bankName = bankName; 128 | } 129 | 130 | public String getClientIp() { 131 | return clientIp; 132 | } 133 | 134 | public void setClientIp(String clientIp) { 135 | this.clientIp = clientIp; 136 | } 137 | 138 | public String getTransferDesc() { 139 | return transferDesc; 140 | } 141 | 142 | public void setTransferDesc(String transferDesc) { 143 | this.transferDesc = transferDesc; 144 | } 145 | 146 | public String getNotifyUrl() { 147 | return notifyUrl; 148 | } 149 | 150 | public void setNotifyUrl(String notifyUrl) { 151 | this.notifyUrl = notifyUrl; 152 | } 153 | 154 | public String getChannelExtra() { 155 | return channelExtra; 156 | } 157 | 158 | public void setChannelExtra(String channelExtra) { 159 | this.channelExtra = channelExtra; 160 | } 161 | 162 | public String getExtParam() { 163 | return extParam; 164 | } 165 | 166 | public void setExtParam(String extParam) { 167 | this.extParam = extParam; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderCreateReqModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | 5 | /** 6 | * 支付下单请求实体类 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-08 11:00 10 | */ 11 | public class PayOrderCreateReqModel extends JeepayObject { 12 | 13 | private static final long serialVersionUID = -3998573128290306948L; 14 | 15 | @ApiField("mchNo") 16 | private String mchNo; // 商户号 17 | @ApiField("appId") 18 | private String appId; // 应用ID 19 | @ApiField("mchOrderNo") 20 | String mchOrderNo; // 商户订单号 21 | @ApiField("wayCode") 22 | String wayCode; // 支付方式 23 | @ApiField("amount") 24 | Long amount; // 支付金额 25 | @ApiField("currency") 26 | String currency; // 货币代码,当前只支持cny 27 | @ApiField("clientIp") 28 | String clientIp; // 客户端IP 29 | @ApiField("subject") 30 | String subject; // 商品标题 31 | @ApiField("body") 32 | String body; // 商品描述 33 | @ApiField("notifyUrl") 34 | String notifyUrl; // 异步通知地址 35 | @ApiField("returnUrl") 36 | String returnUrl; // 跳转通知地址 37 | @ApiField("expiredTime") 38 | String expiredTime; // 订单失效时间 39 | @ApiField("channelExtra") 40 | String channelExtra; // 特定渠道额外支付参数 41 | @ApiField("extParam") 42 | String extParam; // 商户扩展参数 43 | @ApiField("divisionMode") 44 | private Byte divisionMode; // 分账模式: 0-该笔订单不允许分账[默认], 1-支付成功按配置自动完成分账, 2-商户手动分账(解冻商户金额) 45 | 46 | public PayOrderCreateReqModel() { 47 | } 48 | 49 | public String getMchNo() { 50 | return mchNo; 51 | } 52 | 53 | public void setMchNo(String mchNo) { 54 | this.mchNo = mchNo; 55 | } 56 | 57 | public String getAppId() { 58 | return appId; 59 | } 60 | 61 | public void setAppId(String appId) { 62 | this.appId = appId; 63 | } 64 | 65 | public String getMchOrderNo() { 66 | return mchOrderNo; 67 | } 68 | 69 | public void setMchOrderNo(String mchOrderNo) { 70 | this.mchOrderNo = mchOrderNo; 71 | } 72 | 73 | public String getWayCode() { 74 | return wayCode; 75 | } 76 | 77 | public void setWayCode(String wayCode) { 78 | this.wayCode = wayCode; 79 | } 80 | 81 | public Long getAmount() { 82 | return amount; 83 | } 84 | 85 | public void setAmount(Long amount) { 86 | this.amount = amount; 87 | } 88 | 89 | public String getCurrency() { 90 | return currency; 91 | } 92 | 93 | public void setCurrency(String currency) { 94 | this.currency = currency; 95 | } 96 | 97 | public String getClientIp() { 98 | return clientIp; 99 | } 100 | 101 | public void setClientIp(String clientIp) { 102 | this.clientIp = clientIp; 103 | } 104 | 105 | public String getSubject() { 106 | return subject; 107 | } 108 | 109 | public void setSubject(String subject) { 110 | this.subject = subject; 111 | } 112 | 113 | public String getBody() { 114 | return body; 115 | } 116 | 117 | public void setBody(String body) { 118 | this.body = body; 119 | } 120 | 121 | public String getNotifyUrl() { 122 | return notifyUrl; 123 | } 124 | 125 | public void setNotifyUrl(String notifyUrl) { 126 | this.notifyUrl = notifyUrl; 127 | } 128 | 129 | public String getReturnUrl() { 130 | return returnUrl; 131 | } 132 | 133 | public void setReturnUrl(String returnUrl) { 134 | this.returnUrl = returnUrl; 135 | } 136 | 137 | public String getExpiredTime() { 138 | return expiredTime; 139 | } 140 | 141 | public void setExpiredTime(String expiredTime) { 142 | this.expiredTime = expiredTime; 143 | } 144 | 145 | public String getChannelExtra() { 146 | return channelExtra; 147 | } 148 | 149 | public void setChannelExtra(String channelExtra) { 150 | this.channelExtra = channelExtra; 151 | } 152 | 153 | public String getExtParam() { 154 | return extParam; 155 | } 156 | 157 | public void setExtParam(String extParam) { 158 | this.extParam = extParam; 159 | } 160 | 161 | public Byte getDivisionMode() { 162 | return divisionMode; 163 | } 164 | 165 | public void setDivisionMode(Byte divisionMode) { 166 | this.divisionMode = divisionMode; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/APIResource.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | import com.alibaba.fastjson.JSONException; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.jeequan.jeepay.exception.APIException; 6 | import com.jeequan.jeepay.exception.InvalidRequestException; 7 | import com.jeequan.jeepay.exception.JeepayException; 8 | import com.jeequan.jeepay.request.JeepayRequest; 9 | import com.jeequan.jeepay.response.JeepayResponse; 10 | import com.jeequan.jeepay.util.JSONWriter; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.io.UnsupportedEncodingException; 15 | import java.net.URLEncoder; 16 | import java.nio.charset.Charset; 17 | import java.nio.charset.StandardCharsets; 18 | 19 | /** 20 | * API资源抽象类 21 | * @author jmdhappy 22 | * @site https://www.jeepay.vip 23 | * @date 2021-06-08 11:00 24 | */ 25 | public abstract class APIResource { 26 | 27 | private static final Logger _log = LoggerFactory.getLogger(APIResource.class); 28 | 29 | public static final Charset CHARSET = StandardCharsets.UTF_8; 30 | 31 | private static HttpClient httpClient = new HttpURLConnectionClient(); 32 | 33 | protected enum RequestMethod { 34 | GET, 35 | POST, 36 | DELETE, 37 | PUT 38 | } 39 | 40 | public static Class getSelfClass() { 41 | return APIResource.class; 42 | } 43 | 44 | protected static String urlEncode(String str) { 45 | if (str == null) { 46 | return null; 47 | } 48 | 49 | try { 50 | return URLEncoder.encode(str, CHARSET.name()); 51 | } catch (UnsupportedEncodingException e) { 52 | throw new AssertionError("UTF-8 is unknown"); 53 | } 54 | } 55 | 56 | public T execute( 57 | JeepayRequest request, 58 | RequestMethod method, 59 | String url) throws JeepayException { 60 | 61 | String jsonParam = new JSONWriter().write(request.getBizModel(), true); 62 | 63 | JSONObject params = JSONObject.parseObject(jsonParam); 64 | request.getRequestOptions(); 65 | APIJeepayRequest apiJeepayRequest = new APIJeepayRequest(method, url, params, request.getRequestOptions()); 66 | if(_log.isDebugEnabled()) _log.debug("Jeepay_SDK_REQ:url={}, data={}", apiJeepayRequest.getUrl(), JSONObject.toJSONString(apiJeepayRequest.getParams())); 67 | APIJeepayResponse response = httpClient.requestWithRetries(apiJeepayRequest); 68 | int responseCode = response.getResponseCode(); 69 | String responseBody = response.getResponseBody(); 70 | if(_log.isDebugEnabled()) _log.debug("Jeepay_SDK_RES:code={}, body={}", responseCode, responseBody); 71 | if (responseCode != 200) { 72 | handleAPIError(response); 73 | } 74 | 75 | T resource = null; 76 | 77 | try { 78 | resource = JSONObject.parseObject(responseBody, request.getResponseClass()); 79 | } catch (JSONException e) { 80 | raiseMalformedJsonError(responseBody, responseCode); 81 | } 82 | 83 | return resource; 84 | } 85 | 86 | /** 87 | * 错误处理 88 | * @param response 89 | * @throws JeepayException 90 | */ 91 | private static void handleAPIError(APIJeepayResponse response) 92 | throws JeepayException { 93 | 94 | String rBody = response.getResponseBody(); 95 | int rCode = response.getResponseCode(); 96 | JSONObject jsonObject = new JSONObject(); 97 | try { 98 | jsonObject = JSONObject.parseObject(rBody); 99 | 100 | } catch (JSONException e) { 101 | raiseMalformedJsonError(rBody, rCode); 102 | } 103 | 104 | if(rCode == 404) { 105 | throw new InvalidRequestException(jsonObject.getString("status") + ", " 106 | + jsonObject.getString("error") + ", " 107 | + jsonObject.getString("path") 108 | , rCode, null); 109 | } 110 | 111 | } 112 | 113 | private static void raiseMalformedJsonError( 114 | String responseBody, int responseCode) throws APIException { 115 | throw new APIException( 116 | String.format( 117 | "Invalid response object from API: %s. (HTTP response code was %d)", 118 | responseBody, responseCode), 119 | null, 120 | null, 121 | responseCode, 122 | null); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/test/java/com/jeequan/jeepay/RefundOrderTest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import com.jeequan.jeepay.exception.JeepayException; 4 | import com.jeequan.jeepay.model.RefundOrderCreateReqModel; 5 | import com.jeequan.jeepay.model.RefundOrderQueryReqModel; 6 | import com.jeequan.jeepay.request.RefundOrderCreateRequest; 7 | import com.jeequan.jeepay.request.RefundOrderQueryRequest; 8 | import com.jeequan.jeepay.response.RefundOrderCreateResponse; 9 | import com.jeequan.jeepay.response.RefundOrderQueryResponse; 10 | import org.junit.jupiter.api.BeforeAll; 11 | import org.junit.jupiter.api.Test; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.util.Date; 16 | 17 | class RefundOrderTest { 18 | 19 | final static Logger _log = LoggerFactory.getLogger(RefundOrderTest.class); 20 | 21 | @BeforeAll 22 | public static void initApiKey() { 23 | Jeepay.setApiBase(JeepayTestData.getApiBase()); 24 | Jeepay.apiKey = JeepayTestData.getApiKey(); 25 | Jeepay.mchNo = JeepayTestData.getMchNo(); 26 | Jeepay.appId = JeepayTestData.getAppId(); 27 | } 28 | 29 | @Test 30 | public void testRefundOrderCreate() { 31 | // 接口文档:https://docs.jeequan.com/docs/jeepay/refund_api 32 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, Jeepay.getApiBase()); 33 | RefundOrderCreateRequest request = new RefundOrderCreateRequest(); 34 | RefundOrderCreateReqModel model = new RefundOrderCreateReqModel(); 35 | model.setMchNo(Jeepay.mchNo); // 商户号 36 | model.setAppId(jeepayClient.getAppId()); // 应用ID 37 | model.setMchOrderNo(""); // 商户支付单号(与支付订单号二者传一) 38 | model.setPayOrderId("P202106181104177050002"); // 支付订单号(与商户支付单号二者传一) 39 | String refundOrderNo = "mho" + new Date().getTime(); 40 | model.setMchRefundNo(refundOrderNo); // 商户退款单号 41 | model.setRefundAmount(4l); // 退款金额,单位分 42 | model.setCurrency("cny"); // 币种,目前只支持cny 43 | model.setClientIp("192.166.1.132"); // 发起支付请求客户端的IP地址 44 | model.setRefundReason("退款测试"); // 退款原因 45 | model.setNotifyUrl("https://www.jeequan.com"); // 异步通知地址 46 | model.setChannelExtra(""); // 渠道扩展参数 47 | model.setExtParam(""); // 商户扩展参数,回调时原样返回 48 | request.setBizModel(model); 49 | try { 50 | RefundOrderCreateResponse response = jeepayClient.execute(request); 51 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 52 | // 判断退款发起是否成功(并不代表退款成功) 53 | if(response.isSuccess(Jeepay.apiKey)) { 54 | String refundOrderId = response.get().getRefundOrderId(); 55 | _log.info("refundOrderId:{}", refundOrderId); 56 | _log.info("mchRefundNo:{}", response.get().getMchRefundNo()); 57 | }else { 58 | _log.info("下单失败:refundOrderNo={}, msg={}", refundOrderNo, response.getMsg()); 59 | _log.info("通道错误码:{}", response.get().getErrCode()); 60 | _log.info("通道错误信息:{}", response.get().getErrMsg()); 61 | } 62 | } catch (JeepayException e) { 63 | _log.error(e.getMessage()); 64 | } 65 | 66 | } 67 | 68 | @Test 69 | public void testRefundOrderQuery() { 70 | // 接口文档:https://docs.jeequan.com/docs/jeepay/refund_api 71 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, Jeepay.getApiBase()); 72 | RefundOrderQueryRequest request = new RefundOrderQueryRequest(); 73 | RefundOrderQueryReqModel model = new RefundOrderQueryReqModel(); 74 | model.setMchNo(Jeepay.mchNo); // 商户号 75 | model.setAppId(jeepayClient.getAppId()); // 应用ID 76 | model.setRefundOrderId("P202106181105527690009"); // 退款单号 77 | request.setBizModel(model); 78 | try { 79 | RefundOrderQueryResponse response = jeepayClient.execute(request); 80 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 81 | if(response.isSuccess(Jeepay.apiKey)) { 82 | _log.info("订单信息:{}", response); 83 | _log.info("退款状态:{}", response.get().getState()); 84 | _log.info("退款金额:{}", response.get().getRefundAmount()); 85 | } 86 | } catch (JeepayException e) { 87 | e.printStackTrace(); 88 | } 89 | 90 | } 91 | 92 | 93 | 94 | } -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/RefundOrderQueryResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /** 4 | * 退款查单响应实体类 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-18 10:00 8 | */ 9 | public class RefundOrderQueryResModel extends JeepayObject { 10 | 11 | private static final long serialVersionUID = -5184554341263929245L; 12 | 13 | /** 14 | * 退款订单号(支付系统生成订单号) 15 | */ 16 | private String refundOrderId; 17 | 18 | /** 19 | * 支付订单号 20 | */ 21 | private String payOrderId; 22 | 23 | /** 24 | * 商户号 25 | */ 26 | private String mchNo; 27 | 28 | /** 29 | * 应用ID 30 | */ 31 | private String appId; 32 | 33 | /** 34 | * 商户退款单号 35 | */ 36 | private String mchRefundNo; 37 | 38 | /** 39 | * 支付金额,单位分 40 | */ 41 | private Long payAmount; 42 | 43 | /** 44 | * 退款金额,单位分 45 | */ 46 | private Long refundAmount; 47 | 48 | /** 49 | * 三位货币代码,人民币:cny 50 | */ 51 | private String currency; 52 | 53 | /** 54 | * 退款状态 55 | * 0-订单生成 56 | * 1-退款中 57 | * 2-退款成功 58 | * 3-退款失败 59 | * 4-退款关闭 60 | */ 61 | private Byte state; 62 | 63 | /** 64 | * 渠道订单号 65 | */ 66 | private String channelOrderNo; 67 | 68 | /** 69 | * 渠道错误码 70 | */ 71 | private String errCode; 72 | 73 | /** 74 | * 渠道错误描述 75 | */ 76 | private String errMsg; 77 | 78 | /** 79 | * 扩展参数 80 | */ 81 | private String extParam; 82 | 83 | /** 84 | * 订单创建时间,13位时间戳 85 | */ 86 | private Long createdAt; 87 | 88 | /** 89 | * 订单支付成功时间,13位时间戳 90 | */ 91 | private Long successTime; 92 | 93 | public String getRefundOrderId() { 94 | return refundOrderId; 95 | } 96 | 97 | public void setRefundOrderId(String refundOrderId) { 98 | this.refundOrderId = refundOrderId; 99 | } 100 | 101 | public String getPayOrderId() { 102 | return payOrderId; 103 | } 104 | 105 | public void setPayOrderId(String payOrderId) { 106 | this.payOrderId = payOrderId; 107 | } 108 | 109 | public String getMchNo() { 110 | return mchNo; 111 | } 112 | 113 | public void setMchNo(String mchNo) { 114 | this.mchNo = mchNo; 115 | } 116 | 117 | public String getAppId() { 118 | return appId; 119 | } 120 | 121 | public void setAppId(String appId) { 122 | this.appId = appId; 123 | } 124 | 125 | public String getMchRefundNo() { 126 | return mchRefundNo; 127 | } 128 | 129 | public void setMchRefundNo(String mchRefundNo) { 130 | this.mchRefundNo = mchRefundNo; 131 | } 132 | 133 | public Long getPayAmount() { 134 | return payAmount; 135 | } 136 | 137 | public void setPayAmount(Long payAmount) { 138 | this.payAmount = payAmount; 139 | } 140 | 141 | public Long getRefundAmount() { 142 | return refundAmount; 143 | } 144 | 145 | public void setRefundAmount(Long refundAmount) { 146 | this.refundAmount = refundAmount; 147 | } 148 | 149 | public String getCurrency() { 150 | return currency; 151 | } 152 | 153 | public void setCurrency(String currency) { 154 | this.currency = currency; 155 | } 156 | 157 | public Byte getState() { 158 | return state; 159 | } 160 | 161 | public void setState(Byte state) { 162 | this.state = state; 163 | } 164 | 165 | public String getChannelOrderNo() { 166 | return channelOrderNo; 167 | } 168 | 169 | public void setChannelOrderNo(String channelOrderNo) { 170 | this.channelOrderNo = channelOrderNo; 171 | } 172 | 173 | public String getErrCode() { 174 | return errCode; 175 | } 176 | 177 | public void setErrCode(String errCode) { 178 | this.errCode = errCode; 179 | } 180 | 181 | public String getErrMsg() { 182 | return errMsg; 183 | } 184 | 185 | public void setErrMsg(String errMsg) { 186 | this.errMsg = errMsg; 187 | } 188 | 189 | public String getExtParam() { 190 | return extParam; 191 | } 192 | 193 | public void setExtParam(String extParam) { 194 | this.extParam = extParam; 195 | } 196 | 197 | public Long getCreatedAt() { 198 | return createdAt; 199 | } 200 | 201 | public void setCreatedAt(Long createdAt) { 202 | this.createdAt = createdAt; 203 | } 204 | 205 | public Long getSuccessTime() { 206 | return successTime; 207 | } 208 | 209 | public void setSuccessTime(Long successTime) { 210 | this.successTime = successTime; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/JeepayClient.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.jeequan.jeepay.exception.JeepayException; 5 | import com.jeequan.jeepay.net.APIJeepayRequest; 6 | import com.jeequan.jeepay.net.APIResource; 7 | import com.jeequan.jeepay.net.RequestOptions; 8 | import com.jeequan.jeepay.request.JeepayRequest; 9 | import com.jeequan.jeepay.response.JeepayResponse; 10 | import com.jeequan.jeepay.util.JSONWriter; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * Jeepay sdk客户端 16 | * 17 | * @author jmdhappy 18 | * @site https://www.jeepay.vip 19 | * @date 2021-06-08 11:00 20 | */ 21 | public class JeepayClient extends APIResource { 22 | 23 | private static final Map clientMap = new HashMap(); 24 | 25 | private String appId; 26 | private String signType = Jeepay.DEFAULT_SIGN_TYPE; 27 | private String apiKey = Jeepay.apiKey; 28 | private String apiBase = Jeepay.getApiBase(); 29 | 30 | public JeepayClient(String apiBase, String signType, String apiKey) { 31 | this.apiBase = apiBase; 32 | this.signType = signType; 33 | this.apiKey = apiKey; 34 | } 35 | 36 | public JeepayClient(String apiBase, String apiKey) { 37 | this.apiBase = apiBase; 38 | this.apiKey = apiKey; 39 | } 40 | 41 | public JeepayClient(String apiKey) { 42 | this.apiKey = apiKey; 43 | } 44 | 45 | public JeepayClient() { 46 | } 47 | 48 | public static synchronized JeepayClient getInstance(String appId, String apiKey, 49 | String apiBase) { 50 | JeepayClient client = clientMap.get(appId); 51 | if (client != null) { 52 | return client; 53 | } 54 | client = new JeepayClient(); 55 | clientMap.put(appId, client); 56 | client.appId = appId; 57 | client.apiKey = apiKey; 58 | client.apiBase = apiBase; 59 | return client; 60 | } 61 | 62 | public static synchronized JeepayClient getInstance(String appId, String apiKey) { 63 | JeepayClient client = clientMap.get(appId); 64 | if (client != null) { 65 | return client; 66 | } 67 | client = new JeepayClient(); 68 | clientMap.put(appId, client); 69 | client.appId = appId; 70 | client.apiKey = apiKey; 71 | return client; 72 | } 73 | 74 | public static synchronized JeepayClient getInstance(String appId) { 75 | JeepayClient client = clientMap.get(appId); 76 | if (client != null) { 77 | return client; 78 | } 79 | client = new JeepayClient(); 80 | clientMap.put(appId, client); 81 | client.appId = appId; 82 | return client; 83 | } 84 | 85 | public String getAppId() { 86 | return appId; 87 | } 88 | 89 | public String getSignType() { 90 | return signType; 91 | } 92 | 93 | public void setSignType(String signType) { 94 | this.signType = signType; 95 | } 96 | 97 | public String getApiKey() { 98 | return apiKey; 99 | } 100 | 101 | public void setApiKey(String apiKey) { 102 | this.apiKey = apiKey; 103 | } 104 | 105 | public String getApiBase() { 106 | return apiBase; 107 | } 108 | 109 | public void setApiBase(String apiBase) { 110 | this.apiBase = apiBase; 111 | } 112 | 113 | public T execute(JeepayRequest request) throws JeepayException { 114 | 115 | // 支持用户自己设置RequestOptions 116 | if (request.getRequestOptions() == null) { 117 | RequestOptions options = RequestOptions.builder() 118 | .setVersion(request.getApiVersion()) 119 | .setUri(request.getApiUri()) 120 | .setAppId(this.appId) 121 | .setApiKey(this.apiKey) 122 | .build(); 123 | request.setRequestOptions(options); 124 | } 125 | 126 | return execute(request, RequestMethod.POST, this.apiBase); 127 | } 128 | 129 | 130 | public String getRequestUrl(JeepayRequest request) throws JeepayException { 131 | // 支持用户自己设置RequestOptions 132 | if (request.getRequestOptions() == null) { 133 | RequestOptions options = RequestOptions.builder() 134 | .setVersion(request.getApiVersion()) 135 | .setUri(request.getApiUri()) 136 | .setAppId(this.appId) 137 | .setApiKey(this.apiKey) 138 | .build(); 139 | request.setRequestOptions(options); 140 | } 141 | String jsonParam = new JSONWriter().write(request.getBizModel(), true); 142 | 143 | JSONObject params = JSONObject.parseObject(jsonParam); 144 | request.getRequestOptions(); 145 | 146 | return APIJeepayRequest.buildURLWithSign(this.apiBase, params, request.getRequestOptions()).toString(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/PayOrderQueryResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /** 4 | * 支付查单响应实体类 5 | * @author jmdhappy 6 | * @site https://www.jeepay.vip 7 | * @date 2021-06-08 11:00 8 | */ 9 | public class PayOrderQueryResModel extends JeepayObject { 10 | 11 | /** 12 | * 支付订单号 13 | */ 14 | private String payOrderId; 15 | 16 | /** 17 | * 商户号 18 | */ 19 | private String mchNo; 20 | 21 | /** 22 | * 商户订单号 23 | */ 24 | private String mchOrderNo; 25 | 26 | /** 27 | * 支付接口 28 | */ 29 | private String ifCode; 30 | 31 | /** 32 | * 支付方式 33 | */ 34 | private String wayCode; 35 | 36 | /** 37 | * 支付金额,单位分 38 | */ 39 | private Long amount; 40 | 41 | /** 42 | * 三位货币代码,人民币:cny 43 | */ 44 | private String currency; 45 | 46 | /** 47 | * 支付订单状态 48 | * 0-订单生成 49 | * 1-支付中 50 | * 2-支付成功 51 | * 3-支付失败 52 | * 4-已撤销 53 | * 5-已退款 54 | * 6-订单关闭 55 | */ 56 | private int state; 57 | 58 | /** 59 | * 客户端IPV4地址 60 | */ 61 | private String clientIp; 62 | 63 | /** 64 | * 商品标题 65 | */ 66 | private String subject; 67 | 68 | /** 69 | * 商品描述 70 | */ 71 | private String body; 72 | 73 | /** 74 | * 渠道订单号 75 | */ 76 | private String channelOrderNo; 77 | 78 | /** 79 | * 渠道错误码 80 | */ 81 | private String errCode; 82 | 83 | /** 84 | * 渠道错误描述 85 | */ 86 | private String errMsg; 87 | 88 | /** 89 | * 扩展参数 90 | */ 91 | private String extParam; 92 | 93 | /** 94 | * 订单创建时间,13位时间戳 95 | */ 96 | private Long createdAt; 97 | 98 | /** 99 | * 订单支付成功时间,13位时间戳 100 | */ 101 | private Long successTime; 102 | 103 | public String getPayOrderId() { 104 | return payOrderId; 105 | } 106 | 107 | public void setPayOrderId(String payOrderId) { 108 | this.payOrderId = payOrderId; 109 | } 110 | 111 | public String getMchNo() { 112 | return mchNo; 113 | } 114 | 115 | public void setMchNo(String mchNo) { 116 | this.mchNo = mchNo; 117 | } 118 | 119 | public String getMchOrderNo() { 120 | return mchOrderNo; 121 | } 122 | 123 | public void setMchOrderNo(String mchOrderNo) { 124 | this.mchOrderNo = mchOrderNo; 125 | } 126 | 127 | public String getIfCode() { 128 | return ifCode; 129 | } 130 | 131 | public void setIfCode(String ifCode) { 132 | this.ifCode = ifCode; 133 | } 134 | 135 | public String getWayCode() { 136 | return wayCode; 137 | } 138 | 139 | public void setWayCode(String wayCode) { 140 | this.wayCode = wayCode; 141 | } 142 | 143 | public Long getAmount() { 144 | return amount; 145 | } 146 | 147 | public void setAmount(Long amount) { 148 | this.amount = amount; 149 | } 150 | 151 | public String getCurrency() { 152 | return currency; 153 | } 154 | 155 | public void setCurrency(String currency) { 156 | this.currency = currency; 157 | } 158 | 159 | public int getState() { 160 | return state; 161 | } 162 | 163 | public void setState(int state) { 164 | this.state = state; 165 | } 166 | 167 | public String getClientIp() { 168 | return clientIp; 169 | } 170 | 171 | public void setClientIp(String clientIp) { 172 | this.clientIp = clientIp; 173 | } 174 | 175 | public String getSubject() { 176 | return subject; 177 | } 178 | 179 | public void setSubject(String subject) { 180 | this.subject = subject; 181 | } 182 | 183 | public String getBody() { 184 | return body; 185 | } 186 | 187 | public void setBody(String body) { 188 | this.body = body; 189 | } 190 | 191 | public String getChannelOrderNo() { 192 | return channelOrderNo; 193 | } 194 | 195 | public void setChannelOrderNo(String channelOrderNo) { 196 | this.channelOrderNo = channelOrderNo; 197 | } 198 | 199 | public String getErrCode() { 200 | return errCode; 201 | } 202 | 203 | public void setErrCode(String errCode) { 204 | this.errCode = errCode; 205 | } 206 | 207 | public String getErrMsg() { 208 | return errMsg; 209 | } 210 | 211 | public void setErrMsg(String errMsg) { 212 | this.errMsg = errMsg; 213 | } 214 | 215 | public String getExtParam() { 216 | return extParam; 217 | } 218 | 219 | public void setExtParam(String extParam) { 220 | this.extParam = extParam; 221 | } 222 | 223 | public Long getCreatedAt() { 224 | return createdAt; 225 | } 226 | 227 | public void setCreatedAt(Long createdAt) { 228 | this.createdAt = createdAt; 229 | } 230 | 231 | public Long getSuccessTime() { 232 | return successTime; 233 | } 234 | 235 | public void setSuccessTime(Long successTime) { 236 | this.successTime = successTime; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/HttpClient.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import com.jeequan.jeepay.exception.APIConnectionException; 6 | import com.jeequan.jeepay.exception.JeepayException; 7 | 8 | import java.net.ConnectException; 9 | import java.net.URL; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.concurrent.ThreadLocalRandom; 13 | 14 | /** 15 | * Http请求客户端 16 | * @author jmdhappy 17 | * @site https://www.jeepay.vip 18 | * @date 2021-06-08 11:00 19 | */ 20 | public abstract class HttpClient { 21 | 22 | /** 23 | * 网络故障后尝试发送HTTP请求之间的最大延迟时间 24 | */ 25 | public static final long maxNetworkRetriesDelay = 5000; 26 | 27 | /** 28 | * 网络故障后尝试发送HTTP请求之间的最小延迟时间 29 | */ 30 | public static final long minNetworkRetriesDelay = 500; 31 | 32 | /** 33 | * 是否网络重试休眠 34 | */ 35 | boolean networkRetriesSleep = true; 36 | 37 | public HttpClient() {} 38 | 39 | /** 40 | * 发送http请求 41 | * @param request 42 | * @return 43 | * @throws JeepayException 44 | */ 45 | public abstract APIJeepayResponse request(APIJeepayRequest request) throws JeepayException; 46 | 47 | /** 48 | * 发送请求到Jeepay的API(支持重试) 49 | * @param request 50 | * @return 51 | * @throws JeepayException 52 | */ 53 | public APIJeepayResponse requestWithRetries(APIJeepayRequest request) throws JeepayException { 54 | APIConnectionException requestException = null; 55 | APIJeepayResponse response = null; 56 | int retry = 0; 57 | 58 | while (true) { 59 | requestException = null; 60 | 61 | try { 62 | response = this.request(request); 63 | } catch (APIConnectionException e) { 64 | requestException = e; 65 | } 66 | 67 | if (!this.shouldRetry(retry, requestException, request, response)) { 68 | break; 69 | } 70 | 71 | retry += 1; 72 | 73 | try { 74 | Thread.sleep(this.sleepTime(retry)); 75 | } catch (InterruptedException e) { 76 | Thread.currentThread().interrupt(); 77 | } 78 | } 79 | 80 | if (requestException != null) { 81 | throw requestException; 82 | } 83 | 84 | response.setNumRetries(retry); 85 | 86 | return response; 87 | } 88 | 89 | protected static String buildUserAgentString(String version) { 90 | return String.format("Jeepay/v1 JavaBindings/%s", version); 91 | } 92 | 93 | protected static String buildXJeepayClientUserAgentString(String version) { 94 | String[] propertyNames = { 95 | "os.name", 96 | "os.version", 97 | "os.arch", 98 | "java.version", 99 | "java.vendor", 100 | "java.vm.version", 101 | "java.vm.vendor" 102 | }; 103 | 104 | Map propertyMap = new HashMap<>(); 105 | for (String propertyName : propertyNames) { 106 | propertyMap.put(propertyName, System.getProperty(propertyName)); 107 | } 108 | propertyMap.put("bindings.version", version); 109 | propertyMap.put("lang", "Java"); 110 | propertyMap.put("publisher", "Jeepay"); 111 | return JSON.toJSONString(propertyMap); 112 | } 113 | 114 | private boolean shouldRetry( 115 | int numRetries, JeepayException exception, APIJeepayRequest request, APIJeepayResponse response) { 116 | // Do not retry if we are out of retries. 117 | if (numRetries >= request.options.getMaxNetworkRetries()) { 118 | return false; 119 | } 120 | 121 | // Retry on connection error. 122 | if ((exception != null) 123 | && (exception.getCause() != null) 124 | && (exception.getCause() instanceof ConnectException)) { 125 | return true; 126 | } 127 | 128 | // Retry on 500, 503, and other internal errors. 129 | if ((response != null) && (response.getResponseCode() >= 500)) { 130 | return true; 131 | } 132 | 133 | return false; 134 | } 135 | 136 | private long sleepTime(int numRetries) { 137 | if (!networkRetriesSleep) { 138 | return 0; 139 | } 140 | 141 | long delay = (long) (minNetworkRetriesDelay * Math.pow(2, numRetries - 1)); 142 | 143 | if (delay > maxNetworkRetriesDelay) { 144 | delay = maxNetworkRetriesDelay; 145 | } 146 | 147 | double jitter = ThreadLocalRandom.current().nextDouble(0.75, 1.0); 148 | delay = (long) (delay * jitter); 149 | 150 | if (delay < minNetworkRetriesDelay) { 151 | delay = minNetworkRetriesDelay; 152 | } 153 | 154 | return delay; 155 | } 156 | 157 | private static String getRequestURIFromURL(URL url) { 158 | String path = url.getPath(); 159 | String query = url.getQuery(); 160 | if (query == null) { 161 | return path; 162 | } 163 | return path + "?" + query; 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/test/java/com/jeequan/jeepay/TransferOrderTest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import com.jeequan.jeepay.exception.JeepayException; 4 | import com.jeequan.jeepay.model.ChannelUserReqModel; 5 | import com.jeequan.jeepay.model.TransferOrderCreateReqModel; 6 | import com.jeequan.jeepay.model.TransferOrderQueryReqModel; 7 | import com.jeequan.jeepay.request.ChannelUserRequest; 8 | import com.jeequan.jeepay.request.TransferOrderCreateRequest; 9 | import com.jeequan.jeepay.request.TransferOrderQueryRequest; 10 | import com.jeequan.jeepay.response.TransferOrderCreateResponse; 11 | import com.jeequan.jeepay.response.TransferOrderQueryResponse; 12 | import org.junit.jupiter.api.BeforeAll; 13 | import org.junit.jupiter.api.Test; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | import java.util.Date; 18 | 19 | class TransferOrderTest { 20 | 21 | final static Logger _log = LoggerFactory.getLogger(TransferOrderTest.class); 22 | 23 | @BeforeAll 24 | public static void initApiKey() { 25 | Jeepay.setApiBase(JeepayTestData.getApiBase()); 26 | Jeepay.apiKey = JeepayTestData.getApiKey(); 27 | Jeepay.mchNo = JeepayTestData.getMchNo(); 28 | Jeepay.appId = JeepayTestData.getAppId(); 29 | } 30 | 31 | @Test 32 | public void testTransferOrderCreate() { 33 | // 转账接口文档:https://docs.jeequan.com/docs/jeepay/transfer_api 34 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, 35 | Jeepay.getApiBase()); 36 | TransferOrderCreateRequest request = new TransferOrderCreateRequest(); 37 | TransferOrderCreateReqModel model = new TransferOrderCreateReqModel(); 38 | model.setMchNo(Jeepay.mchNo); // 商户号 39 | model.setAppId(jeepayClient.getAppId()); // 应用ID 40 | model.setMchOrderNo("mho" + new Date().getTime()); // 商户转账单号 41 | model.setAmount(1L); 42 | model.setCurrency("CNY"); 43 | model.setIfCode("wxpay"); 44 | model.setEntryType("WX_CASH"); 45 | model.setAccountNo("a6BcIwtTvIqv1zXZohc61biryWok"); 46 | model.setAccountName(""); 47 | model.setTransferDesc("测试转账"); 48 | model.setClientIp("192.166.1.132"); // 发起转账请求客户端的IP地址 49 | request.setBizModel(model); 50 | 51 | try { 52 | TransferOrderCreateResponse response = jeepayClient.execute(request); 53 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 54 | // 判断转账发起是否成功(并不代表转账成功) 55 | if (response.isSuccess(Jeepay.apiKey)) { 56 | String transferId = response.get().getTransferId(); 57 | _log.info("transferId:{}", transferId); 58 | _log.info("mchOrderNo:{}", response.get().getMchOrderNo()); 59 | } else { 60 | _log.info("下单失败:mchOrderNo={}, msg={}", model.getMchOrderNo(), response.getMsg()); 61 | _log.info("通道错误码:{}", response.get().getErrCode()); 62 | _log.info("通道错误信息:{}", response.get().getErrMsg()); 63 | } 64 | } catch (JeepayException e) { 65 | _log.error(e.getMessage()); 66 | } 67 | 68 | } 69 | 70 | @Test 71 | public void testTransferOrderQuery() { 72 | // 转账接口文档:https://docs.jeequan.com/docs/jeepay/transfer_api 73 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, 74 | Jeepay.getApiBase()); 75 | TransferOrderQueryRequest request = new TransferOrderQueryRequest(); 76 | TransferOrderQueryReqModel model = new TransferOrderQueryReqModel(); 77 | model.setMchNo(Jeepay.mchNo); // 商户号 78 | model.setAppId(jeepayClient.getAppId()); // 应用ID 79 | model.setTransferId("T202108121543441860003"); // 转账单号 80 | request.setBizModel(model); 81 | try { 82 | TransferOrderQueryResponse response = jeepayClient.execute(request); 83 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 84 | if (response.isSuccess(Jeepay.apiKey)) { 85 | _log.info("订单信息:{}", response); 86 | _log.info("转账状态:{}", response.get().getState()); 87 | _log.info("转账金额:{}", response.get().getAmount()); 88 | } 89 | } catch (JeepayException e) { 90 | e.printStackTrace(); 91 | } 92 | 93 | } 94 | 95 | 96 | @Test 97 | public void getChannelUserIdUrl() { 98 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, 99 | Jeepay.getApiBase()); 100 | ChannelUserRequest request = new ChannelUserRequest(); 101 | ChannelUserReqModel model = new ChannelUserReqModel(); 102 | model.setAppId(jeepayClient.getAppId()); 103 | model.setMchNo(Jeepay.mchNo); 104 | model.setRedirectUrl("https://httpdump.io/30cbe"); 105 | model.setIfCode("AUTO"); 106 | request.setBizModel(model); 107 | 108 | try { 109 | String url = jeepayClient.getRequestUrl(request); 110 | _log.info("跳转 URL: {}", url); 111 | } catch (JeepayException e) { 112 | e.printStackTrace(); 113 | } 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/DivisionReceiverBindResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | import java.math.BigDecimal; 4 | 5 | /*** 6 | * 分账账号的响应结果 7 | * 8 | * @author terrfly 9 | * @site https://www.jeepay.vip 10 | * @date 2021/8/25 10:38 11 | */ 12 | public class DivisionReceiverBindResModel extends JeepayObject { 13 | 14 | /** 15 | * 分账接收者ID 16 | */ 17 | private Long receiverId; 18 | 19 | /** 20 | * 接收者账号别名 21 | */ 22 | private String receiverAlias; 23 | 24 | /** 25 | * 组ID(便于商户接口使用) 26 | */ 27 | private Long receiverGroupId; 28 | 29 | /** 30 | * 商户号 31 | */ 32 | private String mchNo; 33 | 34 | /** 35 | * 应用ID 36 | */ 37 | private String appId; 38 | 39 | /** 40 | * 支付接口代码 41 | */ 42 | private String ifCode; 43 | 44 | /** 45 | * 分账接收账号类型: 0-个人(对私) 1-商户(对公) 46 | */ 47 | private Byte accType; 48 | 49 | /** 50 | * 分账接收账号 51 | */ 52 | private String accNo; 53 | 54 | /** 55 | * 分账接收账号名称 56 | */ 57 | private String accName; 58 | 59 | /** 60 | * 分账关系类型(参考微信), 如: SERVICE_PROVIDER 服务商等 61 | */ 62 | private String relationType; 63 | 64 | /** 65 | * 当选择自定义时,需要录入该字段。 否则为对应的名称 66 | */ 67 | private String relationTypeName; 68 | 69 | 70 | /** 71 | * 渠道特殊信息 72 | */ 73 | private String channelExtInfo; 74 | 75 | /** 76 | * 绑定成功时间 77 | */ 78 | private Long bindSuccessTime; 79 | 80 | /** 81 | * 分账比例 82 | */ 83 | private BigDecimal divisionProfit; 84 | 85 | /** 86 | * 分账状态 1-绑定成功, 0-绑定异常 87 | */ 88 | private Byte bindState; 89 | 90 | /** 91 | * 支付渠道错误码 92 | */ 93 | private String errCode; 94 | 95 | /** 96 | * 支付渠道错误信息 97 | */ 98 | private String errMsg; 99 | 100 | public Long getReceiverId() { 101 | return receiverId; 102 | } 103 | 104 | public void setReceiverId(Long receiverId) { 105 | this.receiverId = receiverId; 106 | } 107 | 108 | public String getReceiverAlias() { 109 | return receiverAlias; 110 | } 111 | 112 | public void setReceiverAlias(String receiverAlias) { 113 | this.receiverAlias = receiverAlias; 114 | } 115 | 116 | public Long getReceiverGroupId() { 117 | return receiverGroupId; 118 | } 119 | 120 | public void setReceiverGroupId(Long receiverGroupId) { 121 | this.receiverGroupId = receiverGroupId; 122 | } 123 | 124 | public String getMchNo() { 125 | return mchNo; 126 | } 127 | 128 | public void setMchNo(String mchNo) { 129 | this.mchNo = mchNo; 130 | } 131 | 132 | public String getAppId() { 133 | return appId; 134 | } 135 | 136 | public void setAppId(String appId) { 137 | this.appId = appId; 138 | } 139 | 140 | public String getIfCode() { 141 | return ifCode; 142 | } 143 | 144 | public void setIfCode(String ifCode) { 145 | this.ifCode = ifCode; 146 | } 147 | 148 | public Byte getAccType() { 149 | return accType; 150 | } 151 | 152 | public void setAccType(Byte accType) { 153 | this.accType = accType; 154 | } 155 | 156 | public String getAccNo() { 157 | return accNo; 158 | } 159 | 160 | public void setAccNo(String accNo) { 161 | this.accNo = accNo; 162 | } 163 | 164 | public String getAccName() { 165 | return accName; 166 | } 167 | 168 | public void setAccName(String accName) { 169 | this.accName = accName; 170 | } 171 | 172 | public String getRelationType() { 173 | return relationType; 174 | } 175 | 176 | public void setRelationType(String relationType) { 177 | this.relationType = relationType; 178 | } 179 | 180 | public String getRelationTypeName() { 181 | return relationTypeName; 182 | } 183 | 184 | public void setRelationTypeName(String relationTypeName) { 185 | this.relationTypeName = relationTypeName; 186 | } 187 | 188 | public String getChannelExtInfo() { 189 | return channelExtInfo; 190 | } 191 | 192 | public void setChannelExtInfo(String channelExtInfo) { 193 | this.channelExtInfo = channelExtInfo; 194 | } 195 | 196 | public Long getBindSuccessTime() { 197 | return bindSuccessTime; 198 | } 199 | 200 | public void setBindSuccessTime(Long bindSuccessTime) { 201 | this.bindSuccessTime = bindSuccessTime; 202 | } 203 | 204 | public BigDecimal getDivisionProfit() { 205 | return divisionProfit; 206 | } 207 | 208 | public void setDivisionProfit(BigDecimal divisionProfit) { 209 | this.divisionProfit = divisionProfit; 210 | } 211 | 212 | public Byte getBindState() { 213 | return bindState; 214 | } 215 | 216 | public void setBindState(Byte bindState) { 217 | this.bindState = bindState; 218 | } 219 | 220 | public String getErrCode() { 221 | return errCode; 222 | } 223 | 224 | public void setErrCode(String errCode) { 225 | this.errCode = errCode; 226 | } 227 | 228 | public String getErrMsg() { 229 | return errMsg; 230 | } 231 | 232 | public void setErrMsg(String errMsg) { 233 | this.errMsg = errMsg; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jeepay-sdk-java 2 | 3 | ## 接口文档 4 | 5 | 接口文档:[https://doc.jeequan.com/#/integrate/open/api](https://doc.jeequan.com/#/integrate/open/api "Jeepay接口文档") 6 | 7 | ## 快速开始 8 | 9 | 引入sdk依赖(最新发布版本1.6.1),支持:支付、退款、转账、分账等接口。 10 | 11 | ```xml 12 | 13 | com.jeequan 14 | jeepay-sdk-java 15 | 1.6.1 16 | 17 | ``` 18 | 19 | 客户端调用代码可参考: 20 | 21 | 完整支付测试代码 `com.jeequan.jeepay.PayOrderTest` 22 | 23 | ```java 24 | // 创建客户端 25 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey); 26 | 27 | // 构建请求数据 28 | String wayCode = "WX_BAR"; // 支付方式 29 | PayOrderCreateRequest request = new PayOrderCreateRequest(); 30 | PayOrderCreateReqModel model = new PayOrderCreateReqModel(); 31 | model.setMchNo(Jeepay.mchNo); // 商户号 32 | model.setAppId(jeepayClient.getAppId()); // 应用ID 33 | String orderNo = "mho" + new Date().getTime(); 34 | model.setMchOrderNo(orderNo); // 商户订单号 35 | model.setWayCode(wayCode); // 支付方式 36 | model.setAmount(1l); // 金额,单位分 37 | model.setCurrency("CNY"); // 币种,目前只支持cny 38 | model.setClientIp("192.166.1.132"); // 发起支付请求客户端的IP地址 39 | model.setSubject("商品标题"); // 商品标题 40 | model.setBody("商品描述"); // 商品描述 41 | model.setNotifyUrl("https://www.jeequan.com"); // 异步通知地址 42 | model.setReturnUrl(""); // 前端跳转地址 43 | model.setChannelExtra(channelExtra(wayCode)); // 渠道扩展参数 44 | model.setExtParam(""); // 商户扩展参数,回调时原样返回 45 | request.setBizModel(model); 46 | 47 | // 发起统一下单 48 | PayOrderCreateResponse response = jeepayClient.execute(request); 49 | 50 | // 验证返回数据签名 51 | response.checkSign(Jeepay.apiKey); 52 | 53 | // 判断下单是否返回成功 54 | response.isSuccess(Jeepay.apiKey) 55 | ``` 56 | 57 | 完整退款测试代码 `com.jeequan.jeepay.RefundOrderTest` 58 | 59 | ```java 60 | // 创建客户端 61 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey); 62 | 63 | // 构建请求数据 64 | RefundOrderCreateRequest request = new RefundOrderCreateRequest(); 65 | RefundOrderCreateReqModel model = new RefundOrderCreateReqModel(); 66 | model.setMchNo(Jeepay.mchNo); // 商户号 67 | model.setAppId(Jeepay.appId); // 应用ID 68 | model.setMchOrderNo(""); // 商户支付单号(与支付订单号二者传一) 69 | model.setPayOrderId("P202106181104177050002"); // 支付订单号(与商户支付单号二者传一) 70 | String refundOrderNo = "mho" + new Date().getTime(); 71 | model.setMchRefundNo(refundOrderNo); // 商户退款单号 72 | model.setRefundAmount(4l); // 退款金额,单位分 73 | model.setCurrency("cny"); // 币种,目前只支持cny 74 | model.setClientIp("192.166.1.132"); // 发起支付请求客户端的 IP 地址,格式为 IPV4,如: 127.0.0.1 75 | model.setRefundReason("退款测试"); // 退款原因 76 | model.setNotifyUrl("https://www.jeequan.com"); // 异步通知地址 77 | model.setChannelExtra(""); // 渠道扩展参数 78 | model.setExtParam(""); // 商户扩展参数,回调时原样返回 79 | request.setBizModel(model); 80 | 81 | // 发起统一退款 82 | RefundOrderCreateResponse response = jeepayClient.execute(request); 83 | 84 | // 验证返回数据签名 85 | response.checkSign(Jeepay.apiKey); 86 | 87 | // 判断退款发起是否成功(并不代表退款成功)退款状态 0-订单生成 1-退款中 2-退款成功 3-退款失败 4-退款关闭 88 | // 如果 response.get().getState()==2 表示退款成功 89 | response.isSuccess(Jeepay.apiKey) 90 | ``` 91 | 92 | 完整转账测试代码 `com.jeequan.jeepay.TransferOrderTest` 93 | 94 | ```java 95 | // 创建客户端 96 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey); 97 | TransferOrderCreateRequest request = new TransferOrderCreateRequest(); 98 | TransferOrderCreateReqModel model = new TransferOrderCreateReqModel(); 99 | model.setMchNo(Jeepay.mchNo); // 商户号 100 | model.setAppId(Jeepay.appId); // 应用ID 101 | model.setMchOrderNo("mho" + new Date().getTime()); // 商户转账单号 102 | model.setAmount(1L); 103 | model.setCurrency("CNY"); 104 | model.setIfCode("wxpay"); 105 | model.setEntryType("WX_CASH"); 106 | model.setAccountNo("a6BcIwtTvIqv1zXZohc61biryWok"); 107 | model.setAccountName(""); 108 | model.setTransferDesc("测试转账"); 109 | model.setClientIp("192.166.1.132"); // 发起转账请求客户端的IP地址 110 | request.setBizModel(model); 111 | try { 112 | TransferOrderCreateResponse response = jeepayClient.execute(request); 113 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 114 | // 判断转账发起是否成功(并不代表转账成功) 115 | if(response.isSuccess(Jeepay.apiKey)) { 116 | String transferId = response.get().getTransferId(); 117 | _log.info("transferId:{}", transferId); 118 | _log.info("mchOrderNo:{}", response.get().getMchOrderNo()); 119 | }else { 120 | _log.info("下单失败:mchOrderNo={}, msg={}", model.getMchOrderNo(), response.getMsg()); 121 | _log.info("通道错误码:{}", response.get().getErrCode()); 122 | _log.info("通道错误信息:{}", response.get().getErrMsg()); 123 | } 124 | } catch (JeepayException e) { 125 | _log.error(e.getMessage()); 126 | } 127 | ``` 128 | 129 | ## 其他相关 130 | 131 | Jeepay是一套适合互联网企业使用的开源支付系统,支持多渠道服务商和普通商户模式。已对接`微信支付`,`支付宝`,`云闪付`官方接口,支持聚合码支付。 132 | 133 | Jeepay使用`Spring Boot`和`Ant Design Vue`开发,集成`Spring Security`实现权限管理功能,是一套非常实用的web开发框架。 134 | 135 | - Jeepay支付流程体验:[https://www.jeequan.com/demo/jeepay_cashier.html](https://www.jeequan.com/demo/jeepay_cashier.html "Jeepay支付体验") 136 | - Jeepay运营平台和商户系统演体验:[https://www.jeequan.com/doc/detail_84.html](https://www.jeequan.com/doc/detail_84.html "Jeepay支付系统体验") 137 | - Jeepay项目文档:[https://doc.jeequan.com/#/integrate/open](https://doc.jeequan.com/#/integrate/open "Jeepay项目文档") 138 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/model/TransferOrderQueryResModel.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.model; 2 | 3 | /*** 4 | * 转账查单响应实体类 5 | * 6 | * @author terrfly 7 | * @site https://www.jeepay.vip 8 | * @date 2021/8/16 16:08 9 | */ 10 | public class TransferOrderQueryResModel extends JeepayObject { 11 | 12 | 13 | /** 14 | * 转账订单号 15 | */ 16 | private String transferId; 17 | 18 | /** 19 | * 商户号 20 | */ 21 | private String mchNo; 22 | 23 | /** 24 | * 应用ID 25 | */ 26 | private String appId; 27 | 28 | /** 29 | * 商户订单号 30 | */ 31 | private String mchOrderNo; 32 | 33 | /** 34 | * 支付接口代码 35 | */ 36 | private String ifCode; 37 | 38 | /** 39 | * 入账方式: WX_CASH-微信零钱; ALIPAY_CASH-支付宝转账; BANK_CARD-银行卡 40 | */ 41 | private String entryType; 42 | 43 | /** 44 | * 转账金额,单位分 45 | */ 46 | private Long amount; 47 | 48 | /** 49 | * 三位货币代码,人民币:cny 50 | */ 51 | private String currency; 52 | 53 | /** 54 | * 收款账号 55 | */ 56 | private String accountNo; 57 | 58 | /** 59 | * 收款人姓名 60 | */ 61 | private String accountName; 62 | 63 | /** 64 | * 收款人开户行名称 65 | */ 66 | private String bankName; 67 | 68 | /** 69 | * 转账备注信息 70 | */ 71 | private String transferDesc; 72 | 73 | /** 74 | * 支付状态: 0-订单生成, 1-转账中, 2-转账成功, 3-转账失败, 4-订单关闭 75 | */ 76 | private Byte state; 77 | 78 | /** 79 | * 特定渠道发起额外参数 80 | */ 81 | private String channelExtra; 82 | 83 | /** 84 | * 渠道订单号 85 | */ 86 | private String channelOrderNo; 87 | 88 | /** 89 | * 渠道响应数据(如微信确认数据包) 90 | */ 91 | private String channelResData; 92 | 93 | /** 94 | * 渠道支付错误码 95 | */ 96 | private String errCode; 97 | 98 | /** 99 | * 渠道支付错误描述 100 | */ 101 | private String errMsg; 102 | 103 | /** 104 | * 商户扩展参数 105 | */ 106 | private String extParam; 107 | 108 | /** 109 | * 转账成功时间 110 | */ 111 | private Long successTime; 112 | 113 | /** 114 | * 创建时间 115 | */ 116 | private Long createdAt; 117 | 118 | public String getTransferId() { 119 | return transferId; 120 | } 121 | 122 | public void setTransferId(String transferId) { 123 | this.transferId = transferId; 124 | } 125 | 126 | public String getMchNo() { 127 | return mchNo; 128 | } 129 | 130 | public void setMchNo(String mchNo) { 131 | this.mchNo = mchNo; 132 | } 133 | 134 | public String getAppId() { 135 | return appId; 136 | } 137 | 138 | public void setAppId(String appId) { 139 | this.appId = appId; 140 | } 141 | 142 | public String getMchOrderNo() { 143 | return mchOrderNo; 144 | } 145 | 146 | public void setMchOrderNo(String mchOrderNo) { 147 | this.mchOrderNo = mchOrderNo; 148 | } 149 | 150 | public String getIfCode() { 151 | return ifCode; 152 | } 153 | 154 | public void setIfCode(String ifCode) { 155 | this.ifCode = ifCode; 156 | } 157 | 158 | public String getEntryType() { 159 | return entryType; 160 | } 161 | 162 | public void setEntryType(String entryType) { 163 | this.entryType = entryType; 164 | } 165 | 166 | public Long getAmount() { 167 | return amount; 168 | } 169 | 170 | public void setAmount(Long amount) { 171 | this.amount = amount; 172 | } 173 | 174 | public String getCurrency() { 175 | return currency; 176 | } 177 | 178 | public void setCurrency(String currency) { 179 | this.currency = currency; 180 | } 181 | 182 | public String getAccountNo() { 183 | return accountNo; 184 | } 185 | 186 | public void setAccountNo(String accountNo) { 187 | this.accountNo = accountNo; 188 | } 189 | 190 | public String getAccountName() { 191 | return accountName; 192 | } 193 | 194 | public void setAccountName(String accountName) { 195 | this.accountName = accountName; 196 | } 197 | 198 | public String getBankName() { 199 | return bankName; 200 | } 201 | 202 | public void setBankName(String bankName) { 203 | this.bankName = bankName; 204 | } 205 | 206 | public String getTransferDesc() { 207 | return transferDesc; 208 | } 209 | 210 | public void setTransferDesc(String transferDesc) { 211 | this.transferDesc = transferDesc; 212 | } 213 | 214 | public Byte getState() { 215 | return state; 216 | } 217 | 218 | public void setState(Byte state) { 219 | this.state = state; 220 | } 221 | 222 | public String getChannelExtra() { 223 | return channelExtra; 224 | } 225 | 226 | public void setChannelExtra(String channelExtra) { 227 | this.channelExtra = channelExtra; 228 | } 229 | 230 | public String getChannelOrderNo() { 231 | return channelOrderNo; 232 | } 233 | 234 | public void setChannelOrderNo(String channelOrderNo) { 235 | this.channelOrderNo = channelOrderNo; 236 | } 237 | 238 | public String getChannelResData() { 239 | return channelResData; 240 | } 241 | 242 | public void setChannelResData(String channelResData) { 243 | this.channelResData = channelResData; 244 | } 245 | 246 | public String getErrCode() { 247 | return errCode; 248 | } 249 | 250 | public void setErrCode(String errCode) { 251 | this.errCode = errCode; 252 | } 253 | 254 | public String getErrMsg() { 255 | return errMsg; 256 | } 257 | 258 | public void setErrMsg(String errMsg) { 259 | this.errMsg = errMsg; 260 | } 261 | 262 | public String getExtParam() { 263 | return extParam; 264 | } 265 | 266 | public void setExtParam(String extParam) { 267 | this.extParam = extParam; 268 | } 269 | 270 | public Long getSuccessTime() { 271 | return successTime; 272 | } 273 | 274 | public void setSuccessTime(Long successTime) { 275 | this.successTime = successTime; 276 | } 277 | 278 | public Long getCreatedAt() { 279 | return createdAt; 280 | } 281 | 282 | public void setCreatedAt(Long createdAt) { 283 | this.createdAt = createdAt; 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.jeequan 8 | jeepay-sdk-java 9 | 1.6.1 10 | jar 11 | 12 | ${project.groupId}:${project.artifactId} 13 | jeepay java sdk 14 | https://github.com/jeequan/jeepay-sdk-java 15 | 16 | 17 | 18 | The MIT License 19 | https://github.com/jeequan/jeepay-sdk-java/blob/main/LICENSE 20 | 21 | 22 | 23 | 24 | https://github.com/jeequan/jeepay-sdk-java.git 25 | https://github.com/jeequan 26 | https://github.com/jeequan/jeepay-sdk-java 27 | 28 | 29 | 30 | 31 | jeequan 32 | jmdhappy@126.com 33 | https://github.com/jeequan/jeepay-sdk-java 34 | jeequan 35 | https://www.jeequan.com 36 | 37 | 38 | 39 | 40 | 8 41 | 8 42 | 1.2.7 43 | 1.2.78 44 | 45 | 46 | 47 | 48 | com.alibaba 49 | fastjson 50 | ${fastjson.version} 51 | 52 | 53 | ch.qos.logback 54 | logback-classic 55 | ${logback-classic.version} 56 | 57 | 58 | org.junit.jupiter 59 | junit-jupiter-api 60 | 5.8.0-M1 61 | test 62 | 63 | 64 | 65 | 66 | 67 | release 68 | 69 | 70 | 71 | 72 | org.sonatype.central 73 | central-publishing-maven-plugin 74 | 0.7.0 75 | true 76 | 77 | central 78 | true 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-release-plugin 84 | 2.5 85 | 86 | true 87 | false 88 | release 89 | deploy 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-compiler-plugin 95 | 3.3 96 | 97 | 1.8 98 | 1.8 99 | 100 | 101 | 102 | org.apache.maven.plugins 103 | maven-gpg-plugin 104 | 1.6 105 | 106 | /usr/local/bin/gpg 107 | jeequan 108 | 109 | 110 | 111 | sign-artifacts 112 | verify 113 | 114 | sign 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-source-plugin 122 | 3.0.1 123 | 124 | 125 | attach-sources 126 | 127 | jar-no-fork 128 | 129 | 130 | 131 | 132 | 133 | org.apache.maven.plugins 134 | maven-javadoc-plugin 135 | 3.0.0 136 | 137 | 138 | attach-javadocs 139 | 140 | jar 141 | 142 | 143 | 144 | false 145 | none 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/RequestOptions.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | 5 | /** 6 | * 请求参数选项内容 7 | * @author jmdhappy 8 | * @site https://www.jeepay.vip 9 | * @date 2021-06-08 11:00 10 | */ 11 | public class RequestOptions { 12 | 13 | private String uri; 14 | private String version; 15 | private String signType; 16 | private String appId; 17 | private String apiKey; 18 | 19 | private int connectTimeout; 20 | private int readTimeout; 21 | private int maxNetworkRetries; 22 | private String acceptLanguage; 23 | 24 | public static RequestOptions getDefault(String uri, String version) { 25 | return new RequestOptions( 26 | uri, 27 | version, 28 | Jeepay.DEFAULT_SIGN_TYPE, 29 | Jeepay.appId, 30 | Jeepay.apiKey, 31 | Jeepay.getConnectTimeout(), 32 | Jeepay.getReadTimeout(), 33 | Jeepay.getMaxNetworkRetries(), 34 | Jeepay.getAcceptLanguage()); 35 | } 36 | 37 | private RequestOptions( 38 | String uri, 39 | String version, 40 | String signType, 41 | String appId, 42 | String apiKey, 43 | int connectTimeout, 44 | int readTimeout, 45 | int maxNetworkRetries, 46 | String acceptLanguage) { 47 | this.uri = uri; 48 | this.version = version; 49 | this.signType = signType; 50 | this.appId = appId; 51 | this.apiKey = apiKey; 52 | this.connectTimeout = connectTimeout; 53 | this.readTimeout = readTimeout; 54 | this.maxNetworkRetries = maxNetworkRetries; 55 | this.acceptLanguage = acceptLanguage; 56 | } 57 | 58 | public String getUri() { 59 | return uri; 60 | } 61 | 62 | public String getVersion() { 63 | return version; 64 | } 65 | 66 | public String getSignType() { 67 | return signType; 68 | } 69 | 70 | public String getAppId() { 71 | return appId; 72 | } 73 | 74 | public String getApiKey() { 75 | return apiKey; 76 | } 77 | 78 | public int getConnectTimeout() { 79 | return connectTimeout; 80 | } 81 | 82 | public int getReadTimeout() { 83 | return readTimeout; 84 | } 85 | 86 | public int getMaxNetworkRetries() { 87 | return maxNetworkRetries; 88 | } 89 | 90 | public String getAcceptLanguage() { 91 | return acceptLanguage; 92 | } 93 | 94 | public static RequestOptionsBuilder builder() { 95 | return new RequestOptionsBuilder(); 96 | } 97 | 98 | public static class RequestOptionsBuilder { 99 | private String uri; 100 | private String version; 101 | private String signType; 102 | private String appId; 103 | private String apiKey; 104 | private int connectTimeout; 105 | private int readTimeout; 106 | private int maxNetworkRetries; 107 | private String acceptLanguage; 108 | 109 | public RequestOptionsBuilder() { 110 | this.signType = Jeepay.DEFAULT_SIGN_TYPE; 111 | this.appId = Jeepay.appId; 112 | this.apiKey = Jeepay.apiKey; 113 | this.connectTimeout = Jeepay.getConnectTimeout(); 114 | this.readTimeout = Jeepay.getReadTimeout(); 115 | this.maxNetworkRetries = Jeepay.getMaxNetworkRetries(); 116 | this.acceptLanguage = Jeepay.getAcceptLanguage(); 117 | } 118 | 119 | public String getUri() { 120 | return uri; 121 | } 122 | 123 | public RequestOptionsBuilder setUri(String uri) { 124 | this.uri = normalizeApiUri(uri); 125 | return this; 126 | } 127 | 128 | public String getVersion() { 129 | return version; 130 | } 131 | 132 | public RequestOptionsBuilder setVersion(String version) { 133 | this.version = version; 134 | return this; 135 | } 136 | 137 | public String getSignType() { 138 | return signType; 139 | } 140 | 141 | public RequestOptionsBuilder setSignType(String signType) { 142 | this.signType = signType; 143 | return this; 144 | } 145 | 146 | public String getAppId() { 147 | return appId; 148 | } 149 | 150 | public RequestOptionsBuilder setAppId(String appId) { 151 | this.appId = normalizeAppId(appId); 152 | return this; 153 | } 154 | 155 | public RequestOptionsBuilder clearAppId() { 156 | this.appId = null; 157 | return this; 158 | } 159 | 160 | public String getApiKey() { 161 | return apiKey; 162 | } 163 | 164 | public RequestOptionsBuilder setApiKey(String apiKey) { 165 | this.apiKey = normalizeApiKey(apiKey); 166 | return this; 167 | } 168 | 169 | public RequestOptionsBuilder clearApiKey() { 170 | this.apiKey = null; 171 | return this; 172 | } 173 | 174 | public int getConnectTimeout() { 175 | return connectTimeout; 176 | } 177 | 178 | public RequestOptionsBuilder setConnectTimeout(int connectTimeout) { 179 | this.connectTimeout = connectTimeout; 180 | return this; 181 | } 182 | 183 | public int getReadTimeout() { 184 | return readTimeout; 185 | } 186 | 187 | public RequestOptionsBuilder setReadTimeout(int readTimeout) { 188 | this.readTimeout = readTimeout; 189 | return this; 190 | } 191 | 192 | public int getMaxNetworkRetries() { 193 | return maxNetworkRetries; 194 | } 195 | 196 | public RequestOptionsBuilder setMaxNetworkRetries(int maxNetworkRetries) { 197 | this.maxNetworkRetries = maxNetworkRetries; 198 | return this; 199 | } 200 | 201 | public String getAcceptLanguage() { 202 | return acceptLanguage; 203 | } 204 | 205 | public RequestOptionsBuilder setAcceptLanguage(String acceptLanguage) { 206 | this.acceptLanguage = normalizeAcceptLanguage(acceptLanguage); 207 | return this; 208 | } 209 | 210 | public RequestOptions build() { 211 | return new RequestOptions( 212 | normalizeApiUri(this.uri), 213 | version, 214 | signType, 215 | normalizeAppId(this.appId), 216 | normalizeApiKey(this.apiKey), 217 | connectTimeout, 218 | readTimeout, 219 | maxNetworkRetries, 220 | acceptLanguage); 221 | } 222 | } 223 | 224 | private static String normalizeApiUri(String apiUri) { 225 | if (apiUri == null) { 226 | throw new InvalidRequestOptionsException("接口URI不能为空!"); 227 | } 228 | if(apiUri.startsWith("/")) { 229 | throw new InvalidRequestOptionsException("接口URI("+apiUri+")不能以'/'开头"); 230 | } 231 | return apiUri; 232 | } 233 | 234 | private static String normalizeAppId(String appId) { 235 | if (appId == null) { 236 | return null; 237 | } 238 | String normalized = appId.trim(); 239 | if (normalized.isEmpty()) { 240 | throw new InvalidRequestOptionsException("appId不能为空!"); 241 | } 242 | return normalized; 243 | } 244 | 245 | private static String normalizeApiKey(String apiKey) { 246 | if (apiKey == null) { 247 | return null; 248 | } 249 | String normalized = apiKey.trim(); 250 | if (normalized.isEmpty()) { 251 | throw new InvalidRequestOptionsException("API key不能为空!"); 252 | } 253 | return normalized; 254 | } 255 | 256 | private static String normalizeAcceptLanguage(String acceptLanguage) { 257 | if (acceptLanguage == null) { 258 | return null; 259 | } 260 | String normalized = acceptLanguage.trim(); 261 | if (normalized.isEmpty()) { 262 | throw new InvalidRequestOptionsException("Accept-Language不能空!"); 263 | } 264 | return normalized; 265 | } 266 | 267 | public static class InvalidRequestOptionsException extends RuntimeException { 268 | private static final long serialVersionUID = 1L; 269 | 270 | public InvalidRequestOptionsException(String message) { 271 | super(message); 272 | } 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/test/java/com/jeequan/jeepay/PayOrderTest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.jeequan.jeepay.exception.JeepayException; 5 | import com.jeequan.jeepay.model.PayOrderCloseReqModel; 6 | import com.jeequan.jeepay.model.PayOrderCreateReqModel; 7 | import com.jeequan.jeepay.model.PayOrderQueryReqModel; 8 | import com.jeequan.jeepay.request.PayOrderCloseRequest; 9 | import com.jeequan.jeepay.request.PayOrderCreateRequest; 10 | import com.jeequan.jeepay.request.PayOrderQueryRequest; 11 | import com.jeequan.jeepay.response.PayOrderCloseResponse; 12 | import com.jeequan.jeepay.response.PayOrderCreateResponse; 13 | import com.jeequan.jeepay.response.PayOrderQueryResponse; 14 | import org.junit.jupiter.api.BeforeAll; 15 | import org.junit.jupiter.api.Test; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.util.Date; 20 | 21 | class PayOrderTest { 22 | 23 | final static Logger _log = LoggerFactory.getLogger(PayOrderTest.class); 24 | 25 | @BeforeAll 26 | public static void initApiKey() { 27 | Jeepay.setApiBase(JeepayTestData.getApiBase()); 28 | Jeepay.apiKey = JeepayTestData.getApiKey(); 29 | Jeepay.mchNo = JeepayTestData.getMchNo(); 30 | Jeepay.appId = JeepayTestData.getAppId(); 31 | } 32 | 33 | @Test 34 | public void testPayOrderCreate() { 35 | 36 | /* 37 | 支持自己定义RequestOptions属性,更灵活 38 | RequestOptions options = RequestOptions.builder().setAppId("60deb8d6c6104c854e2346e4").setApiKey("11982212000912313").setUri("api/pay/unifiedOrder").setReadTimeout(100).build(); 39 | PayOrderCreateRequest request = new PayOrderCreateRequest(); 40 | request.setRequestOptions(options); 41 | */ 42 | 43 | /* 44 | 特殊支付方式: 45 | QR_CASHIER ( 通过二维码跳转到收银台完成支付, 已集成获取用户ID的实现。 ) 46 | AUTO_BAR (自动分类条码支付) 47 | */ 48 | 49 | // 支付接口文档:https://docs.jeequan.com/docs/jeepay/payment_api 50 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, Jeepay.getApiBase()); 51 | String wayCode = "WX_LITE"; // 支付方式 52 | PayOrderCreateRequest request = new PayOrderCreateRequest(); 53 | PayOrderCreateReqModel model = new PayOrderCreateReqModel(); 54 | model.setMchNo(Jeepay.mchNo); // 商户号 55 | model.setAppId(jeepayClient.getAppId()); // 应用ID 56 | String orderNo = "mho" + new Date().getTime(); 57 | model.setMchOrderNo(orderNo); // 商户订单号 58 | model.setWayCode(wayCode); // 支付方式 59 | model.setAmount(1l); // 金额,单位分 60 | model.setCurrency("CNY"); // 币种,目前只支持cny 61 | model.setClientIp("192.166.1.132"); // 发起支付请求客户端的IP地址 62 | model.setSubject("商品标题"); // 商品标题 63 | model.setBody("商品描述"); // 商品描述 64 | model.setNotifyUrl("https://www.jeequan.com"); // 异步通知地址 65 | model.setReturnUrl(""); // 前端跳转地址 66 | model.setChannelExtra(channelExtra(wayCode)); // 渠道扩展参数 67 | model.setExtParam(""); // 商户扩展参数,回调时原样返回 68 | 69 | request.setBizModel(model); 70 | try { 71 | PayOrderCreateResponse response = jeepayClient.execute(request); 72 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 73 | // 下单成功 74 | if(response.isSuccess(Jeepay.apiKey)) { 75 | String payOrderId = response.get().getPayOrderId(); 76 | _log.info("payOrderId:{}", payOrderId); 77 | _log.info("mchOrderNo:{}", response.get().getMchOrderNo()); 78 | }else { 79 | _log.info("下单失败:{}", orderNo); 80 | _log.info("通道错误码:{}", response.get().getErrCode()); 81 | _log.info("通道错误信息:{}", response.get().getErrMsg()); 82 | } 83 | } catch (JeepayException e) { 84 | _log.error(e.getMessage()); 85 | } 86 | 87 | } 88 | 89 | String channelExtra(String wayCode) { 90 | if("WX_LITE".equals(wayCode)) return wxJsapiExtra(); 91 | if("WX_JSAPI".equals(wayCode)) return wxJsapiExtra(); 92 | if("WX_BAR".equals(wayCode)) return wxBarExtra(); 93 | if("ALI_BAR".equals(wayCode)) return aliBarExtra(); 94 | if("YSF_BAR".equals(wayCode)) return ysfBarExtra(); 95 | if("UPACP_BAR".equals(wayCode)) return upacpBarExtra(); 96 | if("QR_CASHIER".equals(wayCode)) return qrCashierExtra(); 97 | if("AUTO_BAR".equals(wayCode)) return autoBarExtra(); 98 | if("PP_PC".equals(wayCode)) return ppExtra(); 99 | if("SAND_H5".equals(wayCode)) return sandH5Extra(); 100 | return ""; 101 | } 102 | 103 | private String wxJsapiExtra() { 104 | JSONObject obj = new JSONObject(); 105 | obj.put("openId", "134756231107811344"); 106 | return obj.toString(); 107 | } 108 | 109 | private String wxBarExtra() { 110 | JSONObject obj = new JSONObject(); 111 | obj.put("authCode", "134675721924600802"); 112 | return obj.toString(); 113 | } 114 | 115 | private String aliBarExtra() { 116 | JSONObject obj = new JSONObject(); 117 | obj.put("authCode", "1180812820366966512"); 118 | return obj.toString(); 119 | } 120 | 121 | private String ysfBarExtra() { 122 | JSONObject obj = new JSONObject(); 123 | obj.put("authCode", "6223194037624963090"); 124 | return obj.toString(); 125 | } 126 | 127 | private String upacpBarExtra() { 128 | JSONObject obj = new JSONObject(); 129 | obj.put("authCode", "6227662446181058584"); 130 | return obj.toString(); 131 | } 132 | 133 | private String qrCashierExtra() { 134 | JSONObject obj = new JSONObject(); 135 | obj.put("payDataType", "codeImgUrl"); 136 | return obj.toString(); 137 | } 138 | 139 | private String autoBarExtra() { 140 | JSONObject obj = new JSONObject(); 141 | obj.put("authCode", "134753177301492386"); 142 | return obj.toString(); 143 | } 144 | 145 | private String ppExtra() { 146 | JSONObject obj = new JSONObject(); 147 | obj.put("cancelUrl", "http://baidu.com"); 148 | return obj.toString(); 149 | } 150 | 151 | private String sandH5Extra() { 152 | JSONObject obj = new JSONObject(); 153 | JSONObject payExtra = new JSONObject(); 154 | // 聚合码 155 | obj.put("productCode", "02000001"); 156 | obj.put("payExtra", ""); 157 | obj.put("metaOption", "[{\"s\":\"Pc\",\"n\":\"支付\"}]"); 158 | // 微信公众号 159 | /*obj.put("productCode", "02010002"); 160 | payExtra = new JSONObject(); 161 | payExtra.put("mer_app_id", ""); 162 | payExtra.put("openid", ""); 163 | obj.put("payExtra", payExtra.toString()); 164 | obj.put("metaOption", ""); 165 | // 微信小程序(云函数sdk) 166 | obj.put("productCode", "02010007"); 167 | payExtra = new JSONObject(); 168 | payExtra.put("wx_app_id", ""); // 移动应用Appid(微信开放平台获取,wx开头) 169 | payExtra.put("gh_ori_id", ""); // 小程序原始id(微信公众平台获取,gh_开头) 170 | payExtra.put("path_url", ""); // 拉起小程序页面的可带参路径,不填默认拉起小程序首页 171 | payExtra.put("miniProgramType", "0"); // 开发时根据小程序是开发版、体验版或正式版自行选择。正式版:0; 开发版:1; 体验版:2 172 | obj.put("payExtra", payExtra.toString()); 173 | obj.put("metaOption", ""); 174 | // 支付宝生活号 175 | obj.put("productCode", "02010002"); 176 | payExtra = new JSONObject(); 177 | payExtra.put("buyer_id", ""); // 支付宝生活号所需参数(支付宝H5建议不传) 178 | obj.put("payExtra", payExtra.toString()); 179 | obj.put("metaOption", "");*/ 180 | return obj.toString(); 181 | 182 | } 183 | 184 | @Test 185 | public void testPayOrderQuery() { 186 | // 支付接口文档:https://docs.jeequan.com/docs/jeepay/payment_api 187 | JeepayClient jeepayClient = JeepayClient.getInstance(Jeepay.appId, Jeepay.apiKey, Jeepay.getApiBase()); 188 | PayOrderQueryRequest request = new PayOrderQueryRequest(); 189 | PayOrderQueryReqModel model = new PayOrderQueryReqModel(); 190 | model.setMchNo(Jeepay.mchNo); // 商户号 191 | model.setAppId(jeepayClient.getAppId()); // 应用ID 192 | model.setPayOrderId("P202106181104177050002"); // 支付订单号 193 | request.setBizModel(model); 194 | 195 | try { 196 | PayOrderQueryResponse response = jeepayClient.execute(request); 197 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 198 | 199 | if(response.isSuccess(Jeepay.apiKey)) { 200 | _log.info("订单信息:{}", response); 201 | _log.info("金额:{}", response.get().getAmount()); 202 | } 203 | } catch (JeepayException e) { 204 | 205 | e.printStackTrace(); 206 | } 207 | 208 | } 209 | 210 | @Test 211 | public void testPayOrderClose() { 212 | JeepayClient jeepayClient = new JeepayClient(); 213 | PayOrderCloseRequest request = new PayOrderCloseRequest(); 214 | PayOrderCloseReqModel model = new PayOrderCloseReqModel(); 215 | model.setMchNo(Jeepay.mchNo); // 商户号 216 | model.setAppId(Jeepay.appId); 217 | model.setPayOrderId("P1485879219030011906"); // 支付订单号 218 | request.setBizModel(model); 219 | 220 | try { 221 | PayOrderCloseResponse response = jeepayClient.execute(request); 222 | _log.info("验签结果:{}", response.checkSign(Jeepay.apiKey)); 223 | 224 | if(response.isSuccess(Jeepay.apiKey)) { 225 | _log.info("返回信息:{}", response); 226 | } 227 | } catch (JeepayException e) { 228 | 229 | e.printStackTrace(); 230 | } 231 | 232 | } 233 | } -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/util/JSONWriter.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.util; 2 | 3 | import com.jeequan.jeepay.ApiField; 4 | import com.jeequan.jeepay.ApiListField; 5 | import com.jeequan.jeepay.Jeepay; 6 | 7 | import java.beans.BeanInfo; 8 | import java.beans.IntrospectionException; 9 | import java.beans.Introspector; 10 | import java.beans.PropertyDescriptor; 11 | import java.lang.reflect.Array; 12 | import java.lang.reflect.Field; 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.lang.reflect.Method; 15 | import java.text.CharacterIterator; 16 | import java.text.DateFormat; 17 | import java.text.SimpleDateFormat; 18 | import java.text.StringCharacterIterator; 19 | import java.util.*; 20 | 21 | /** 22 | * 输出Json格式数据 23 | * @author jmdhappy 24 | * @site https://www.jeepay.vip 25 | * @date 2021-06-08 11:00 26 | */ 27 | public class JSONWriter { 28 | 29 | private StringBuffer buf = new StringBuffer(); 30 | private Stack calls = new Stack(); 31 | private boolean emitClassName = true; 32 | private DateFormat format; 33 | 34 | public JSONWriter(boolean emitClassName) { 35 | this.emitClassName = emitClassName; 36 | } 37 | 38 | public JSONWriter() { 39 | this(false); 40 | } 41 | 42 | public JSONWriter(DateFormat format) { 43 | this(false); 44 | this.format = format; 45 | } 46 | 47 | public String write(Object object) { 48 | return write(object, false); 49 | } 50 | 51 | public String write(Object object, boolean isApiModel) { 52 | buf.setLength(0); 53 | value(object, isApiModel); 54 | return buf.toString(); 55 | } 56 | 57 | public String write(long n) { 58 | return String.valueOf(n); 59 | } 60 | 61 | public String write(double d) { 62 | return String.valueOf(d); 63 | } 64 | 65 | public String write(char c) { 66 | return "\"" + c + "\""; 67 | } 68 | 69 | public String write(boolean b) { 70 | return String.valueOf(b); 71 | } 72 | 73 | private void value(Object object) { 74 | value(object, false); 75 | } 76 | 77 | private void value(Object object, boolean isApiModel) { 78 | if (object == null || cyclic(object)) { 79 | add(null); 80 | } else { 81 | calls.push(object); 82 | if (object instanceof Class) { string(object); } else if (object instanceof Boolean) { 83 | bool(((Boolean) object).booleanValue()); 84 | } else if (object instanceof Number) { 85 | add(object); 86 | } else if (object instanceof String) { 87 | string(object); 88 | } else if (object instanceof Character) { 89 | string(object); 90 | } else if (object instanceof Map) { 91 | map((Map) object); 92 | } else if (object.getClass().isArray()) { 93 | array(object, isApiModel); 94 | } else if (object instanceof Iterator) { 95 | array((Iterator) object, isApiModel); 96 | } else if (object instanceof Collection) { 97 | array(((Collection) object).iterator(), isApiModel); 98 | } else if (object instanceof Date) { date((Date) object); } else { 99 | if (isApiModel) { model(object); } else { bean(object); } 100 | } 101 | calls.pop(); 102 | } 103 | } 104 | 105 | private boolean cyclic(Object object) { 106 | Iterator it = calls.iterator(); 107 | while (it.hasNext()) { 108 | Object called = it.next(); 109 | if (object == called) { return true; } 110 | } 111 | return false; 112 | } 113 | 114 | private void bean(Object object) { 115 | add("{"); 116 | BeanInfo info; 117 | boolean addedSomething = false; 118 | try { 119 | info = Introspector.getBeanInfo(object.getClass()); 120 | PropertyDescriptor[] props = info.getPropertyDescriptors(); 121 | for (int i = 0; i < props.length; ++i) { 122 | PropertyDescriptor prop = props[i]; 123 | String name = prop.getName(); 124 | Method accessor = prop.getReadMethod(); 125 | if ((emitClassName || !"class".equals(name)) && accessor != null) { 126 | if (!accessor.isAccessible()) { accessor.setAccessible(true); } 127 | Object value = accessor.invoke(object, (Object[]) null); 128 | if (value == null) { continue; } 129 | if (addedSomething) { add(','); } 130 | add(name, value); 131 | addedSomething = true; 132 | } 133 | } 134 | Field[] ff = object.getClass().getFields(); 135 | for (int i = 0; i < ff.length; ++i) { 136 | Field field = ff[i]; 137 | Object value = field.get(object); 138 | if (value == null) { continue; } 139 | if (addedSomething) { add(','); } 140 | add(field.getName(), value); 141 | addedSomething = true; 142 | } 143 | } catch (IllegalAccessException iae) { 144 | iae.printStackTrace(); 145 | } catch (InvocationTargetException ite) { 146 | ite.getCause().printStackTrace(); 147 | ite.printStackTrace(); 148 | } catch (IntrospectionException ie) { 149 | ie.printStackTrace(); 150 | } 151 | add("}"); 152 | } 153 | 154 | private void model(Object object) { 155 | add("{"); 156 | boolean addedSomething = false; 157 | Field[] ff = object.getClass().getDeclaredFields(); 158 | try { 159 | for (int i = 0; i < ff.length; ++i) { 160 | Field field = ff[i]; 161 | // 获取注解 162 | ApiField jsonField = field.getAnnotation(ApiField.class); 163 | ApiListField listField = field.getAnnotation(ApiListField.class); 164 | // 优先处理列表类型注解,非列表类型才处理字段注解 165 | if (listField != null) { 166 | PropertyDescriptor pd = new PropertyDescriptor(field.getName(), 167 | object.getClass()); 168 | Method accessor = pd.getReadMethod(); 169 | if (!accessor.isAccessible()) { accessor.setAccessible(true); } 170 | Object value = accessor.invoke(object, (Object[]) null); 171 | if (value == null) { continue; } 172 | if (addedSomething) { add(','); } 173 | add(listField.value(), value, true); 174 | addedSomething = true; 175 | } else if (jsonField != null) { 176 | PropertyDescriptor pd = new PropertyDescriptor(field.getName(), 177 | object.getClass()); 178 | Method accessor = pd.getReadMethod(); 179 | if (!accessor.isAccessible()) { accessor.setAccessible(true); } 180 | Object value = accessor.invoke(object, (Object[]) null); 181 | if (value == null) { continue; } 182 | if (addedSomething) { add(','); } 183 | add(jsonField.value(), value, true); 184 | addedSomething = true; 185 | } 186 | } 187 | } catch (IntrospectionException e1) { 188 | 189 | //todo 190 | } catch (IllegalAccessException e2) { 191 | 192 | //todo 193 | } catch (IllegalArgumentException e3) { 194 | // todo 195 | } catch (InvocationTargetException e4) { 196 | // todo 197 | } 198 | add("}"); 199 | } 200 | 201 | private void add(String name, Object value) { 202 | add(name, value, false); 203 | } 204 | 205 | private void add(String name, Object value, boolean isApiModel) { 206 | add('"'); 207 | add(name); 208 | add("\":"); 209 | value(value, isApiModel); 210 | } 211 | 212 | private void map(Map map) { 213 | add("{"); 214 | Iterator it = map.entrySet().iterator(); 215 | while (it.hasNext()) { 216 | Map.Entry e = (Map.Entry) it.next(); 217 | value(e.getKey()); 218 | add(":"); 219 | value(e.getValue()); 220 | if (it.hasNext()) { add(','); } 221 | } 222 | add("}"); 223 | } 224 | 225 | private void array(Iterator it) { 226 | array(it, false); 227 | } 228 | 229 | private void array(Iterator it, boolean isApiModel) { 230 | add("["); 231 | while (it.hasNext()) { 232 | value(it.next(), isApiModel); 233 | if (it.hasNext()) { add(","); } 234 | } 235 | add("]"); 236 | } 237 | 238 | private void array(Object object) { 239 | array(object, false); 240 | } 241 | 242 | private void array(Object object, boolean isApiModel) { 243 | add("["); 244 | int length = Array.getLength(object); 245 | for (int i = 0; i < length; ++i) { 246 | value(Array.get(object, i), isApiModel); 247 | if (i < length - 1) { add(','); } 248 | } 249 | add("]"); 250 | } 251 | 252 | private void bool(boolean b) { 253 | add(b ? "true" : "false"); 254 | } 255 | 256 | private void date(Date date) { 257 | if (this.format == null) { 258 | this.format = new SimpleDateFormat(Jeepay.DATE_TIME_FORMAT); 259 | this.format.setTimeZone(TimeZone.getTimeZone(Jeepay.DATE_TIMEZONE)); 260 | } 261 | add("\""); 262 | add(format.format(date)); 263 | add("\""); 264 | } 265 | 266 | private void string(Object obj) { 267 | add('"'); 268 | CharacterIterator it = new StringCharacterIterator(obj.toString()); 269 | for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { 270 | if (c == '"') { add("\\\""); } else if (c == '\\') { add("\\\\"); } else if (c == '/') { add("\\/"); } else if (c == '\b') { 271 | add("\\b"); 272 | } else if (c 273 | == '\f') { add("\\f"); } else if (c == '\n') { 274 | add("\\n"); 275 | } else if (c 276 | == '\r') { add("\\r"); } else if (c 277 | == '\t') { add("\\t"); } else if (Character 278 | .isISOControl(c)) { 279 | unicode(c); 280 | } else { 281 | add(c); 282 | } 283 | } 284 | add('"'); 285 | } 286 | 287 | private void add(Object obj) { 288 | buf.append(obj); 289 | } 290 | 291 | private void add(char c) { 292 | buf.append(c); 293 | } 294 | 295 | static char[] hex = "0123456789ABCDEF".toCharArray(); 296 | 297 | private void unicode(char c) { 298 | add("\\u"); 299 | int n = c; 300 | for (int i = 0; i < 4; ++i) { 301 | int digit = (n & 0xf000) >> 12; 302 | add(hex[digit]); 303 | n <<= 4; 304 | } 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/main/java/com/jeequan/jeepay/net/APIJeepayRequest.java: -------------------------------------------------------------------------------- 1 | package com.jeequan.jeepay.net; 2 | 3 | import com.jeequan.jeepay.Jeepay; 4 | import com.jeequan.jeepay.exception.APIConnectionException; 5 | import com.jeequan.jeepay.exception.JeepayException; 6 | import com.jeequan.jeepay.util.JeepayKit; 7 | import com.jeequan.jeepay.util.StringUtils; 8 | 9 | import java.io.IOException; 10 | import java.io.UnsupportedEncodingException; 11 | import java.net.URL; 12 | import java.net.URLEncoder; 13 | import java.nio.charset.StandardCharsets; 14 | import java.util.*; 15 | 16 | /** 17 | * API请求 18 | * @author jmdhappy 19 | * @site https://www.jeepay.vip 20 | * @date 2021-06-08 11:00 21 | */ 22 | public class APIJeepayRequest { 23 | /** 24 | * 请求方法 (GET, POST, DELETE or PUT) 25 | * */ 26 | APIResource.RequestMethod method; 27 | 28 | /** 29 | * 请求URL 30 | */ 31 | URL url; 32 | 33 | /** 34 | * 请求Body 35 | */ 36 | HttpContent content; 37 | 38 | /** 39 | * 请求Header 40 | */ 41 | HttpHeaders headers; 42 | 43 | /** 44 | * 请求参数 45 | */ 46 | Map params; 47 | 48 | /** 49 | * 请求选项 50 | */ 51 | RequestOptions options; 52 | 53 | /** 54 | * 实例化Jeepay请求 55 | * @param method 56 | * @param url 57 | * @param params 58 | * @param options 59 | * @throws JeepayException 60 | */ 61 | public APIJeepayRequest( 62 | APIResource.RequestMethod method, 63 | String url, 64 | Map params, 65 | RequestOptions options) 66 | throws JeepayException { 67 | try { 68 | this.params = (params != null) ? Collections.unmodifiableMap(params) : null; 69 | this.options = options; 70 | this.method = method; 71 | this.url = buildURL(method, StringUtils.genUrl(url, this.options.getUri()), params); 72 | this.content = buildContent(method, params, this.options); 73 | this.headers = buildHeaders(method, this.options); 74 | } catch (IOException e) { 75 | throw new APIConnectionException( 76 | String.format( 77 | "请求Jeepay(%s)异常,请检查网络或重试.异常信息:%s", 78 | StringUtils.genUrl(url, options.getUri()), e.getMessage()), 79 | e); 80 | } 81 | } 82 | 83 | private static URL buildURL( 84 | APIResource.RequestMethod method, String spec, Map params) 85 | throws IOException { 86 | StringBuilder sb = new StringBuilder(); 87 | 88 | sb.append(spec); 89 | 90 | if ((method != APIResource.RequestMethod.POST && method != APIResource.RequestMethod.PUT) && (params != null)) { 91 | String queryString = createQuery(params); 92 | if (!queryString.isEmpty()) { 93 | sb.append("?"); 94 | sb.append(queryString); 95 | } 96 | } 97 | 98 | return new URL(sb.toString()); 99 | } 100 | 101 | public static URL buildURLWithSign(String url, Map params, RequestOptions options) throws APIConnectionException { 102 | 103 | params.put(Jeepay.API_VERSION_NAME, options.getVersion()); 104 | params.put(Jeepay.API_SIGN_TYPE_NAME, options.getSignType()); 105 | String requestTime = currentTimeString(); 106 | params.put(Jeepay.API_REQ_TIME_NAME, requestTime); 107 | String signature; 108 | try { 109 | signature = buildJeepaySignature(params, options); 110 | } catch (IOException e) { 111 | throw new APIConnectionException("生成Jeepay请求签名异常", e); 112 | } 113 | 114 | if (signature != null) { 115 | params.put(Jeepay.API_SIGN_NAME, signature); 116 | } 117 | 118 | StringBuilder sb = new StringBuilder(); 119 | 120 | sb.append(StringUtils.genUrl(url, options.getUri())); 121 | 122 | if (params != null) { 123 | String queryString = createQuery(params); 124 | if (!queryString.isEmpty()) { 125 | sb.append("?"); 126 | sb.append(queryString); 127 | } 128 | } 129 | 130 | try { 131 | return new URL(sb.toString()); 132 | } catch (IOException e){ 133 | throw new APIConnectionException("生成 Jeepay 请求URL异常", e); 134 | } 135 | } 136 | 137 | private static HttpContent buildContent ( 138 | APIResource.RequestMethod method, Map params, RequestOptions options) throws JeepayException { 139 | if (method != APIResource.RequestMethod.POST && method != APIResource.RequestMethod.PUT) { 140 | return null; 141 | } 142 | 143 | if (params == null) { 144 | return null; 145 | } 146 | 147 | params.put(Jeepay.API_VERSION_NAME, options.getVersion()); 148 | params.put(Jeepay.API_SIGN_TYPE_NAME, options.getSignType()); 149 | String requestTime = currentTimeString(); 150 | params.put(Jeepay.API_REQ_TIME_NAME, requestTime); 151 | String signature; 152 | try { 153 | signature = buildJeepaySignature(params, options); 154 | } catch (IOException e) { 155 | throw new APIConnectionException("生成Jeepay请求签名异常", e); 156 | } 157 | if (signature != null) { 158 | params.put(Jeepay.API_SIGN_NAME, signature); 159 | } 160 | 161 | return HttpContent.buildJSONContent(params); 162 | } 163 | 164 | /** 165 | * @param params the parameters 166 | * @return queryString 167 | */ 168 | private static String createQuery(Map params) { 169 | if (params == null) { 170 | return ""; 171 | } 172 | 173 | Map flatParams = flattenParams(params); 174 | StringBuilder queryStringBuffer = new StringBuilder(); 175 | for (Map.Entry entry : flatParams.entrySet()) { 176 | if (queryStringBuffer.length() > 0) { 177 | queryStringBuffer.append("&"); 178 | } 179 | queryStringBuffer.append(urlEncodePair(entry.getKey(), 180 | entry.getValue())); 181 | } 182 | return queryStringBuffer.toString(); 183 | } 184 | 185 | /** 186 | * @param k the key 187 | * @param v the value 188 | * @return urlEncodedString 189 | */ 190 | private static String urlEncodePair(String k, String v) { 191 | return String.format("%s=%s", urlEncode(k), urlEncode(v)); 192 | } 193 | 194 | /** 195 | * @param str the string to encode 196 | * @return urlEncodedString 197 | */ 198 | protected static String urlEncode(String str) { 199 | if (str == null) { 200 | return null; 201 | } 202 | 203 | try { 204 | return URLEncoder.encode(str, StandardCharsets.UTF_8.name()); 205 | } catch (UnsupportedEncodingException e) { 206 | throw new AssertionError("UTF-8 is unknown"); 207 | } 208 | } 209 | 210 | /** 211 | * @param params the parameters 212 | * @return flattenParams 213 | */ 214 | private static Map flattenParams(Map params) { 215 | // 如果输入参数为 null,返回空 Map 216 | if (params == null) { 217 | return new HashMap<>(); 218 | } 219 | 220 | Map flatParams = new HashMap<>(); 221 | 222 | for (Map.Entry entry : params.entrySet()) { 223 | String key = entry.getKey(); 224 | Object value = entry.getValue(); 225 | 226 | // 处理空键的情况 227 | if (key == null) { 228 | continue; 229 | } 230 | 231 | if (value instanceof Map) { 232 | // 处理嵌套 Map 233 | handleNestedMap(key, (Map) value, flatParams); 234 | } else if (value instanceof List) { 235 | // 处理 List 类型(包括 ArrayList 和其他实现) 236 | handleNestedList(key, (List) value, flatParams); 237 | } else if (value == null) { 238 | // 处理 null 值 239 | flatParams.put(key, ""); 240 | } else { 241 | // 处理普通值 242 | flatParams.put(key, value.toString()); 243 | } 244 | } 245 | 246 | return flatParams; 247 | } 248 | 249 | // 辅助方法:处理嵌套 Map 250 | private static void handleNestedMap(String parentKey, Map nestedMap, Map flatParams) { 251 | for (Map.Entry nestedEntry : nestedMap.entrySet()) { 252 | String childKey = String.format("%s[%s]", parentKey, nestedEntry.getKey() != null ? nestedEntry.getKey().toString() : "null"); 253 | Object childValue = nestedEntry.getValue(); 254 | 255 | if (childValue instanceof Map) { 256 | handleNestedMap(childKey, (Map) childValue, flatParams); 257 | } else if (childValue instanceof List) { 258 | handleNestedList(childKey, (List) childValue, flatParams); 259 | } else if (childValue == null) { 260 | flatParams.put(childKey, ""); 261 | } else { 262 | flatParams.put(childKey, childValue.toString()); 263 | } 264 | } 265 | } 266 | 267 | // 辅助方法:处理嵌套 List 268 | private static void handleNestedList(String parentKey, List nestedList, Map flatParams) { 269 | for (int i = 0; i < nestedList.size(); i++) { 270 | String childKey = String.format("%s[%d]", parentKey, i); 271 | Object childValue = nestedList.get(i); 272 | 273 | if (childValue instanceof Map) { 274 | handleNestedMap(childKey, (Map) childValue, flatParams); 275 | } else if (childValue instanceof List) { 276 | handleNestedList(childKey, (List) childValue, flatParams); 277 | } else if (childValue == null) { 278 | flatParams.put(childKey, ""); 279 | } else { 280 | flatParams.put(childKey, childValue.toString()); 281 | } 282 | } 283 | } 284 | 285 | 286 | private static HttpHeaders buildHeaders(APIResource.RequestMethod method, RequestOptions options) 287 | throws JeepayException { 288 | Map> headerMap = new HashMap>(); 289 | 290 | // Accept 291 | headerMap.put("Accept", Collections.singletonList("application/json")); 292 | 293 | // Accept-Charset 294 | headerMap.put("Accept-Charset", Collections.singletonList(APIResource.CHARSET.name())); 295 | 296 | // Accept-Language 297 | headerMap.put("Accept-Language", Collections.singletonList(options.getAcceptLanguage())); 298 | 299 | return HttpHeaders.of(headerMap); 300 | } 301 | 302 | protected static String buildJeepaySignature(Map params, RequestOptions options) 303 | throws IOException { 304 | 305 | String signType = options.getSignType(); 306 | if("MD5".equalsIgnoreCase(signType)) { 307 | return JeepayKit.getSign(params, options.getApiKey()); 308 | }else if("RSA2".equalsIgnoreCase(signType)) { 309 | throw new AssertionError("暂不支持RSA2签名"); 310 | } 311 | throw new AssertionError("请设置正确的签名类型"); 312 | } 313 | 314 | protected static String currentTimeString() { 315 | return String.valueOf(System.currentTimeMillis()); 316 | } 317 | 318 | public APIResource.RequestMethod getMethod() { 319 | return method; 320 | } 321 | 322 | public URL getUrl() { 323 | return url; 324 | } 325 | 326 | public HttpContent getContent() { 327 | return content; 328 | } 329 | 330 | public HttpHeaders getHeaders() { 331 | return headers; 332 | } 333 | 334 | public Map getParams() { 335 | return params; 336 | } 337 | 338 | public RequestOptions getOptions() { 339 | return options; 340 | } 341 | } 342 | --------------------------------------------------------------------------------