├── .gitignore ├── src ├── main │ ├── java │ │ └── net │ │ │ └── arccode │ │ │ └── wechat │ │ │ └── pay │ │ │ └── api │ │ │ ├── common │ │ │ ├── util │ │ │ │ ├── json │ │ │ │ │ ├── JSONErrorListener.java │ │ │ │ │ ├── StdoutStreamErrorListener.java │ │ │ │ │ ├── ExceptionErrorListener.java │ │ │ │ │ ├── JSONValidatingReader.java │ │ │ │ │ ├── BufferErrorListener.java │ │ │ │ │ ├── JSONValidatingWriter.java │ │ │ │ │ ├── JSONValidator.java │ │ │ │ │ ├── JSONWriter.java │ │ │ │ │ └── JSONReader.java │ │ │ │ ├── MapUtils.java │ │ │ │ ├── RequestParametersHolder.java │ │ │ │ ├── SDKUtils.java │ │ │ │ ├── RandomUtils.java │ │ │ │ ├── ACHashMap.java │ │ │ │ ├── ImageUtils.java │ │ │ │ ├── WXPaySignUtils.java │ │ │ │ ├── DateUtils.java │ │ │ │ ├── StringUtils.java │ │ │ │ ├── HttpUtils.java │ │ │ │ └── Converters.java │ │ │ ├── annotation │ │ │ │ ├── ApiField.java │ │ │ │ └── ApiListField.java │ │ │ ├── parser │ │ │ │ ├── Converter.java │ │ │ │ ├── WXPayParser.java │ │ │ │ ├── xml │ │ │ │ │ ├── ObjectXmlParser.java │ │ │ │ │ └── XmlConverter.java │ │ │ │ ├── json │ │ │ │ │ ├── ObjectJsonParser.java │ │ │ │ │ └── JsonConverter.java │ │ │ │ └── Reader.java │ │ │ ├── mapping │ │ │ │ └── ACFieldMethod.java │ │ │ ├── exception │ │ │ │ └── WXPayApiException.java │ │ │ ├── constant │ │ │ │ └── WXPayConstants.java │ │ │ └── log │ │ │ │ └── ACLogger.java │ │ │ ├── service │ │ │ ├── IWXPayClient.java │ │ │ └── WXPayClient.java │ │ │ └── protocol │ │ │ ├── base │ │ │ ├── WXPayRequest.java │ │ │ └── WXPayResponse.java │ │ │ ├── query_order │ │ │ ├── QueryOrderRequest.java │ │ │ └── QueryOrderResponse.java │ │ │ ├── unified_order │ │ │ ├── UnifiedOrderResponse.java │ │ │ └── UnifiedOrderRequest.java │ │ │ ├── refund │ │ │ ├── RefundRequest.java │ │ │ └── RefundResponse.java │ │ │ ├── mch_pay │ │ │ ├── MchPayResponse.java │ │ │ └── MchPayRequest.java │ │ │ └── pay_notify │ │ │ └── PayNotifyResponse.java │ ├── resources │ │ └── logback.xml │ └── assembly │ │ └── src.xml └── test │ └── java │ └── net │ └── arccode │ └── wechat │ └── pay │ └── api │ ├── common │ └── util │ │ ├── RandomUtilsTest.java │ │ └── StringUtilsTest.java │ └── service │ └── WXPayClientTest.java ├── LICENSE ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | target 3 | wechat-pay-sdk.iml -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/JSONErrorListener.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | public interface JSONErrorListener { 4 | void start(String text); 5 | 6 | void error(String message, int column); 7 | 8 | void end(); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/StdoutStreamErrorListener.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | public class StdoutStreamErrorListener extends BufferErrorListener { 4 | 5 | public void end() { 6 | System.out.print(buffer.toString()); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/ExceptionErrorListener.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | public class ExceptionErrorListener extends BufferErrorListener { 4 | 5 | public void error(String type, int col) { 6 | super.error(type, col); 7 | throw new IllegalArgumentException(buffer.toString()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/net/arccode/wechat/pay/api/common/util/RandomUtilsTest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * @author http://arccode.net 9 | * @since 2015-11-24 10 | */ 11 | public class RandomUtilsTest { 12 | 13 | 14 | private static final Logger LOG = LoggerFactory.getLogger(RandomUtilsTest.class); 15 | 16 | @Test 17 | public void randomStr() { 18 | 19 | LOG.info(SDKUtils.genRandomStringByLength(32)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/annotation/ApiField.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.annotation; 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 | * 11 | * @author http://arccode.net 12 | * @since 2015-11-02 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(value = {ElementType.FIELD}) 16 | public @interface ApiField { 17 | 18 | /** 19 | * 属性映射名称 20 | */ 21 | public String value() default ""; 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/net/arccode/wechat/pay/api/common/util/StringUtilsTest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import org.junit.Test; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * @author http://arccode.net 9 | * @since 2015-11-24 10 | */ 11 | public class StringUtilsTest { 12 | 13 | 14 | private static final Logger LOG = LoggerFactory.getLogger(StringUtilsTest.class); 15 | 16 | @Test 17 | public void regx() { 18 | String phone = ""; 19 | 20 | boolean flag = StringUtils.isPhoneNum(phone); 21 | 22 | LOG.info(flag + ""); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/annotation/ApiListField.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.annotation; 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 | * 11 | * @author http://arccode.net 12 | * @since 2015-11-05 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(value = { ElementType.FIELD }) 16 | public @interface ApiListField { 17 | 18 | /** 19 | * JSON列表属性映射名称 20 | * 21 | * @return 22 | */ 23 | public String value() default ""; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/parser/Converter.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.parser; 2 | 3 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | 6 | /** 7 | * 动态格式转换器 8 | * 9 | * @author http://arccode.net 10 | * @since 2015-11-05 11 | */ 12 | public interface Converter { 13 | 14 | /** 15 | * 响应字符串转响应对象类型 16 | * 17 | * @param resp 18 | * @param clazz 19 | * @param 20 | * @return 21 | */ 22 | public T toResponse(String resp, Class clazz) throws WXPayApiException; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/parser/WXPayParser.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.parser; 2 | 3 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | 6 | /** 7 | * 响应解释器接口; 响应格式可以是JSON, XML等等. 8 | * 9 | * @author http://arccode.net 10 | * @since 2015-11-05 11 | */ 12 | public interface WXPayParser { 13 | 14 | /** 15 | * 把响应字段解析为对应的协议对象 16 | * 17 | * @param resp 18 | * @return 19 | */ 20 | public T parse(String resp) throws WXPayApiException; 21 | 22 | /** 23 | * 获取响应类类型 24 | * 25 | * @return 26 | */ 27 | public Class getResponseClass(); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/mapping/ACFieldMethod.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.mapping; 2 | 3 | import java.lang.reflect.Field; 4 | import java.lang.reflect.Method; 5 | 6 | /** 7 | * @author http://arccode.net 8 | * @since 2015-11-05 9 | */ 10 | public class ACFieldMethod { 11 | 12 | /** 13 | * 属性 14 | */ 15 | private Field field; 16 | 17 | /** 18 | * 方法 19 | */ 20 | private Method method; 21 | 22 | public Field getField() { 23 | return field; 24 | } 25 | 26 | public void setField(Field field) { 27 | this.field = field; 28 | } 29 | 30 | public Method getMethod() { 31 | return method; 32 | } 33 | 34 | public void setMethod(Method method) { 35 | this.method = method; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/JSONValidatingReader.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | public class JSONValidatingReader extends JSONReader { 4 | public static final Object INVALID = new Object(); 5 | private JSONValidator validator; 6 | 7 | public JSONValidatingReader(JSONValidator validator) { 8 | this.validator = validator; 9 | } 10 | 11 | public JSONValidatingReader(JSONErrorListener listener) { 12 | this(new JSONValidator(listener)); 13 | } 14 | 15 | public JSONValidatingReader() { 16 | this(new StdoutStreamErrorListener()); 17 | } 18 | 19 | public Object read(String string) { 20 | if (!validator.validate(string)) return INVALID; 21 | return super.read(string); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/MapUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 4 | import org.w3c.dom.Element; 5 | 6 | import java.util.Map; 7 | 8 | /** 9 | * map 相关工具 10 | * 11 | * @author http://arccode.net 12 | * @since 2015-11-05 13 | */ 14 | public class MapUtils { 15 | 16 | /** 17 | * map转xml element 18 | * 19 | * @param map 20 | * @return 21 | */ 22 | public static String map2XmlString(Map map) throws WXPayApiException { 23 | Element root = XmlUtils.createRootElement("xml"); 24 | for (String key : map.keySet()) { 25 | XmlUtils.appendCDATAElement(root, key, map.get(key)); 26 | } 27 | 28 | return XmlUtils.nodeToString(root); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/service/IWXPayClient.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.service; 2 | 3 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | import net.arccode.wechat.pay.api.protocol.base.WXPayRequest; 6 | 7 | /** 8 | * 接口调用入口 9 | * 10 | * @author http://arccode.net 11 | * @since 2015-11-05 12 | */ 13 | public interface IWXPayClient { 14 | 15 | /** 16 | * 执行api调用 17 | * 18 | * @param request 19 | * @param 20 | * @return 21 | */ 22 | public T execute(WXPayRequest request) throws WXPayApiException; 23 | 24 | /** 25 | * 解析微信支付异步通知数据 26 | * 27 | * @param notifyData 28 | * @param clazz 29 | * @return 30 | */ 31 | T parseNotify(String notifyData, Class clazz) throws WXPayApiException; 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/base/WXPayRequest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.base; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * 请求接口 7 | * 8 | * @author http://arccode.net 9 | * @since 2015-11-02 10 | */ 11 | public interface WXPayRequest { 12 | 13 | // TODO 添加基础字段 14 | 15 | /** 16 | * 使用何种http verb, 目前支持: GET和POST 17 | * 18 | * @return 19 | */ 20 | String getHttpVerb(); 21 | 22 | /** 23 | * 获取API请求地址 24 | * 25 | * @return API URL 26 | */ 27 | public String getApiURL(); 28 | 29 | /** 30 | * 获取所有的Key-Value形式的文本请求参数集合. 其中: 31 | *
    32 | *
  • Key: 请求参数名
  • 33 | *
  • Value: 请求参数值
  • 34 | *
35 | * 36 | * @return 应用(业务)参数集合 37 | */ 38 | public Map getApplicationParams(); 39 | 40 | 41 | /** 42 | * 得到当前API的响应结果类型 43 | * 44 | * @return 45 | */ 46 | public Class getResponseClass(); 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/parser/xml/ObjectXmlParser.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.parser.xml; 2 | 3 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 4 | import net.arccode.wechat.pay.api.common.parser.Converter; 5 | import net.arccode.wechat.pay.api.common.parser.WXPayParser; 6 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 7 | 8 | /** 9 | * xml对象解析器 10 | * 11 | * @author http://arccode.net 12 | * @since 2015-11-05 13 | */ 14 | public class ObjectXmlParser implements WXPayParser { 15 | 16 | private Class clazz; 17 | 18 | public ObjectXmlParser(Class clazz) { 19 | this.clazz = clazz; 20 | } 21 | 22 | @Override 23 | public T parse(String resp) throws WXPayApiException { 24 | Converter converter = new XmlConverter(); 25 | 26 | return converter.toResponse(resp, clazz); 27 | } 28 | 29 | @Override 30 | public Class getResponseClass() { 31 | return clazz; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/parser/json/ObjectJsonParser.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.parser.json; 2 | 3 | 4 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 5 | import net.arccode.wechat.pay.api.common.parser.Converter; 6 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 7 | import net.arccode.wechat.pay.api.common.parser.WXPayParser; 8 | 9 | /** 10 | * json对象解析器 11 | * 12 | * @author http://arccode.net 13 | * @since 2015-11-06 14 | */ 15 | public class ObjectJsonParser implements WXPayParser { 16 | 17 | private Class clazz; 18 | 19 | public ObjectJsonParser(Class clazz) { 20 | this.clazz = clazz; 21 | } 22 | 23 | @Override 24 | public T parse(String resp) throws WXPayApiException { 25 | Converter converter = new JsonConverter(); 26 | return converter.toResponse(resp, clazz); 27 | } 28 | 29 | @Override 30 | public Class getResponseClass() { 31 | return clazz; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-present, arccode 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/BufferErrorListener.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | public class BufferErrorListener implements JSONErrorListener { 4 | 5 | protected StringBuffer buffer; 6 | private String input; 7 | 8 | public BufferErrorListener(StringBuffer buffer) { 9 | this.buffer = buffer; 10 | } 11 | 12 | public BufferErrorListener() { 13 | this(new StringBuffer()); 14 | } 15 | 16 | public void start(String input) { 17 | this.input = input; 18 | buffer.setLength(0); 19 | } 20 | 21 | public void error(String type, int col) { 22 | buffer.append("expected "); 23 | buffer.append(type); 24 | buffer.append(" at column "); 25 | buffer.append(col); 26 | buffer.append("\n"); 27 | buffer.append(input); 28 | buffer.append("\n"); 29 | indent(col - 1, buffer); 30 | buffer.append("^"); 31 | } 32 | 33 | private void indent(int n, StringBuffer ret) { 34 | for (int i = 0; i < n; ++i) { 35 | ret.append(' '); 36 | } 37 | } 38 | 39 | public void end() { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/RequestParametersHolder.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | /** 4 | * 请求参数分类 5 | * 6 | * @author http://arccode.net 7 | * @since 2015-11-05 8 | */ 9 | public class RequestParametersHolder { 10 | 11 | /** 12 | * 协议必选参数 13 | */ 14 | private ACHashMap protocalMustParams; 15 | 16 | /** 17 | * 协议可选参数 18 | */ 19 | private ACHashMap protocalOptParams; 20 | 21 | /** 22 | * 应用参数 23 | */ 24 | private ACHashMap applicationParams; 25 | 26 | public ACHashMap getProtocalMustParams() { 27 | return protocalMustParams; 28 | } 29 | public void setProtocalMustParams(ACHashMap protocalMustParams) { 30 | this.protocalMustParams = protocalMustParams; 31 | } 32 | public ACHashMap getProtocalOptParams() { 33 | return protocalOptParams; 34 | } 35 | public void setProtocalOptParams(ACHashMap protocalOptParams) { 36 | this.protocalOptParams = protocalOptParams; 37 | } 38 | public ACHashMap getApplicationParams() { 39 | return applicationParams; 40 | } 41 | public void setApplicationParams(ACHashMap applicationParams) { 42 | this.applicationParams = applicationParams; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/exception/WXPayApiException.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.exception; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * API调用异常基类 7 | * 8 | * @author http://arccode.net 9 | * @since 2015-11-02 10 | */ 11 | public class WXPayApiException extends Exception { 12 | private static final long serialVersionUID = -7457798164814346771L; 13 | 14 | /** 15 | * 错误码 16 | */ 17 | private String errCode; 18 | 19 | /** 20 | * 错误消息 21 | */ 22 | private String errMsg; 23 | 24 | public WXPayApiException() { 25 | super(); 26 | } 27 | 28 | public WXPayApiException(String message) { 29 | super(message); 30 | } 31 | 32 | public WXPayApiException(Throwable cause) { 33 | super(cause); 34 | } 35 | 36 | public WXPayApiException(String message, Throwable cause) { 37 | super(message, cause); 38 | } 39 | 40 | public WXPayApiException(String errCode, String errMsg) { 41 | super(errCode + ":" + errMsg); 42 | this.errCode = errCode; 43 | this.errMsg = errMsg; 44 | } 45 | 46 | public String getErrCode() { 47 | return errCode; 48 | } 49 | 50 | public String getErrMsg() { 51 | return errMsg; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/parser/Reader.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.parser; 2 | 3 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 9 | * 10 | * @author http://arccode.net 11 | * @since 2015-11-05 12 | */ 13 | public interface Reader { 14 | 15 | /** 16 | * 判断返回结果是否包含指定的属性。 17 | * 18 | * @param name 属性名称 19 | * @return true/false 20 | */ 21 | public boolean hasReturnField(Object name); 22 | 23 | /** 24 | * 读取单个基本对象。 25 | * 26 | * @param name 映射名称 27 | * @return 基本对象值 28 | */ 29 | public Object getPrimitiveObject(Object name); 30 | 31 | /** 32 | * 读取单个自定义对象。 33 | * 34 | * @param name 映射名称 35 | * @param type 映射类型 36 | * @return 映射类型的实例 37 | * @throws WXPayApiException 38 | */ 39 | public Object getObject(Object name, Class type) throws WXPayApiException; 40 | 41 | /** 42 | * 读取多个对象的值。 43 | * 44 | * @param listName 列表名称 45 | * @param itemName 映射名称 46 | * @param subType 嵌套映射类型 47 | * @return 嵌套映射类型实例列表 48 | * @throws WXPayApiException 49 | */ 50 | public List getListObjects(Object listName, Object itemName, Class subType) 51 | throws WXPayApiException; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | UTF-8 9 | 10 | %X{sessionId} %date [%thread] %-5level %logger{80} - %msg%n 11 | 12 | 13 | 14 | 16 | /Users/arccode/data/logs/wx-pay-sdk.log 17 | UTF-8 18 | 20 | /Users/arccode/data/logs/wx-pay-sdk.log.%d{yyyy-MM-dd} 21 | 22 | 23 | %X{sessionId} %date [%thread] %-5level %logger{80} - %msg%n 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/JSONValidatingWriter.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | public class JSONValidatingWriter extends JSONWriter { 4 | 5 | private JSONValidator validator; 6 | 7 | public JSONValidatingWriter(JSONValidator validator, boolean emitClassName) { 8 | super(emitClassName); 9 | this.validator = validator; 10 | } 11 | 12 | public JSONValidatingWriter(JSONValidator validator) { 13 | this.validator = validator; 14 | } 15 | 16 | public JSONValidatingWriter(JSONErrorListener listener, boolean emitClassName) { 17 | this(new JSONValidator(listener), emitClassName); 18 | } 19 | 20 | public JSONValidatingWriter(JSONErrorListener listener) { 21 | this(new JSONValidator(listener)); 22 | } 23 | 24 | public JSONValidatingWriter() { 25 | this(new StdoutStreamErrorListener()); 26 | } 27 | 28 | public JSONValidatingWriter(boolean emitClassName) { 29 | this(new StdoutStreamErrorListener(), emitClassName); 30 | } 31 | 32 | private String validate(String text) { 33 | validator.validate(text); 34 | return text; 35 | } 36 | 37 | public String write(Object object) { 38 | return validate(super.write(object)); 39 | } 40 | 41 | public String write(long n) { 42 | return validate(super.write(n)); 43 | } 44 | 45 | public String write(double d) { 46 | return validate(super.write(d)); 47 | } 48 | 49 | public String write(char c) { 50 | return validate(super.write(c)); 51 | } 52 | 53 | public String write(boolean b) { 54 | return validate(super.write(b)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/base/WXPayResponse.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.base; 2 | 3 | import net.arccode.wechat.pay.api.common.annotation.ApiField; 4 | 5 | import java.io.Serializable; 6 | import java.util.Map; 7 | 8 | /** 9 | * API基础响应信息 10 | * 11 | * @author http://arccode.net 12 | * @since 2015-11-02 13 | */ 14 | public abstract class WXPayResponse implements Serializable { 15 | private static final long serialVersionUID = 819761681742723087L; 16 | 17 | /** 18 | * 返回状态码 19 | */ 20 | @ApiField("return_code") 21 | private String returnCode; 22 | 23 | /** 24 | * 返回信息 25 | */ 26 | @ApiField("return_msg") 27 | private String returnMsg; 28 | 29 | /** 30 | * 响应字符串 31 | */ 32 | private String body; 33 | 34 | /** 35 | * API请求参数 36 | */ 37 | private Map params; 38 | 39 | public String getReturnCode() { 40 | return returnCode; 41 | } 42 | 43 | public void setReturnCode(String returnCode) { 44 | this.returnCode = returnCode; 45 | } 46 | 47 | public String getReturnMsg() { 48 | return returnMsg; 49 | } 50 | 51 | public void setReturnMsg(String returnMsg) { 52 | this.returnMsg = returnMsg; 53 | } 54 | 55 | public String getBody() { 56 | return body; 57 | } 58 | 59 | public void setBody(String body) { 60 | this.body = body; 61 | } 62 | 63 | public Map getParams() { 64 | return params; 65 | } 66 | 67 | public void setParams(Map params) { 68 | this.params = params; 69 | } 70 | 71 | public boolean isSuccess() { 72 | return "SUCCESS".equalsIgnoreCase(this.returnCode); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/SDKUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | *
 7 |  * sdk 使用的工具
 8 |  * 
9 | * 10 | * @author http://arccode.net 11 | * @since 2015-09-18 12 | */ 13 | public class SDKUtils { 14 | 15 | /** 16 | * 构造订单号 17 | * @return 18 | */ 19 | public static String genOrderNo() { 20 | 21 | return "O" + DateUtils.convertDate2String("yyMMddHHmmssSSS") + RandomUtils.randomNInt(8); 22 | } 23 | 24 | /** 25 | * 构造结算号 26 | * @return 27 | */ 28 | public static String genSettleNo() { 29 | 30 | return "S" + DateUtils.convertDate2String("yyMMddHHmmssSSS") + RandomUtils.randomNInt(8); 31 | } 32 | 33 | /** 34 | * 构造付款流水号 35 | * @return 36 | */ 37 | public static String genOutTradeNo() { 38 | 39 | return "T" + DateUtils.convertDate2String("yyMMddHHmmssSSS") + RandomUtils.randomNInt(8); 40 | } 41 | 42 | /** 43 | * 构造退款流水号 44 | * @return 45 | */ 46 | public static String genOutRefundNo() { 47 | 48 | return "R" + DateUtils.convertDate2String("yyMMddHHmmssSSS") + RandomUtils.randomNInt(8); 49 | } 50 | 51 | /** 52 | * 获取一定长度的随机字符串 53 | * @param length 指定字符串长度 54 | * @return 一定长度的字符串 55 | */ 56 | public static String genRandomStringByLength(int length) { 57 | String base = "abcdefghijklmnopqrstuvwxyz0123456789"; 58 | Random random = new Random(); 59 | StringBuffer sb = new StringBuffer(); 60 | for (int i = 0; i < length; i++) { 61 | int number = random.nextInt(base.length()); 62 | sb.append(base.charAt(number)); 63 | } 64 | return sb.toString(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/assembly/src.xml: -------------------------------------------------------------------------------- 1 | 18 | 21 | bundle 22 | 23 | zip 24 | 25 | 26 | 27 | 28 | ${project.basedir}/README* 29 | ${project.basedir}/LICENSE* 30 | ${project.basedir}/NOTICE* 31 | 32 | 33 | 34 | ${project.build.directory} 35 | lib 36 | 37 | *.jar 38 | 39 | 40 | 41 | ${project.build.directory}/site 42 | docs 43 | 44 | **/*.* 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/RandomUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | 4 | import java.security.SecureRandom; 5 | import java.util.Random; 6 | 7 | /** 8 | * 描述: 随机数工具类 9 | * 10 | * @author http://arccode.net 11 | * @since 2015-05-25 12 | */ 13 | public class RandomUtils { 14 | 15 | private static Random random; 16 | 17 | /** 18 | * 获取一定长度的随机字符串 19 | * @param length 指定字符串长度 20 | * @return 一定长度的字符串 21 | */ 22 | public static String randomNInt(int length) { 23 | String base = "0123456789"; 24 | Random random = new Random(); 25 | StringBuffer sb = new StringBuffer(); 26 | for (int i = 0; i < length; i++) { 27 | int number = random.nextInt(base.length()); 28 | sb.append(base.charAt(number)); 29 | } 30 | return sb.toString(); 31 | } 32 | 33 | /** 34 | * 返回8位随机整数 35 | * @return 36 | */ 37 | public static int randomInt() { 38 | if (random == null) { 39 | random = new Random(); 40 | } 41 | int ret = random.nextInt(99999999) + 1; 42 | return ret; 43 | } 44 | 45 | /** 46 | * 返回随机长整数 47 | * @return 48 | */ 49 | public static long randomLong() { 50 | if (random == null) { 51 | random = new Random(); 52 | } 53 | long ret = random.nextLong() + 1; 54 | return ret; 55 | } 56 | 57 | public static String randomKey() { 58 | byte[] keyBytes = new byte[16]; 59 | new SecureRandom().nextBytes(keyBytes); 60 | return new String(hex(keyBytes)); 61 | } 62 | 63 | private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 64 | 'f'}; 65 | private static String hex(byte[] input) { 66 | StringBuffer buf = new StringBuffer(); 67 | for (int j = 0; j < input.length; j++) { 68 | buf.append(DIGITS[(input[j] >> 4) & 0x0f]); 69 | buf.append(DIGITS[input[j] & 0x0f]); 70 | } 71 | return buf.toString(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/ACHashMap.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | 5 | import java.text.DateFormat; 6 | import java.text.SimpleDateFormat; 7 | import java.util.*; 8 | 9 | /** 10 | * 纯字符串字典结构 11 | * 12 | * @author http://arccode.net 13 | * @since 2015-11-02 14 | */ 15 | public class ACHashMap extends HashMap { 16 | private static final long serialVersionUID = 3881590349447518907L; 17 | 18 | public ACHashMap() { 19 | super(); 20 | } 21 | 22 | public ACHashMap(Map map) { 23 | super(map); 24 | } 25 | 26 | public String put(String key, Object value) { 27 | String strValue; 28 | 29 | if (value == null) { 30 | strValue = null; 31 | } else if (value instanceof String) { 32 | strValue = (String) value; 33 | } else if (value instanceof Integer) { 34 | strValue = ((Integer) value).toString(); 35 | } else if (value instanceof Long) { 36 | strValue = ((Long) value).toString(); 37 | } else if (value instanceof Float) { 38 | strValue = ((Float) value).toString(); 39 | } else if (value instanceof Double) { 40 | strValue = ((Double) value).toString(); 41 | } else if (value instanceof Boolean) { 42 | strValue = ((Boolean) value).toString(); 43 | } else if (value instanceof Date) { 44 | DateFormat format = new SimpleDateFormat(WXPayConstants.DATE_TIME_FORMAT); 45 | format.setTimeZone(TimeZone.getTimeZone(WXPayConstants.DATE_TIMEZONE)); 46 | strValue = format.format((Date) value); 47 | } else { 48 | strValue = value.toString(); 49 | } 50 | 51 | return this.put(key, strValue); 52 | } 53 | 54 | public String put(String key, String value) { 55 | if (StringUtils.areNotEmpty(key, value)) { 56 | return super.put(key, value); 57 | } else { 58 | return null; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/ImageUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import sun.misc.BASE64Decoder; 4 | import sun.misc.BASE64Encoder; 5 | 6 | import javax.imageio.ImageIO; 7 | import java.awt.image.BufferedImage; 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | /** 14 | * 描述: 图片工具类 15 | * 16 | * @author http://arccode.net 17 | * @since 2015-05-28 18 | */ 19 | public class ImageUtils { 20 | 21 | /** 22 | *
23 |      * Base64编码的字符解码为图片
24 |      *
25 |      * 
26 | * 27 | * @param imageString 28 | * @return 29 | */ 30 | public static BufferedImage decodeToImage(String imageString) { 31 | 32 | BufferedImage image = null; 33 | byte[] imageByte; 34 | try { 35 | BASE64Decoder decoder = new BASE64Decoder(); 36 | imageByte = decoder.decodeBuffer(imageString); 37 | ByteArrayInputStream bis = new ByteArrayInputStream(imageByte); 38 | image = ImageIO.read(bis); 39 | bis.close(); 40 | } catch (Exception e) { 41 | throw new RuntimeException(e); 42 | } 43 | return image; 44 | } 45 | 46 | /** 47 | *
48 |      * Base64编码的字符解码为字节数组流
49 |      *
50 |      * 
51 | * 52 | * @param imageString 53 | * @return 54 | */ 55 | public static ByteArrayInputStream decodeToStream(String imageString) { 56 | 57 | ByteArrayInputStream bis = null; 58 | byte[] imageByte; 59 | try { 60 | BASE64Decoder decoder = new BASE64Decoder(); 61 | imageByte = decoder.decodeBuffer(imageString); 62 | bis = new ByteArrayInputStream(imageByte); 63 | bis.close(); 64 | } catch (Exception e) { 65 | throw new RuntimeException(e); 66 | } 67 | return bis; 68 | } 69 | 70 | /** 71 | *
72 |      * 图片文件转化为Base64编码字符串
73 |      *
74 |      * 
75 | * 76 | * @param image 77 | * @param type 78 | * @return 79 | */ 80 | public static String encodeToString(BufferedImage image, String type) { 81 | String imageString = null; 82 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 83 | 84 | try { 85 | ImageIO.write(image, type, bos); 86 | byte[] imageBytes = bos.toByteArray(); 87 | 88 | BASE64Encoder encoder = new BASE64Encoder(); 89 | imageString = encoder.encode(imageBytes); 90 | 91 | bos.close(); 92 | } catch (IOException e) { 93 | throw new RuntimeException(e); 94 | } 95 | return imageString.replaceAll("\\n", ""); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/query_order/QueryOrderRequest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.query_order; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | import net.arccode.wechat.pay.api.common.util.ACHashMap; 5 | import net.arccode.wechat.pay.api.protocol.base.WXPayRequest; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * 查询订单详情 11 | *

12 | *

13 | * 详见: https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_2&index=4 14 | * 15 | * @author http://arccode.net 16 | * @since 2018-04-22 17 | */ 18 | public class QueryOrderRequest implements WXPayRequest { 19 | 20 | /**==================== 协议应用参数 ====================**/ 21 | 22 | /** 23 | * 微信订单号 24 | * 必选: 否, 但需要与商户订单号二选一 25 | * String(32) 1009660380201506130728806387 微信的订单号,优先使用 26 | */ 27 | private String transactionId; 28 | /** 29 | * 商户订单号 30 | * 必选: 否, 但需要与微信订单号二选一 31 | * String(32) 20150806125346 商户系统内部的订单号,当没提供transaction_id时需要传这个。 32 | */ 33 | private String outTradeNo; 34 | 35 | /** 36 | * 随机字符串 必填: 是 37 | *

38 | * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS String(32) 随机字符串,不长于32位 39 | */ 40 | private String nonceStr; 41 | 42 | public QueryOrderRequest() { 43 | } 44 | 45 | public QueryOrderRequest(String transactionId, String outTradeNo, String nonceStr) { 46 | this.transactionId = transactionId; 47 | this.outTradeNo = outTradeNo; 48 | this.nonceStr = nonceStr; 49 | } 50 | 51 | @Override 52 | public String getHttpVerb() { 53 | return WXPayConstants.HTTP_POST; 54 | } 55 | 56 | @Override 57 | public String getApiURL() { 58 | return WXPayConstants.QUERY_ORDER; 59 | } 60 | 61 | @Override 62 | public Map getApplicationParams() { 63 | ACHashMap txtParams = new ACHashMap(); 64 | txtParams.put("transaction_id", this.transactionId); 65 | txtParams.put("out_trade_no", this.outTradeNo); 66 | txtParams.put("nonce_str", this.nonceStr); 67 | return txtParams; 68 | } 69 | 70 | @Override 71 | public Class getResponseClass() { 72 | return QueryOrderResponse.class; 73 | } 74 | 75 | public String getTransactionId() { 76 | return transactionId; 77 | } 78 | 79 | public void setTransactionId(String transactionId) { 80 | this.transactionId = transactionId; 81 | } 82 | 83 | public String getOutTradeNo() { 84 | return outTradeNo; 85 | } 86 | 87 | public void setOutTradeNo(String outTradeNo) { 88 | this.outTradeNo = outTradeNo; 89 | } 90 | 91 | public String getNonceStr() { 92 | return nonceStr; 93 | } 94 | 95 | public void setNonceStr(String nonceStr) { 96 | this.nonceStr = nonceStr; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/constant/WXPayConstants.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.constant; 2 | 3 | /** 4 | * 微信常量 5 | * 6 | * @author http://arccode.net 7 | * @since 2015-11-02 8 | */ 9 | public class WXPayConstants { 10 | 11 | /**==================== 基础常量 ====================**/ 12 | 13 | public static final String SDK_VERSION = "wx-sdk-0_0_1"; 14 | 15 | public static final String APP_ID = "appid"; 16 | public static final String MCH_ID = "mch_id"; 17 | 18 | // 针对商户支付 19 | public static final String MCH_PAY_APPID = "mch_appid"; 20 | public static final String MCH_PAY_ID = "mchid"; 21 | 22 | public static final String NONCE_STR = "nonce_str"; 23 | public static final String NOTIFY_URL = "notify_url"; 24 | public static final String TRADE_TYPE = "trade_type"; 25 | public static final String FEE_TYPE = "fee_type"; 26 | public static final String LIMIT_PAY = "limit_pay"; 27 | 28 | 29 | public static final String SIGN_TYPE = "sign_type"; 30 | public static final String SIGN = "sign"; 31 | public static final String CHARSET = "charset"; 32 | public static final String CHARSET_UTF8 = "UTF-8"; 33 | 34 | 35 | public static final String SIGN_TYPE_MD5 = "MD5"; 36 | public static final String SIGN_TYPE_RSA = "RSA"; 37 | 38 | /** 39 | * 响应格式 JSON 40 | */ 41 | public static final String FORMAT_JSON = "json"; 42 | 43 | /** 44 | * 响应格式 XML 45 | */ 46 | public static final String FORMAT_XML = "xml"; 47 | 48 | /**==================== 通用常量 ====================**/ 49 | 50 | /** 51 | * 默认时间格式 52 | */ 53 | public static final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"; 54 | 55 | /** 56 | * Date默认时区 57 | */ 58 | public static final String DATE_TIMEZONE = "GMT+8"; 59 | 60 | /**==================== 接口常量 ====================**/ 61 | 62 | /** 63 | * 统一下单接口 64 | */ 65 | public static final String UNIFIED_ORDER_API = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 66 | 67 | /** 68 | * 退款 69 | */ 70 | public static final String REFUND_API = "https://api.mch.weixin.qq.com/secapi/pay/refund"; 71 | 72 | /** 73 | * 商户支付接口 74 | */ 75 | public static final String MCH_PAY_API = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; 76 | 77 | /** 78 | * 查询订单 79 | */ 80 | public static final String QUERY_ORDER = "https://api.mch.weixin.qq.com/pay/orderquery"; 81 | 82 | /**==================== 商户常量 ====================**/ 83 | 84 | 85 | 86 | /** 87 | * http get 88 | */ 89 | public static final String HTTP_GET = "GET"; 90 | 91 | /** 92 | * http post 93 | */ 94 | public static final String HTTP_POST = "POST"; 95 | 96 | /** 97 | * https post 带证书, 服务于商户支付 98 | */ 99 | public static final String HTTPS_POST_CA_MCH_PAY = "POST_CA_FOR_MCH_PAY"; 100 | 101 | /** 102 | * https post 带证书, 服务于客户退款 103 | */ 104 | public static final String HTTPS_POST_CA_REFUND = "POST_CA_FOR_REFUND"; 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/WXPaySignUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 4 | 5 | import java.security.MessageDigest; 6 | import java.util.*; 7 | 8 | /** 9 | * 签名工具 10 | * 11 | * @author http://arccode.net 12 | * @since 2015-11-05 13 | */ 14 | public class WXPaySignUtils { 15 | 16 | /** 17 | * 签名排序并连接成字符串 18 | * 19 | * @param requestParametersHolder 20 | * @return 21 | */ 22 | public static String getSignatureContent(RequestParametersHolder requestParametersHolder) { 23 | Map sortedParams = new TreeMap(); 24 | ACHashMap appParams = requestParametersHolder.getApplicationParams(); 25 | if (appParams != null && appParams.size() > 0) { 26 | sortedParams.putAll(appParams); 27 | } 28 | 29 | ACHashMap protocolMustParams = requestParametersHolder.getProtocalMustParams(); 30 | if (protocolMustParams != null && protocolMustParams.size() > 0) { 31 | sortedParams.putAll(protocolMustParams); 32 | } 33 | 34 | ACHashMap protocolOptParams = requestParametersHolder.getProtocalOptParams(); 35 | if (protocolOptParams != null && protocolOptParams.size() > 0) { 36 | sortedParams.putAll(protocolOptParams); 37 | } 38 | 39 | return getSignContent(sortedParams); 40 | } 41 | 42 | /** 43 | * @param sortedParams 44 | * @return 45 | */ 46 | public static String getSignContent(Map sortedParams) { 47 | StringBuffer content = new StringBuffer(); 48 | List keys = new ArrayList(sortedParams.keySet()); 49 | Collections.sort(keys); 50 | int index = 0; 51 | for (int i = 0; i < keys.size(); i++) { 52 | String key = keys.get(i); 53 | String value = sortedParams.get(key); 54 | if (StringUtils.areNotEmpty(key, value)) { 55 | content.append((index == 0 ? "" : "&") + key + "=" + value); 56 | index++; 57 | } 58 | } 59 | 60 | return content.toString(); 61 | } 62 | 63 | /** 64 | * 返回md5签名后的值 65 | * 66 | * @param signContent 67 | * @param key 68 | * @return 69 | */ 70 | public static String md5Sign(String signContent, String key, String charset) throws WXPayApiException { 71 | try { 72 | byte[] digest = MessageDigest.getInstance("MD5").digest((signContent + "&key=" + key).getBytes(charset)); 73 | return hex(digest); 74 | } catch (Exception e) { 75 | throw new WXPayApiException(e); 76 | } 77 | } 78 | 79 | // MD5 digest 80 | private static final char[] DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 81 | 82 | private static String hex(byte[] input) { 83 | StringBuffer buf = new StringBuffer(); 84 | for (int j = 0; j < input.length; j++) { 85 | buf.append(DIGITS[(input[j] >> 4) & 0x0f]); 86 | buf.append(DIGITS[input[j] & 0x0f]); 87 | } 88 | return buf.toString(); 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/unified_order/UnifiedOrderResponse.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.unified_order; 2 | 3 | import net.arccode.wechat.pay.api.common.annotation.ApiField; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | 6 | /** 7 | * 扫码支付下单响应参数 8 | * 9 | * @author http://arccode.net 10 | * @since 2015-11-02 11 | */ 12 | public class UnifiedOrderResponse extends WXPayResponse { 13 | private static final long serialVersionUID = -116106125361949648L; 14 | 15 | /**==================== 以下字段在return_code为SUCCESS的时候有返回 ====================**/ 16 | 17 | /** 18 | * 公众号ID 19 | */ 20 | @ApiField("appid") 21 | private String appId; 22 | 23 | /** 24 | * 商户号 25 | */ 26 | @ApiField("mch_id") 27 | private String mchId; 28 | 29 | /** 30 | * 设备号 31 | */ 32 | @ApiField("device_info") 33 | private String deviceInfo; 34 | 35 | /** 36 | * 随机字符串 37 | */ 38 | @ApiField("nonce_str") 39 | private String nonceStr; 40 | 41 | /** 42 | * 签名 43 | */ 44 | @ApiField("sign") 45 | private String sign; 46 | 47 | /** 48 | * 业务结果 49 | */ 50 | @ApiField("result_code") 51 | private String resultCode; 52 | 53 | /** 54 | * 错误代码 55 | */ 56 | @ApiField("err_code") 57 | private String errCode; 58 | 59 | /** 60 | * 错误代码描述 61 | */ 62 | @ApiField("err_code_des") 63 | private String errCodeDes; 64 | 65 | /**==================== 以下字段在return_code 和result_code都为SUCCESS的时候有返回 ====================**/ 66 | 67 | /** 68 | * 交易类型 69 | */ 70 | @ApiField("trade_type") 71 | private String tradeType; 72 | 73 | /** 74 | * 预支付交易会话标识 75 | */ 76 | @ApiField("prepay_id") 77 | private String prepayId; 78 | 79 | /** 80 | * 二维码链接 81 | */ 82 | @ApiField("code_url") 83 | private String codeUrl; 84 | 85 | public String getAppId() { 86 | return appId; 87 | } 88 | 89 | public void setAppId(String appId) { 90 | this.appId = appId; 91 | } 92 | 93 | public String getMchId() { 94 | return mchId; 95 | } 96 | 97 | public void setMchId(String mchId) { 98 | this.mchId = mchId; 99 | } 100 | 101 | public String getDeviceInfo() { 102 | return deviceInfo; 103 | } 104 | 105 | public void setDeviceInfo(String deviceInfo) { 106 | this.deviceInfo = deviceInfo; 107 | } 108 | 109 | public String getNonceStr() { 110 | return nonceStr; 111 | } 112 | 113 | public void setNonceStr(String nonceStr) { 114 | this.nonceStr = nonceStr; 115 | } 116 | 117 | public String getSign() { 118 | return sign; 119 | } 120 | 121 | public void setSign(String sign) { 122 | this.sign = sign; 123 | } 124 | 125 | public String getResultCode() { 126 | return resultCode; 127 | } 128 | 129 | public void setResultCode(String resultCode) { 130 | this.resultCode = resultCode; 131 | } 132 | 133 | public String getErrCode() { 134 | return errCode; 135 | } 136 | 137 | public void setErrCode(String errCode) { 138 | this.errCode = errCode; 139 | } 140 | 141 | public String getErrCodeDes() { 142 | return errCodeDes; 143 | } 144 | 145 | public void setErrCodeDes(String errCodeDes) { 146 | this.errCodeDes = errCodeDes; 147 | } 148 | 149 | public String getTradeType() { 150 | return tradeType; 151 | } 152 | 153 | public void setTradeType(String tradeType) { 154 | this.tradeType = tradeType; 155 | } 156 | 157 | public String getPrepayId() { 158 | return prepayId; 159 | } 160 | 161 | public void setPrepayId(String prepayId) { 162 | this.prepayId = prepayId; 163 | } 164 | 165 | public String getCodeUrl() { 166 | return codeUrl; 167 | } 168 | 169 | public void setCodeUrl(String codeUrl) { 170 | this.codeUrl = codeUrl; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/parser/xml/XmlConverter.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.parser.xml; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 5 | import net.arccode.wechat.pay.api.common.parser.Converter; 6 | import net.arccode.wechat.pay.api.common.util.Converters; 7 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 8 | import net.arccode.wechat.pay.api.common.parser.Reader; 9 | import net.arccode.wechat.pay.api.common.util.XmlUtils; 10 | import org.w3c.dom.Element; 11 | 12 | import java.text.DateFormat; 13 | import java.text.ParseException; 14 | import java.text.SimpleDateFormat; 15 | import java.util.ArrayList; 16 | import java.util.Date; 17 | import java.util.List; 18 | 19 | /** 20 | * xml格式转换 21 | * 22 | * @author http://arccode.net 23 | * @since 2015-11-05 24 | */ 25 | public class XmlConverter implements Converter { 26 | 27 | @Override 28 | public T toResponse(String resp, Class clazz) throws WXPayApiException { 29 | 30 | Element root = XmlUtils.getRootElementFromString(resp); 31 | return getModelFromXML(root, clazz); 32 | } 33 | 34 | private T getModelFromXML(final Element element, Class clazz) throws WXPayApiException { 35 | if (element == null) 36 | return null; 37 | 38 | return Converters.convert(clazz, new Reader() { 39 | public boolean hasReturnField(Object name) { 40 | Element childE = XmlUtils.getChildElement(element, (String) name); 41 | return childE != null; 42 | } 43 | 44 | public Object getPrimitiveObject(Object name) { 45 | return XmlUtils.getElementValue(element, (String) name); 46 | } 47 | 48 | public Object getObject(Object name, Class type) throws WXPayApiException { 49 | Element childE = XmlUtils.getChildElement(element, (String) name); 50 | if (childE != null) { 51 | return getModelFromXML(childE, type); 52 | } else { 53 | return null; 54 | } 55 | } 56 | 57 | public List getListObjects(Object listName, Object itemName, Class subType) 58 | throws WXPayApiException { 59 | List list = null; 60 | Element listE = XmlUtils.getChildElement(element, (String) listName); 61 | 62 | if (listE != null) { 63 | list = new ArrayList(); 64 | List itemEs = XmlUtils.getChildElements(listE, (String) itemName); 65 | for (Element itemE : itemEs) { 66 | Object obj = null; 67 | String value = XmlUtils.getElementValue(itemE); 68 | 69 | if (String.class.isAssignableFrom(subType)) { 70 | obj = value; 71 | } else if (Long.class.isAssignableFrom(subType)) { 72 | obj = Long.valueOf(value); 73 | } else if (Integer.class.isAssignableFrom(subType)) { 74 | obj = Integer.valueOf(value); 75 | } else if (Boolean.class.isAssignableFrom(subType)) { 76 | obj = Boolean.valueOf(value); 77 | } else if (Date.class.isAssignableFrom(subType)) { 78 | DateFormat format = new SimpleDateFormat( 79 | WXPayConstants.DATE_TIME_FORMAT); 80 | try { 81 | obj = format.parse(value); 82 | } catch (ParseException e) { 83 | throw new WXPayApiException(e); 84 | } 85 | } else { 86 | obj = getModelFromXML(itemE, subType); 87 | } 88 | if (obj != null) 89 | list.add(obj); 90 | } 91 | } 92 | return list; 93 | } 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/DateUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import java.text.ParseException; 4 | import java.text.SimpleDateFormat; 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | import java.util.TimeZone; 8 | 9 | /** 10 | * 描述: 日期工具类 11 | * 12 | * @author http://arccode.net 13 | * @since 2015-05-25 14 | */ 15 | public class DateUtils { 16 | 17 | /** 18 | * 日期类型转字符串 19 | * @param dateFormat 格式化格式, eg: yyyy-MM-dd, yyyy-MM-dd HH:mm:ss 20 | * @return 21 | */ 22 | public static String convertDate2String(String dateFormat) { 23 | SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); 24 | String dateStr = sdf.format(getNow()); 25 | return dateStr.trim(); 26 | } 27 | 28 | /** 29 | * 日期类型转字符串 30 | * @param date java.util.Date 31 | * @param dateFormat 格式化格式, eg: yyyy-MM-dd, yyyy-MM-dd HH:mm:ss 32 | * @return 33 | */ 34 | public static String convertDate2String(Date date, String dateFormat) { 35 | 36 | if(null == date) { 37 | return ""; 38 | } 39 | SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); 40 | String dateStr = sdf.format(date); 41 | return dateStr.trim(); 42 | } 43 | 44 | /** 45 | * 日期类型转字符串, 带时区转换 46 | * @param dateFormat 格式化格式, eg: yyyy-MM-dd, yyyy-MM-dd HH:mm:ss 47 | * @return 48 | */ 49 | public static String convertDate2String(Date sourceDate, String dateFormat, TimeZone sourceTimeZone, 50 | TimeZone targetTimeZone) { 51 | 52 | if(null == sourceDate) { 53 | return ""; 54 | } 55 | 56 | Long targetTime = sourceDate.getTime() - sourceTimeZone.getRawOffset() + targetTimeZone.getRawOffset(); 57 | return convertDate2String(new Date(targetTime), dateFormat); 58 | } 59 | 60 | /** 61 | * 时区转换 62 | * 63 | * @param sourceDate 64 | * @param sourceTimeZone 65 | * @param targetTimeZone 66 | * @return 67 | */ 68 | public static Date convertTimeZone(Date sourceDate, TimeZone sourceTimeZone, TimeZone targetTimeZone) { 69 | 70 | if(null == sourceDate) { 71 | return null; 72 | } 73 | 74 | Long targetTime = sourceDate.getTime() - sourceTimeZone.getRawOffset() + targetTimeZone.getRawOffset(); 75 | return new Date(targetTime); 76 | } 77 | 78 | /** 79 | * 字符串转日期类型 80 | * @param dateStr 81 | * @param dateFormat 82 | * @return 83 | */ 84 | public static Date convertString2Date(String dateStr, String dateFormat) throws ParseException { 85 | if(null == dateStr || "".equals(dateStr.trim())) { 86 | return null; 87 | } 88 | 89 | SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); 90 | Date result = sdf.parse(dateStr); 91 | 92 | return result; 93 | } 94 | 95 | /** 96 | * 获取指定的date, 参看calendar api 97 | * @param field 98 | * @param amount 99 | * @return 100 | */ 101 | public static Date getDefineTime(int field, int amount) { 102 | Calendar calendar = Calendar.getInstance(); 103 | calendar.add(field, amount); 104 | return calendar.getTime(); 105 | } 106 | 107 | /** 108 | * 获取指定日期的起始值, e.g: 2014-11-11 10:11:22 -> 2014-11-11 00:00:00 109 | * @param date 110 | * @return 111 | */ 112 | public static Date getDateStartByDate(Date date) { 113 | 114 | Calendar calendar = Calendar.getInstance(); 115 | calendar.setTime(date); 116 | calendar.set(Calendar.HOUR_OF_DAY, 0); 117 | calendar.set(Calendar.MINUTE, 0); 118 | calendar.set(Calendar.SECOND, 0); 119 | 120 | return calendar.getTime(); 121 | } 122 | 123 | /** 124 | * 获取指定日期的结束值, e.g: 2014-11-11 10:11:22 -> 2014-11-11 23:59:59 125 | * @param date 126 | * @return 127 | */ 128 | public static Date getDateEndByDate(Date date) { 129 | 130 | Calendar calendar = Calendar.getInstance(); 131 | calendar.setTime(date); 132 | calendar.set(Calendar.HOUR_OF_DAY, 23); 133 | calendar.set(Calendar.MINUTE, 59); 134 | calendar.set(Calendar.SECOND, 59); 135 | 136 | return calendar.getTime(); 137 | } 138 | 139 | /** 140 | * 返回当前日期 141 | * @return 142 | */ 143 | public static Date getNow() { 144 | Calendar calendar = Calendar.getInstance(); 145 | return calendar.getTime(); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/refund/RefundRequest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.refund; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | import net.arccode.wechat.pay.api.common.util.ACHashMap; 5 | import net.arccode.wechat.pay.api.protocol.base.WXPayRequest; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * 微信支付申请退款 11 | * 12 | * 详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_4&index=6 13 | * 14 | * @author http://arccode.net 15 | * @since 2015-11-02 16 | */ 17 | public class RefundRequest implements WXPayRequest { 18 | 19 | /**==================== 协议可选参数 ====================**/ 20 | 21 | /** 22 | * 货币种类, 可空, 符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 23 | */ 24 | private String refundFeeType; 25 | 26 | /**==================== 协议应用参数 ====================**/ 27 | 28 | /** 29 | * 设备号, 可空 30 | */ 31 | private String deviceInfo; 32 | 33 | /** 34 | * 商户订单号, 不可空 35 | */ 36 | private String outTradeNo; 37 | 38 | /** 39 | * 商户退款单号, 不可空 40 | */ 41 | private String outRefundNo; 42 | 43 | /** 44 | * 总金额(分), 不可空 45 | */ 46 | private Integer totalFee; 47 | 48 | /** 49 | * 退款金额(分), 不可空 50 | */ 51 | private Integer refundFee; 52 | 53 | /** 54 | * 操作员, 不可空, 操作员帐号, 默认为商户号 55 | */ 56 | private String opUserId; 57 | 58 | /** 59 | * 随机字符串, 不可空 60 | */ 61 | private String nonceStr; 62 | 63 | public RefundRequest() { 64 | } 65 | 66 | public RefundRequest(String outTradeNo, String outRefundNo, Integer totalFee, Integer refundFee, 67 | String opUserId, String nonceStr) { 68 | this.outTradeNo = outTradeNo; 69 | this.outRefundNo = outRefundNo; 70 | this.totalFee = totalFee; 71 | this.refundFee = refundFee; 72 | this.opUserId = opUserId; 73 | this.nonceStr = nonceStr; 74 | } 75 | 76 | @Override 77 | public String getHttpVerb() { 78 | return WXPayConstants.HTTPS_POST_CA_REFUND; 79 | } 80 | 81 | @Override 82 | public String getApiURL() { 83 | return WXPayConstants.REFUND_API; 84 | } 85 | 86 | @Override 87 | public Map getApplicationParams() { 88 | ACHashMap txtParams = new ACHashMap(); 89 | txtParams.put("device_info", this.deviceInfo); 90 | txtParams.put("out_trade_no", this.outTradeNo); 91 | txtParams.put("out_refund_no", this.outRefundNo); 92 | txtParams.put("total_fee", this.totalFee); 93 | txtParams.put("refund_fee", this.refundFee); 94 | txtParams.put("op_user_id", this.opUserId); 95 | txtParams.put("nonce_str", this.nonceStr); 96 | 97 | return txtParams; 98 | } 99 | 100 | @Override 101 | public Class getResponseClass() { 102 | return RefundResponse.class; 103 | } 104 | 105 | public String getRefundFeeType() { 106 | return refundFeeType; 107 | } 108 | 109 | public void setRefundFeeType(String refundFeeType) { 110 | this.refundFeeType = refundFeeType; 111 | } 112 | 113 | public String getDeviceInfo() { 114 | return deviceInfo; 115 | } 116 | 117 | public void setDeviceInfo(String deviceInfo) { 118 | this.deviceInfo = deviceInfo; 119 | } 120 | 121 | public String getOutTradeNo() { 122 | return outTradeNo; 123 | } 124 | 125 | public void setOutTradeNo(String outTradeNo) { 126 | this.outTradeNo = outTradeNo; 127 | } 128 | 129 | public String getOutRefundNo() { 130 | return outRefundNo; 131 | } 132 | 133 | public void setOutRefundNo(String outRefundNo) { 134 | this.outRefundNo = outRefundNo; 135 | } 136 | 137 | public Integer getTotalFee() { 138 | return totalFee; 139 | } 140 | 141 | public void setTotalFee(Integer totalFee) { 142 | this.totalFee = totalFee; 143 | } 144 | 145 | public Integer getRefundFee() { 146 | return refundFee; 147 | } 148 | 149 | public void setRefundFee(Integer refundFee) { 150 | this.refundFee = refundFee; 151 | } 152 | 153 | public String getOpUserId() { 154 | return opUserId; 155 | } 156 | 157 | public void setOpUserId(String opUserId) { 158 | this.opUserId = opUserId; 159 | } 160 | 161 | public String getNonceStr() { 162 | return nonceStr; 163 | } 164 | 165 | public void setNonceStr(String nonceStr) { 166 | this.nonceStr = nonceStr; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/mch_pay/MchPayResponse.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.mch_pay; 2 | 3 | import net.arccode.wechat.pay.api.common.annotation.ApiField; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | 6 | /** 7 | * 扫码支付下单响应参数 8 | * 9 | * @author http://arccode.net 10 | * @since 2015-11-02 11 | */ 12 | public class MchPayResponse extends WXPayResponse { 13 | private static final long serialVersionUID = -2681586546776415340L; 14 | 15 | /**==================== 以下字段在return_code为SUCCESS的时候有返回 ====================**/ 16 | 17 | /**商户appid mch_appid 18 | * 必选: 是 19 | * 20 | * wx8888888888888888 String 微信分配的公众账号ID(企业号corpid即为此appId) 21 | */ 22 | @ApiField("mch_appid") 23 | private String mchAppId; 24 | 25 | /** 26 | * 商户号 mchid 27 | * 必选: 是 28 | * 29 | * 1900000109 String(32) 微信支付分配的商户号 30 | */ 31 | @ApiField("mchid") 32 | private String mchId; 33 | 34 | /** 35 | * 设备号 device_info 36 | * 必选: 否 37 | * 38 | * 013467007045764 String(32) 微信支付分配的终端设备号, 39 | */ 40 | @ApiField("device_info") 41 | private String deviceInfo; 42 | 43 | /** 44 | * 随机字符串 nonce_str 45 | * 必选: 是 46 | * 47 | * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS String(32) 随机字符串,不长于32位 48 | */ 49 | @ApiField("nonce_str") 50 | private String nonceStr; 51 | 52 | /** 53 | * 业务结果 result_code 54 | * 必选: 是 55 | * 56 | * SUCCESS String(16) SUCCESS/FAIL 57 | */ 58 | @ApiField("result_code") 59 | private String resultCode; 60 | 61 | /** 62 | * 错误代码 err_code 63 | * 必选: 否 64 | * 65 | * SYSTEMERROR String(32) 错误码信息 66 | */ 67 | @ApiField("err_code") 68 | private String errCode; 69 | 70 | /** 71 | * 错误代码描述 err_code_des 72 | * 必选: 否 73 | * 74 | * 系统错误 String(128) 结果信息描述 75 | */ 76 | @ApiField("err_code_des") 77 | private String errCodeDes; 78 | 79 | 80 | /**==================== 以下字段在return_code 和result_code都为SUCCESS的时候有返回 ====================**/ 81 | 82 | /** 83 | * 商户订单号 partner_trade_no 84 | * 必选: 是 85 | * 86 | * 1217752501201407033233368018 String(32) 商户订单号,需保持唯一性 87 | */ 88 | @ApiField("partner_trade_no") 89 | private String partnerTradeNo; 90 | 91 | /** 92 | * 微信订单号 payment_no 93 | * 必选: 是 94 | * 95 | * 1007752501201407033233368018 String 企业付款成功,返回的微信订单号 96 | */ 97 | @ApiField("payment_no") 98 | private String paymentNo; 99 | 100 | /** 101 | * 微信支付成功时间 payment_time 102 | * 必选: 是 103 | * 104 | * 2015-05-19 15:26:59 String 企业付款成功时间 105 | */ 106 | @ApiField("payment_time") 107 | private String paymentTime; 108 | 109 | public String getMchAppId() { 110 | return mchAppId; 111 | } 112 | 113 | public void setMchAppId(String mchAppId) { 114 | this.mchAppId = mchAppId; 115 | } 116 | 117 | public String getMchId() { 118 | return mchId; 119 | } 120 | 121 | public void setMchId(String mchId) { 122 | this.mchId = mchId; 123 | } 124 | 125 | public String getDeviceInfo() { 126 | return deviceInfo; 127 | } 128 | 129 | public void setDeviceInfo(String deviceInfo) { 130 | this.deviceInfo = deviceInfo; 131 | } 132 | 133 | public String getNonceStr() { 134 | return nonceStr; 135 | } 136 | 137 | public void setNonceStr(String nonceStr) { 138 | this.nonceStr = nonceStr; 139 | } 140 | 141 | public String getResultCode() { 142 | return resultCode; 143 | } 144 | 145 | public void setResultCode(String resultCode) { 146 | this.resultCode = resultCode; 147 | } 148 | 149 | public String getErrCode() { 150 | return errCode; 151 | } 152 | 153 | public void setErrCode(String errCode) { 154 | this.errCode = errCode; 155 | } 156 | 157 | public String getErrCodeDes() { 158 | return errCodeDes; 159 | } 160 | 161 | public void setErrCodeDes(String errCodeDes) { 162 | this.errCodeDes = errCodeDes; 163 | } 164 | 165 | public String getPartnerTradeNo() { 166 | return partnerTradeNo; 167 | } 168 | 169 | public void setPartnerTradeNo(String partnerTradeNo) { 170 | this.partnerTradeNo = partnerTradeNo; 171 | } 172 | 173 | public String getPaymentNo() { 174 | return paymentNo; 175 | } 176 | 177 | public void setPaymentNo(String paymentNo) { 178 | this.paymentNo = paymentNo; 179 | } 180 | 181 | public String getPaymentTime() { 182 | return paymentTime; 183 | } 184 | 185 | public void setPaymentTime(String paymentTime) { 186 | this.paymentTime = paymentTime; 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import java.util.regex.Matcher; 4 | import java.util.regex.Pattern; 5 | 6 | /** 7 | * 字符串工具类 8 | * 9 | * @author http://arccode.net 10 | * @since 2015-11-02 11 | */ 12 | public class StringUtils { 13 | 14 | private StringUtils() { 15 | } 16 | 17 | /** 18 | * 判断是否是ip地址 19 | * @param ip 20 | * @return 21 | */ 22 | public static boolean isIpAddress(String ip) { 23 | if (ip.length() < 7 || ip.length() > 15 || "".equals(ip)) { 24 | return false; 25 | } 26 | /** 27 | * 判断IP格式和范围 28 | */ 29 | String rexp = "([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"; 30 | Pattern regex = Pattern.compile(rexp); 31 | Matcher matcher = regex.matcher(ip); 32 | boolean isMatched = matcher.find(); 33 | 34 | return isMatched; 35 | } 36 | 37 | /** 38 | * 判断是否是Email 39 | * 40 | * @param email 41 | * @return 42 | */ 43 | public static boolean isEmailAddress(String email) { 44 | if (email.length() < 3 || "".equals(email)) { 45 | return false; 46 | } 47 | 48 | String rexp = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; 49 | Pattern regex = Pattern.compile(rexp); 50 | Matcher matcher = regex.matcher(email); 51 | boolean isMatched = matcher.find(); 52 | 53 | return isMatched; 54 | } 55 | 56 | /** 57 | * 判断是否是手机 58 | * 59 | * @param phone 60 | * @return 61 | */ 62 | public static boolean isPhoneNum(String phone) { 63 | String rexp = "^((13[0-9])|(147)|(15[^4,\\D])|(18[0-9])|(17[0-9]))\\d{8}$"; 64 | Pattern regex = Pattern.compile(rexp); 65 | Matcher matcher = regex.matcher(phone); 66 | boolean isMatched = matcher.find(); 67 | 68 | return isMatched; 69 | } 70 | 71 | /** 72 | * 检查指定的字符串是否为空。 73 | *
    74 | *
  • SysUtils.isEmpty(null) = true
  • 75 | *
  • SysUtils.isEmpty("") = true
  • 76 | *
  • SysUtils.isEmpty(" ") = true
  • 77 | *
  • SysUtils.isEmpty("abc") = false
  • 78 | *
79 | * 80 | * @param value 待检查的字符串 81 | * @return true/false 82 | */ 83 | public static boolean isEmpty(String value) { 84 | int strLen; 85 | if (value == null || (strLen = value.length()) == 0) { 86 | return true; 87 | } 88 | for (int i = 0; i < strLen; i++) { 89 | if ((Character.isWhitespace(value.charAt(i)) == false)) { 90 | return false; 91 | } 92 | } 93 | return true; 94 | } 95 | 96 | /** 97 | * 检查对象是否为数字型字符串,包含负数开头的。 98 | */ 99 | public static boolean isNumeric(Object obj) { 100 | if (obj == null) { 101 | return false; 102 | } 103 | char[] chars = obj.toString().toCharArray(); 104 | int length = chars.length; 105 | if (length < 1) 106 | return false; 107 | 108 | int i = 0; 109 | if (length > 1 && chars[0] == '-') 110 | i = 1; 111 | 112 | for (; i < length; i++) { 113 | if (!Character.isDigit(chars[i])) { 114 | return false; 115 | } 116 | } 117 | return true; 118 | } 119 | 120 | /** 121 | * 检查指定的字符串列表是否不为空。 122 | */ 123 | public static boolean areNotEmpty(String... values) { 124 | boolean result = true; 125 | if (values == null || values.length == 0) { 126 | result = false; 127 | } else { 128 | for (String value : values) { 129 | result &= !isEmpty(value); 130 | } 131 | } 132 | return result; 133 | } 134 | 135 | /** 136 | * 把通用字符编码的字符串转化为汉字编码。 137 | */ 138 | public static String unicodeToChinese(String unicode) { 139 | StringBuilder out = new StringBuilder(); 140 | if (!isEmpty(unicode)) { 141 | for (int i = 0; i < unicode.length(); i++) { 142 | out.append(unicode.charAt(i)); 143 | } 144 | } 145 | return out.toString(); 146 | } 147 | 148 | /** 149 | * 过滤不可见字符 150 | */ 151 | public static String stripNonValidXMLCharacters(String input) { 152 | if (input == null || ("".equals(input))) 153 | return ""; 154 | StringBuilder out = new StringBuilder(); 155 | char current; 156 | for (int i = 0; i < input.length(); i++) { 157 | current = input.charAt(i); 158 | if ((current == 0x9) || (current == 0xA) || (current == 0xD) 159 | || ((current >= 0x20) && (current <= 0xD7FF)) 160 | || ((current >= 0xE000) && (current <= 0xFFFD)) 161 | || ((current >= 0x10000) && (current <= 0x10FFFF))) 162 | out.append(current); 163 | } 164 | return out.toString(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/mch_pay/MchPayRequest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.mch_pay; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | import net.arccode.wechat.pay.api.common.util.ACHashMap; 5 | import net.arccode.wechat.pay.api.protocol.base.WXPayRequest; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * 商户支付API 11 | *

12 | * 详见: https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2 13 | * 14 | * @author http://arccode.net 15 | * @since 2015-12-03 16 | */ 17 | public class MchPayRequest implements WXPayRequest { 18 | 19 | /**==================== 协议可选参数 ====================**/ 20 | 21 | 22 | /**==================== 协议应用参数 ====================**/ 23 | 24 | /** 25 | * 设备号 必填: 否 26 | *

27 | * 013467007045764 String(32) 微信支付分配的终端设备号 28 | */ 29 | private String deviceInfo; 30 | 31 | /** 32 | * 商户订单号 必填: 是 33 | *

34 | * 10000098201411111234567890 String 商户订单号,需保持唯一性 35 | */ 36 | private String partnerTradeNo; 37 | 38 | /** 39 | * 用户openid 必填: 是 40 | *

41 | * oxTWIuGaIt6gTKsQRLau2M0yL16E String 商户appid下,某用户的openid 42 | */ 43 | private String openId; 44 | 45 | /** 46 | * 校验用户姓名选项 必填: 是 47 | *

48 | * String 49 | *

50 | * NO_CHECK:不校验真实姓名 51 | * FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账) 52 | * OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功) 53 | */ 54 | private String checkName; 55 | 56 | /** 57 | * 收款用户姓名 必填: 否 58 | *

59 | * 马花花 String 收款用户真实姓名。 60 | */ 61 | private String reUserName; 62 | 63 | /** 64 | * 企业付款金额 65 | * 必填: 是 66 | * 企业付款金额,单位为分 67 | */ 68 | private Integer amount; 69 | 70 | /** 71 | * 企业付款描述信息 必填: 是 72 | *

73 | * 理赔 String 企业付款操作说明信息. 74 | */ 75 | private String desc; 76 | 77 | /** 78 | * Ip地址 必填: 是 79 | *

80 | * 192.168.0.1 String(32) 调用接口的机器Ip地址 81 | */ 82 | private String spBillCreateIp; 83 | 84 | /** 85 | * 随机字符串 必填: 是 86 | *

87 | * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS String(32) 随机字符串,不长于32位 88 | */ 89 | private String nonceStr; 90 | 91 | public MchPayRequest() { 92 | } 93 | 94 | public MchPayRequest(String partnerTradeNo, String openId, String checkName, Integer amount, 95 | String desc, String spBillCreateIp, String nonceStr) { 96 | this.partnerTradeNo = partnerTradeNo; 97 | this.openId = openId; 98 | this.checkName = checkName; 99 | this.amount = amount; 100 | this.desc = desc; 101 | this.spBillCreateIp = spBillCreateIp; 102 | this.nonceStr = nonceStr; 103 | } 104 | 105 | @Override 106 | public String getHttpVerb() { 107 | return WXPayConstants.HTTPS_POST_CA_MCH_PAY; 108 | } 109 | 110 | @Override 111 | public String getApiURL() { 112 | return WXPayConstants.MCH_PAY_API; 113 | } 114 | 115 | @Override 116 | public Map getApplicationParams() { 117 | ACHashMap txtParams = new ACHashMap(); 118 | txtParams.put("device_info", this.deviceInfo); 119 | txtParams.put("partner_trade_no", this.partnerTradeNo); 120 | txtParams.put("openid", this.openId); 121 | txtParams.put("check_name", this.checkName); 122 | txtParams.put("re_user_name", this.reUserName); 123 | txtParams.put("amount", this.amount); 124 | txtParams.put("desc", this.desc); 125 | txtParams.put("spbill_create_ip", this.spBillCreateIp); 126 | txtParams.put("nonce_str", this.nonceStr); 127 | 128 | return txtParams; 129 | } 130 | 131 | @Override 132 | public Class getResponseClass() { 133 | return MchPayResponse.class; 134 | } 135 | 136 | public String getDeviceInfo() { 137 | return deviceInfo; 138 | } 139 | 140 | public void setDeviceInfo(String deviceInfo) { 141 | this.deviceInfo = deviceInfo; 142 | } 143 | 144 | public String getPartnerTradeNo() { 145 | return partnerTradeNo; 146 | } 147 | 148 | public void setPartnerTradeNo(String partnerTradeNo) { 149 | this.partnerTradeNo = partnerTradeNo; 150 | } 151 | 152 | public String getOpenId() { 153 | return openId; 154 | } 155 | 156 | public void setOpenId(String openId) { 157 | this.openId = openId; 158 | } 159 | 160 | public String getCheckName() { 161 | return checkName; 162 | } 163 | 164 | public void setCheckName(String checkName) { 165 | this.checkName = checkName; 166 | } 167 | 168 | public String getReUserName() { 169 | return reUserName; 170 | } 171 | 172 | public void setReUserName(String reUserName) { 173 | this.reUserName = reUserName; 174 | } 175 | 176 | public Integer getAmount() { 177 | return amount; 178 | } 179 | 180 | public void setAmount(Integer amount) { 181 | this.amount = amount; 182 | } 183 | 184 | public String getDesc() { 185 | return desc; 186 | } 187 | 188 | public void setDesc(String desc) { 189 | this.desc = desc; 190 | } 191 | 192 | public String getSpBillCreateIp() { 193 | return spBillCreateIp; 194 | } 195 | 196 | public void setSpBillCreateIp(String spBillCreateIp) { 197 | this.spBillCreateIp = spBillCreateIp; 198 | } 199 | 200 | public String getNonceStr() { 201 | return nonceStr; 202 | } 203 | 204 | public void setNonceStr(String nonceStr) { 205 | this.nonceStr = nonceStr; 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/parser/json/JsonConverter.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.parser.json; 2 | 3 | 4 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 5 | import net.arccode.wechat.pay.api.common.parser.Converter; 6 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 7 | import net.arccode.wechat.pay.api.common.parser.Reader; 8 | import net.arccode.wechat.pay.api.common.util.Converters; 9 | import net.arccode.wechat.pay.api.common.util.json.ExceptionErrorListener; 10 | import net.arccode.wechat.pay.api.common.util.json.JSONReader; 11 | import net.arccode.wechat.pay.api.common.util.json.JSONValidatingReader; 12 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 13 | 14 | import java.text.DateFormat; 15 | import java.text.ParseException; 16 | import java.text.SimpleDateFormat; 17 | import java.util.*; 18 | 19 | /** 20 | * json格式转换 21 | * 22 | * @author http://arccode.net 23 | * @since 2015-11-06 24 | */ 25 | public class JsonConverter implements Converter { 26 | 27 | 28 | @Override 29 | public T toResponse(String resp, Class clazz) throws WXPayApiException { 30 | JSONReader reader = new JSONValidatingReader(new ExceptionErrorListener()); 31 | Object rootObj = reader.read(resp); 32 | if (rootObj instanceof Map) { 33 | Map rootJson = (Map) rootObj; 34 | Collection values = rootJson.values(); 35 | for (Object rspObj : values) { 36 | if (rspObj instanceof Map) { 37 | Map rspJson = (Map) rspObj; 38 | return fromJson(rspJson, clazz); 39 | } 40 | } 41 | } 42 | return null; 43 | } 44 | 45 | /** 46 | * 把JSON格式的数据转换为对象 47 | * 48 | * @param 泛型领域对象 49 | * @param json JSON格式的数据 50 | * @param clazz 泛型领域类型 51 | * @return 领域对象 52 | * @throws WXPayApiException 53 | */ 54 | public T fromJson(final Map json, Class clazz) throws WXPayApiException { 55 | return Converters.convert(clazz, new Reader() { 56 | public boolean hasReturnField(Object name) { 57 | return json.containsKey(name); 58 | } 59 | 60 | public Object getPrimitiveObject(Object name) { 61 | return json.get(name); 62 | } 63 | 64 | public Object getObject(Object name, Class type) throws WXPayApiException { 65 | Object tmp = json.get(name); 66 | if (tmp instanceof Map) { 67 | Map map = (Map) tmp; 68 | return fromJson(map, type); 69 | } else { 70 | return null; 71 | } 72 | } 73 | 74 | public List getListObjects(Object listName, Object itemName, Class subType) 75 | throws WXPayApiException { 76 | List listObjs = null; 77 | 78 | Object listTmp = json.get(listName); 79 | if (listTmp instanceof Map) { 80 | Map jsonMap = (Map) listTmp; 81 | Object itemTmp = jsonMap.get(itemName); 82 | if (itemTmp == null && listName != null) { 83 | String listNameStr = listName.toString(); 84 | itemTmp = jsonMap.get(listNameStr.substring(0, listNameStr.length() - 1)); 85 | } 86 | if (itemTmp instanceof List) { 87 | listObjs = getListObjectsInner(subType, itemTmp); 88 | } 89 | } else if (listTmp instanceof List) { 90 | listObjs = getListObjectsInner(subType, listTmp); 91 | } 92 | 93 | return listObjs; 94 | } 95 | 96 | private List getListObjectsInner(Class subType, Object itemTmp) 97 | throws WXPayApiException { 98 | List listObjs; 99 | listObjs = new ArrayList(); 100 | List tmpList = (List) itemTmp; 101 | for (Object subTmp : tmpList) { 102 | Object obj = null; 103 | if (String.class.isAssignableFrom(subType)) { 104 | obj = subTmp; 105 | } else if (Long.class.isAssignableFrom(subType)) { 106 | obj = subTmp; 107 | } else if (Integer.class.isAssignableFrom(subType)) { 108 | obj = subTmp; 109 | } else if (Boolean.class.isAssignableFrom(subType)) { 110 | obj = subTmp; 111 | } else if (Date.class.isAssignableFrom(subType)) { 112 | DateFormat format = new SimpleDateFormat(WXPayConstants.DATE_TIME_FORMAT); 113 | try { 114 | obj = format.parse(String.valueOf(subTmp)); 115 | } catch (ParseException e) { 116 | throw new WXPayApiException(e); 117 | } 118 | } else if (subTmp instanceof Map) {// object 119 | Map subMap = (Map) subTmp; 120 | obj = fromJson(subMap, subType); 121 | } 122 | 123 | if (obj != null) { 124 | listObjs.add(obj); 125 | } 126 | } 127 | return listObjs; 128 | } 129 | 130 | }); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/JSONValidator.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | import java.text.CharacterIterator; 4 | import java.text.StringCharacterIterator; 5 | 6 | public class JSONValidator { 7 | 8 | private JSONErrorListener listener; 9 | private CharacterIterator it; 10 | private char c; 11 | private int col; 12 | 13 | public JSONValidator(JSONErrorListener listener) { 14 | this.listener = listener; 15 | } 16 | 17 | public boolean validate(String input) { 18 | input = input.trim(); 19 | listener.start(input); 20 | boolean ret = valid(input); 21 | listener.end(); 22 | return ret; 23 | } 24 | 25 | private boolean valid(String input) { 26 | if ("".equals(input)) return true; 27 | 28 | boolean ret = true; 29 | it = new StringCharacterIterator(input); 30 | c = it.first(); 31 | col = 1; 32 | if (!value()) { 33 | ret = error("value", 1); 34 | } else { 35 | skipWhiteSpace(); 36 | if (c != CharacterIterator.DONE) { 37 | ret = error("end", col); 38 | } 39 | } 40 | 41 | return ret; 42 | } 43 | 44 | private boolean value() { 45 | return 46 | literal("true") || 47 | literal("false") || 48 | literal("null") || 49 | string() || 50 | number() || 51 | object() || 52 | array(); 53 | } 54 | 55 | private boolean literal(String text) { 56 | CharacterIterator ci = new StringCharacterIterator(text); 57 | char t = ci.first(); 58 | if (c != t) return false; 59 | 60 | int start = col; 61 | boolean ret = true; 62 | for (t = ci.next(); t != CharacterIterator.DONE; t = ci.next()) { 63 | if (t != nextCharacter()) { 64 | ret = false; 65 | break; 66 | } 67 | } 68 | nextCharacter(); 69 | 70 | if (!ret) error("literal " + text, start); 71 | return ret; 72 | } 73 | 74 | private boolean array() { 75 | return aggregate('[', ']', false); 76 | } 77 | 78 | private boolean object() { 79 | return aggregate('{', '}', true); 80 | } 81 | 82 | private boolean aggregate(char entryCharacter, char exitCharacter, boolean prefix) { 83 | if (c != entryCharacter) return false; 84 | nextCharacter(); 85 | skipWhiteSpace(); 86 | if (c == exitCharacter) { 87 | nextCharacter(); 88 | return true; 89 | } 90 | 91 | for (; ; ) { 92 | if (prefix) { 93 | int start = col; 94 | if (!string()) return error("string", start); 95 | skipWhiteSpace(); 96 | if (c != ':') return error("colon", col); 97 | nextCharacter(); 98 | skipWhiteSpace(); 99 | } 100 | if (value()) { 101 | skipWhiteSpace(); 102 | if (c == ',') { 103 | nextCharacter(); 104 | } else if (c == exitCharacter) { 105 | break; 106 | } else { 107 | return error("comma or " + exitCharacter, col); 108 | } 109 | } else { 110 | return error("value", col); 111 | } 112 | skipWhiteSpace(); 113 | } 114 | 115 | nextCharacter(); 116 | return true; 117 | } 118 | 119 | private boolean number() { 120 | if (!Character.isDigit(c) && c != '-') return false; 121 | int start = col; 122 | 123 | if (c == '-') nextCharacter(); 124 | 125 | if (c == '0') { 126 | nextCharacter(); 127 | } else if (Character.isDigit(c)) { 128 | while (Character.isDigit(c)) nextCharacter(); 129 | } else { 130 | return error("number", start); 131 | } 132 | 133 | if (c == '.') { 134 | nextCharacter(); 135 | if (Character.isDigit(c)) { 136 | while (Character.isDigit(c)) nextCharacter(); 137 | } else { 138 | return error("number", start); 139 | } 140 | } 141 | 142 | if (c == 'e' || c == 'E') { 143 | nextCharacter(); 144 | if (c == '+' || c == '-') { 145 | nextCharacter(); 146 | } 147 | if (Character.isDigit(c)) { 148 | while (Character.isDigit(c)) nextCharacter(); 149 | } else { 150 | return error("number", start); 151 | } 152 | } 153 | 154 | return true; 155 | } 156 | 157 | private boolean string() { 158 | if (c != '"') return false; 159 | 160 | int start = col; 161 | boolean escaped = false; 162 | 163 | for (nextCharacter(); c != CharacterIterator.DONE; nextCharacter()) { 164 | if (!escaped && c == '\\') { 165 | escaped = true; 166 | } else if (escaped) { 167 | if (!escape()) { 168 | return false; 169 | } 170 | escaped = false; 171 | } else if (c == '"') { 172 | nextCharacter(); 173 | return true; 174 | } 175 | } 176 | 177 | return error("quoted string", start); 178 | } 179 | 180 | private boolean escape() { 181 | int start = col - 1; 182 | if ("\\\"/bfnrtu".indexOf(c) < 0) { 183 | return error("escape sequence \\\",\\\\,\\/,\\b,\\f,\\n,\\r,\\t or \\uxxxx", start); 184 | } 185 | if (c == 'u') { 186 | if (!ishex(nextCharacter()) || !ishex(nextCharacter()) || 187 | !ishex(nextCharacter()) || !ishex(nextCharacter())) { 188 | return error("unicode escape sequence \\uxxxx", start); 189 | } 190 | } 191 | return true; 192 | } 193 | 194 | private boolean ishex(char d) { 195 | return "0123456789abcdefABCDEF".indexOf(c) >= 0; 196 | } 197 | 198 | private char nextCharacter() { 199 | c = it.next(); 200 | ++col; 201 | return c; 202 | } 203 | 204 | private void skipWhiteSpace() { 205 | while (Character.isWhitespace(c)) { 206 | nextCharacter(); 207 | } 208 | } 209 | 210 | private boolean error(String type, int col) { 211 | if (listener != null) listener.error(type, col); 212 | return false; 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/test/java/net/arccode/wechat/pay/api/service/WXPayClientTest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.service; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 5 | import net.arccode.wechat.pay.api.protocol.mch_pay.MchPayResponse; 6 | import net.arccode.wechat.pay.api.protocol.pay_notify.PayNotifyResponse; 7 | import net.arccode.wechat.pay.api.common.util.SDKUtils; 8 | import net.arccode.wechat.pay.api.protocol.mch_pay.MchPayRequest; 9 | import net.arccode.wechat.pay.api.protocol.query_order.QueryOrderRequest; 10 | import net.arccode.wechat.pay.api.protocol.query_order.QueryOrderResponse; 11 | import net.arccode.wechat.pay.api.protocol.refund.RefundRequest; 12 | import net.arccode.wechat.pay.api.protocol.refund.RefundResponse; 13 | import net.arccode.wechat.pay.api.protocol.unified_order.UnifiedOrderRequest; 14 | import net.arccode.wechat.pay.api.protocol.unified_order.UnifiedOrderResponse; 15 | import org.junit.Assert; 16 | import org.junit.Before; 17 | import org.junit.Test; 18 | import org.slf4j.Logger; 19 | import org.slf4j.LoggerFactory; 20 | 21 | /** 22 | * 微信客户端测试类 23 | * 24 | * @author http://arccode.net 25 | * @since 2015-11-05 26 | */ 27 | public class WXPayClientTest { 28 | 29 | private static final Logger LOG = LoggerFactory.getLogger(WXPayClientTest.class); 30 | 31 | private WXPayClient wxPayClient; 32 | 33 | private WXPayClient wxPayVIPClient; 34 | 35 | private String asyncNotifyUrl = "http://domain:port/path"; 36 | 37 | @Before 38 | public void before() { 39 | 40 | // 以下配置参数根据公司申请的微信支付帐号填写 41 | String appId = ""; 42 | String mchId = ""; 43 | String key = ""; 44 | String certPwd = ""; 45 | // 绝对路径, 用于退款和商户支付 46 | String certPath = ""; 47 | 48 | wxPayClient = new WXPayClient(appId, mchId, key); 49 | wxPayVIPClient = new WXPayClient(appId, mchId, key, certPwd, certPath); 50 | } 51 | 52 | /** 53 | * 扫码支付下单 54 | */ 55 | @Test 56 | public void scanPay() throws WXPayApiException { 57 | 58 | String nonceStr = SDKUtils.genRandomStringByLength(32); 59 | UnifiedOrderRequest request = new UnifiedOrderRequest("commodity-899", SDKUtils 60 | .genOutTradeNo(), 61 | 1, "192.168.1.1", asyncNotifyUrl, "NATIVE", nonceStr); 62 | 63 | UnifiedOrderResponse response = wxPayClient.execute(request); 64 | 65 | LOG.info(JSON.toJSONString(response)); 66 | 67 | } 68 | 69 | /** 70 | * 公众号支付下单 71 | */ 72 | @Test 73 | public void jsApiPay() throws WXPayApiException { 74 | 75 | String nonceStr = SDKUtils.genRandomStringByLength(32); 76 | UnifiedOrderRequest request = new UnifiedOrderRequest("commodity-899", SDKUtils 77 | .genOutTradeNo(), 78 | 1, "192.168.1.1", asyncNotifyUrl, "JSAPI", nonceStr); 79 | request.setOpenId("oKVmeuHht8J0Ni58CSNe474AHA3E"); 80 | UnifiedOrderResponse response = wxPayClient.execute(request); 81 | 82 | LOG.info(JSON.toJSONString(response)); 83 | 84 | } 85 | 86 | /** 87 | * APP支付下单 88 | */ 89 | @Test 90 | public void appPay() throws WXPayApiException { 91 | 92 | String nonceStr = SDKUtils.genRandomStringByLength(32); 93 | UnifiedOrderRequest request = new UnifiedOrderRequest("commodity-899", SDKUtils 94 | .genOutTradeNo(), 95 | 1, "192.168.1.1", asyncNotifyUrl, "APP", nonceStr); 96 | 97 | UnifiedOrderResponse response = wxPayClient.execute(request); 98 | 99 | LOG.info(JSON.toJSONString(response)); 100 | 101 | } 102 | 103 | 104 | /** 105 | * 退款 106 | */ 107 | @Test 108 | public void refund() throws WXPayApiException { 109 | 110 | String nonceStr = SDKUtils.genRandomStringByLength(32); 111 | RefundRequest request = new RefundRequest("T15121416014891124211768", 112 | SDKUtils.genOutRefundNo(), 1, 1, "112102020", nonceStr); 113 | 114 | RefundResponse response = wxPayVIPClient.execute(request); 115 | Assert.assertNotNull(response); 116 | 117 | LOG.info(JSON.toJSONString(response)); 118 | 119 | } 120 | 121 | 122 | /** 123 | * 商户支付 124 | */ 125 | @Test 126 | public void mchPay() throws WXPayApiException { 127 | 128 | String nonceStr = SDKUtils.genRandomStringByLength(32); 129 | 130 | String customerOpenId = "oKVmeuHht8J0Ni58CSNe474AHA3E"; 131 | MchPayRequest mchPayRequest = new MchPayRequest(SDKUtils.genOutTradeNo(), 132 | customerOpenId, "NO_CHECK", 100, "xxxx年xx月结算", "192.168.1.1", nonceStr); 133 | 134 | MchPayResponse response = wxPayVIPClient.execute(mchPayRequest); 135 | Assert.assertNotNull(response); 136 | 137 | LOG.info(JSON.toJSONString(response)); 138 | 139 | } 140 | 141 | /** 142 | * 查询订单详情 143 | */ 144 | @Test 145 | public void queryOrder() throws WXPayApiException { 146 | 147 | String nonceStr = SDKUtils.genRandomStringByLength(32); 148 | QueryOrderRequest request = new QueryOrderRequest(null, "T18042215145391412971763", 149 | nonceStr); 150 | 151 | QueryOrderResponse response = wxPayClient.execute(request); 152 | 153 | LOG.info(JSON.toJSONString(response)); 154 | 155 | } 156 | 157 | /** 158 | * 解析支付通知内容 159 | */ 160 | @Test 161 | public void notifyTxtParse() throws WXPayApiException { 162 | String notifyTxt = "\n" + 163 | " \n" + 164 | " \n" + 165 | " \n" + 166 | " \n" + 167 | " \n" + 168 | " \n" + 169 | " \n" + 170 | " \n" + 171 | " \n" + 172 | " \n" + 173 | " \n" + 174 | " \n" + 175 | " \n" + 176 | " \n" + 177 | " 1\n" + 178 | " \n" + 179 | " \n" + 180 | ""; 181 | 182 | 183 | PayNotifyResponse response = wxPayClient.parseNotify(notifyTxt, PayNotifyResponse.class); 184 | 185 | 186 | LOG.info(JSON.toJSONString(response)); 187 | 188 | } 189 | 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import com.squareup.okhttp.Callback; 4 | import com.squareup.okhttp.OkHttpClient; 5 | import com.squareup.okhttp.Request; 6 | import com.squareup.okhttp.Response; 7 | import org.apache.http.HttpEntity; 8 | import org.apache.http.HttpResponse; 9 | import org.apache.http.client.config.RequestConfig; 10 | import org.apache.http.client.methods.HttpPost; 11 | import org.apache.http.conn.ConnectTimeoutException; 12 | import org.apache.http.conn.ConnectionPoolTimeoutException; 13 | import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 14 | import org.apache.http.entity.StringEntity; 15 | import org.apache.http.impl.client.CloseableHttpClient; 16 | import org.apache.http.impl.client.HttpClients; 17 | import org.apache.http.ssl.SSLContexts; 18 | import org.apache.http.util.EntityUtils; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import javax.net.ssl.SSLContext; 23 | import java.io.File; 24 | import java.io.FileInputStream; 25 | import java.io.IOException; 26 | import java.net.SocketTimeoutException; 27 | import java.security.*; 28 | import java.security.cert.CertificateException; 29 | import java.util.concurrent.TimeUnit; 30 | 31 | /** 32 | *
 33 |  * Http工具, 普通使用OkHTTP; 对于需要加载证书访问的情况, 暂时使用HttpClient实现
 34 |  *
 35 |  * OkHttp官方文档并不建议我们创建多个OkHttpClient,因此全局使用一个.
 36 |  *
 37 |  * 
38 | * 39 | * @author http://arccode.net 40 | * @since 2015-09-09 41 | */ 42 | public class HttpUtils { 43 | 44 | private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class); 45 | 46 | private static final OkHttpClient mOkHttpClient = new OkHttpClient(); 47 | 48 | 49 | //表示请求器是否已经做了初始化工作 50 | private static boolean hasInit = false; 51 | 52 | //连接超时时间,默认10秒 53 | private static int socketTimeout = 10000; 54 | 55 | //请求器的配置 56 | private static RequestConfig requestConfig; 57 | 58 | //传输超时时间,默认30秒 59 | private static int connectTimeout = 30000; 60 | 61 | //HTTP请求器 62 | private static CloseableHttpClient httpClient; 63 | 64 | static { 65 | mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS); 66 | } 67 | 68 | /** 69 | * 初始化 SSL 70 | * 71 | * @throws IOException 72 | * @throws KeyStoreException 73 | * @throws UnrecoverableKeyException 74 | * @throws NoSuchAlgorithmException 75 | * @throws KeyManagementException 76 | */ 77 | private static void init(String certPwd, String certPath) throws IOException, KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyManagementException { 78 | 79 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 80 | FileInputStream instream = new FileInputStream(new File(certPath));//加载本地的证书进行https加密传输 81 | try { 82 | keyStore.load(instream, certPwd.toCharArray());//设置证书密码 83 | } catch (CertificateException e) { 84 | e.printStackTrace(); 85 | } catch (NoSuchAlgorithmException e) { 86 | e.printStackTrace(); 87 | } finally { 88 | instream.close(); 89 | } 90 | 91 | // Trust own CA and all self-signed certs 92 | SSLContext sslcontext = SSLContexts.custom() 93 | .loadKeyMaterial(keyStore, certPwd.toCharArray()) 94 | .build(); 95 | // Allow TLSv1 protocol only 96 | SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( 97 | sslcontext, 98 | new String[]{"TLSv1"}, 99 | null, 100 | SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); 101 | 102 | httpClient = HttpClients.custom() 103 | .setSSLSocketFactory(sslsf) 104 | .build(); 105 | 106 | //根据默认超时限制初始化requestConfig 107 | requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout).setConnectTimeout(connectTimeout).build(); 108 | 109 | hasInit = true; 110 | } 111 | 112 | /** 113 | * 该不会开启异步线程。 114 | * 115 | * @param request 116 | * @return 117 | * @throws java.io.IOException 118 | */ 119 | public static Response execute(Request request) throws IOException { 120 | return mOkHttpClient.newCall(request).execute(); 121 | } 122 | 123 | /** 124 | * 带证书发送请求, 该不会开启异步线程。 125 | * 126 | * @param url 127 | * @param body 128 | * @param certPwd 129 | * @param certPath 130 | * @return 131 | * @throws Exception 132 | */ 133 | public static String executeAttachCA(String url, String body, String certPwd, String certPath) throws Exception { 134 | 135 | if (!hasInit) { 136 | init(certPwd, certPath); 137 | } 138 | 139 | String result = null; 140 | 141 | HttpPost httpPost = new HttpPost(url); 142 | 143 | //解决XStream对出现双下划线的bug 144 | 145 | LOG.info("API,POST过去的数据是:"); 146 | LOG.info(body); 147 | 148 | //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别 149 | StringEntity postEntity = new StringEntity(body, "UTF-8"); 150 | httpPost.addHeader("Content-Type", "text/xml"); 151 | httpPost.setEntity(postEntity); 152 | 153 | //设置请求器的配置 154 | httpPost.setConfig(requestConfig); 155 | 156 | LOG.info("executing request" + httpPost.getRequestLine()); 157 | 158 | try { 159 | HttpResponse response = httpClient.execute(httpPost); 160 | 161 | HttpEntity entity = response.getEntity(); 162 | 163 | result = EntityUtils.toString(entity, "UTF-8"); 164 | 165 | } catch (ConnectionPoolTimeoutException e) { 166 | LOG.warn("http get throw ConnectionPoolTimeoutException(wait time out)"); 167 | 168 | } catch (ConnectTimeoutException e) { 169 | LOG.warn("http get throw ConnectTimeoutException"); 170 | 171 | } catch (SocketTimeoutException e) { 172 | LOG.warn("http get throw SocketTimeoutException"); 173 | 174 | } catch (Exception e) { 175 | LOG.warn("http get throw Exception"); 176 | 177 | } finally { 178 | httpPost.abort(); 179 | } 180 | 181 | return result; 182 | 183 | } 184 | 185 | /** 186 | * 开启异步线程访问网络 187 | * 188 | * @param request 189 | * @param responseCallback 190 | */ 191 | public static void enqueue(Request request, Callback responseCallback) { 192 | mOkHttpClient.newCall(request).enqueue(responseCallback); 193 | } 194 | 195 | /** 196 | * 开启异步线程访问网络, 且不在意返回结果(实现空callback) 197 | * 198 | * @param request 199 | */ 200 | public static void enqueue(Request request) { 201 | mOkHttpClient.newCall(request).enqueue(new Callback() { 202 | 203 | @Override 204 | public void onResponse(Response arg0) throws IOException { 205 | 206 | } 207 | 208 | @Override 209 | public void onFailure(Request arg0, IOException arg1) { 210 | 211 | } 212 | }); 213 | } 214 | 215 | public static String getStringFromServer(String url) throws IOException { 216 | Request request = new Request.Builder().url(url).build(); 217 | Response response = execute(request); 218 | if (response.isSuccessful()) { 219 | String responseUrl = response.body().string(); 220 | return responseUrl; 221 | } else { 222 | throw new IOException("Unexpected code " + response); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/unified_order/UnifiedOrderRequest.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.unified_order; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | import net.arccode.wechat.pay.api.common.util.ACHashMap; 5 | import net.arccode.wechat.pay.api.protocol.base.WXPayRequest; 6 | 7 | import java.util.Map; 8 | 9 | /** 10 | * 统一下单入参, 适用于公众号支付(JSAPI)/扫码支付(NATIVE)/APP支付(APP) 11 | *

12 | * 详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1 13 | * 14 | * @author http://arccode.net 15 | * @since 2015-11-02 16 | */ 17 | public class UnifiedOrderRequest implements WXPayRequest { 18 | 19 | /**==================== 协议可选参数 ====================**/ 20 | 21 | /** 22 | * 货币类型, 可空, 默认为CNY 23 | */ 24 | private String feeType; 25 | 26 | /** 27 | * 指定支付方式, 可空 28 | */ 29 | private String limitPay; 30 | 31 | 32 | /**==================== 协议应用参数 ====================**/ 33 | 34 | /** 35 | * 通知地址, 不可空 36 | */ 37 | private String notifyUrl; 38 | 39 | /** 40 | * 交易类型, 不可空 41 | */ 42 | private String tradeType; 43 | 44 | /** 45 | * 设备号, 可空 46 | */ 47 | private String deviceInfo; 48 | 49 | /** 50 | * 商品描述, 不可空 51 | */ 52 | private String body; 53 | 54 | /** 55 | * 商品详情, 可空 56 | */ 57 | private String detail; 58 | 59 | /** 60 | * 附加数据, 可空 61 | */ 62 | private String attach; 63 | 64 | /** 65 | * 商户订单号, 不可空 66 | */ 67 | private String outTradeNo; 68 | 69 | /** 70 | * 总金额(分), 不可空 71 | */ 72 | private Integer totalFee; 73 | 74 | /** 75 | * 终端IP, 不可空 76 | */ 77 | private String spBillCreateIp; 78 | 79 | /** 80 | * 交易起始时间, 可空 81 | */ 82 | private String timeStart; 83 | 84 | /** 85 | * 交易结束时间, 可空 86 | */ 87 | private String timeExpire; 88 | 89 | /** 90 | * 商品标记, 可空 91 | */ 92 | private String goodsTag; 93 | 94 | /** 95 | * 商品ID, 可空 96 | */ 97 | private String productId; 98 | 99 | /** 100 | * 用户标识, 不可空 101 | */ 102 | private String openId; 103 | 104 | /** 105 | * 随机字符串, 不可空 106 | */ 107 | private String nonceStr; 108 | 109 | 110 | public UnifiedOrderRequest() { 111 | } 112 | 113 | public UnifiedOrderRequest(String body, String outTradeNo, Integer totalFee, String 114 | spBillCreateIp, 115 | String notifyUrl, String tradeType, String nonceStr) { 116 | this.body = body; 117 | this.outTradeNo = outTradeNo; 118 | this.totalFee = totalFee; 119 | this.spBillCreateIp = spBillCreateIp; 120 | this.notifyUrl = notifyUrl; 121 | this.tradeType = tradeType; 122 | this.nonceStr = nonceStr; 123 | } 124 | 125 | @Override 126 | public String getHttpVerb() { 127 | return WXPayConstants.HTTP_POST; 128 | } 129 | 130 | @Override 131 | public String getApiURL() { 132 | return WXPayConstants.UNIFIED_ORDER_API; 133 | } 134 | 135 | @Override 136 | public Map getApplicationParams() { 137 | ACHashMap txtParams = new ACHashMap(); 138 | txtParams.put("device_info", this.deviceInfo); 139 | txtParams.put("body", this.body); 140 | txtParams.put("detail", this.detail); 141 | txtParams.put("attach", this.attach); 142 | txtParams.put("out_trade_no", this.outTradeNo); 143 | txtParams.put("total_fee", this.totalFee); 144 | txtParams.put("spbill_create_ip", this.spBillCreateIp); 145 | txtParams.put("time_start", this.timeStart); 146 | txtParams.put("time_expire", this.timeExpire); 147 | txtParams.put("goods_tag", this.goodsTag); 148 | txtParams.put("product_id", this.productId); 149 | txtParams.put("openid", this.openId); 150 | txtParams.put("notify_url", this.notifyUrl); 151 | txtParams.put("trade_type", this.tradeType); 152 | txtParams.put("nonce_str", this.nonceStr); 153 | 154 | return txtParams; 155 | } 156 | 157 | @Override 158 | public Class getResponseClass() { 159 | return UnifiedOrderResponse.class; 160 | } 161 | 162 | public String getOpenId() { 163 | return openId; 164 | } 165 | 166 | public void setOpenId(String openId) { 167 | this.openId = openId; 168 | } 169 | 170 | public String getFeeType() { 171 | return feeType; 172 | } 173 | 174 | public void setFeeType(String feeType) { 175 | this.feeType = feeType; 176 | } 177 | 178 | public String getLimitPay() { 179 | return limitPay; 180 | } 181 | 182 | public void setLimitPay(String limitPay) { 183 | this.limitPay = limitPay; 184 | } 185 | 186 | public String getNotifyUrl() { 187 | return notifyUrl; 188 | } 189 | 190 | public void setNotifyUrl(String notifyUrl) { 191 | this.notifyUrl = notifyUrl; 192 | } 193 | 194 | public String getTradeType() { 195 | return tradeType; 196 | } 197 | 198 | public void setTradeType(String tradeType) { 199 | this.tradeType = tradeType; 200 | } 201 | 202 | public String getDeviceInfo() { 203 | return deviceInfo; 204 | } 205 | 206 | public void setDeviceInfo(String deviceInfo) { 207 | this.deviceInfo = deviceInfo; 208 | } 209 | 210 | public String getBody() { 211 | return body; 212 | } 213 | 214 | public void setBody(String body) { 215 | this.body = body; 216 | } 217 | 218 | public String getDetail() { 219 | return detail; 220 | } 221 | 222 | public void setDetail(String detail) { 223 | this.detail = detail; 224 | } 225 | 226 | public String getAttach() { 227 | return attach; 228 | } 229 | 230 | public void setAttach(String attach) { 231 | this.attach = attach; 232 | } 233 | 234 | public String getOutTradeNo() { 235 | return outTradeNo; 236 | } 237 | 238 | public void setOutTradeNo(String outTradeNo) { 239 | this.outTradeNo = outTradeNo; 240 | } 241 | 242 | public Integer getTotalFee() { 243 | return totalFee; 244 | } 245 | 246 | public void setTotalFee(Integer totalFee) { 247 | this.totalFee = totalFee; 248 | } 249 | 250 | public String getSpBillCreateIp() { 251 | return spBillCreateIp; 252 | } 253 | 254 | public void setSpBillCreateIp(String spBillCreateIp) { 255 | this.spBillCreateIp = spBillCreateIp; 256 | } 257 | 258 | public String getTimeStart() { 259 | return timeStart; 260 | } 261 | 262 | public void setTimeStart(String timeStart) { 263 | this.timeStart = timeStart; 264 | } 265 | 266 | public String getTimeExpire() { 267 | return timeExpire; 268 | } 269 | 270 | public void setTimeExpire(String timeExpire) { 271 | this.timeExpire = timeExpire; 272 | } 273 | 274 | public String getGoodsTag() { 275 | return goodsTag; 276 | } 277 | 278 | public void setGoodsTag(String goodsTag) { 279 | this.goodsTag = goodsTag; 280 | } 281 | 282 | public String getProductId() { 283 | return productId; 284 | } 285 | 286 | public void setProductId(String productId) { 287 | this.productId = productId; 288 | } 289 | 290 | public String getNonceStr() { 291 | return nonceStr; 292 | } 293 | 294 | public void setNonceStr(String nonceStr) { 295 | this.nonceStr = nonceStr; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/JSONWriter.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | 5 | import java.beans.BeanInfo; 6 | import java.beans.IntrospectionException; 7 | import java.beans.Introspector; 8 | import java.beans.PropertyDescriptor; 9 | import java.lang.reflect.Array; 10 | import java.lang.reflect.Field; 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.lang.reflect.Method; 13 | import java.text.CharacterIterator; 14 | import java.text.DateFormat; 15 | import java.text.SimpleDateFormat; 16 | import java.text.StringCharacterIterator; 17 | import java.util.*; 18 | 19 | public class JSONWriter { 20 | 21 | private StringBuffer buf = new StringBuffer(); 22 | private Stack calls = new Stack(); 23 | private boolean emitClassName = true; 24 | private DateFormat format; 25 | 26 | public JSONWriter(boolean emitClassName) { 27 | this.emitClassName = emitClassName; 28 | } 29 | 30 | public JSONWriter() { 31 | this(false); 32 | } 33 | 34 | public JSONWriter(DateFormat format) { 35 | this(false); 36 | this.format = format; 37 | } 38 | 39 | public String write(Object object) { 40 | buf.setLength(0); 41 | value(object); 42 | return buf.toString(); 43 | } 44 | 45 | public String write(long n) { 46 | return String.valueOf(n); 47 | } 48 | 49 | public String write(double d) { 50 | return String.valueOf(d); 51 | } 52 | 53 | public String write(char c) { 54 | return "\"" + c + "\""; 55 | } 56 | 57 | public String write(boolean b) { 58 | return String.valueOf(b); 59 | } 60 | 61 | private void value(Object object) { 62 | if (object == null || cyclic(object)) { 63 | add(null); 64 | } else { 65 | calls.push(object); 66 | if (object instanceof Class) string(object); 67 | else if (object instanceof Boolean) bool(((Boolean) object).booleanValue()); 68 | else if (object instanceof Number) add(object); 69 | else if (object instanceof String) string(object); 70 | else if (object instanceof Character) string(object); 71 | else if (object instanceof Map) map((Map) object); 72 | else if (object.getClass().isArray()) array(object); 73 | else if (object instanceof Iterator) array((Iterator) object); 74 | else if (object instanceof Collection) array(((Collection) object).iterator()); 75 | else if (object instanceof Date) date((Date) object); 76 | else bean(object); 77 | calls.pop(); 78 | } 79 | } 80 | 81 | private boolean cyclic(Object object) { 82 | Iterator it = calls.iterator(); 83 | while (it.hasNext()) { 84 | Object called = it.next(); 85 | if (object == called) return true; 86 | } 87 | return false; 88 | } 89 | 90 | private void bean(Object object) { 91 | add("{"); 92 | BeanInfo info; 93 | boolean addedSomething = false; 94 | try { 95 | info = Introspector.getBeanInfo(object.getClass()); 96 | PropertyDescriptor[] props = info.getPropertyDescriptors(); 97 | for (int i = 0; i < props.length; ++i) { 98 | PropertyDescriptor prop = props[i]; 99 | String name = prop.getName(); 100 | Method accessor = prop.getReadMethod(); 101 | if ((emitClassName || !"class".equals(name)) && accessor != null) { 102 | if (!accessor.isAccessible()) accessor.setAccessible(true); 103 | Object value = accessor.invoke(object, (Object[]) null); 104 | if (value == null) continue; 105 | if (addedSomething) add(','); 106 | add(name, value); 107 | addedSomething = true; 108 | } 109 | } 110 | Field[] ff = object.getClass().getFields(); 111 | for (int i = 0; i < ff.length; ++i) { 112 | Field field = ff[i]; 113 | Object value = field.get(object); 114 | if (value == null) continue; 115 | if (addedSomething) add(','); 116 | add(field.getName(), value); 117 | addedSomething = true; 118 | } 119 | } catch (IllegalAccessException iae) { 120 | iae.printStackTrace(); 121 | } catch (InvocationTargetException ite) { 122 | ite.getCause().printStackTrace(); 123 | ite.printStackTrace(); 124 | } catch (IntrospectionException ie) { 125 | ie.printStackTrace(); 126 | } 127 | add("}"); 128 | } 129 | 130 | private void add(String name, Object value) { 131 | add('"'); 132 | add(name); 133 | add("\":"); 134 | value(value); 135 | } 136 | 137 | private void map(Map map) { 138 | add("{"); 139 | Iterator it = map.entrySet().iterator(); 140 | while (it.hasNext()) { 141 | Map.Entry e = (Map.Entry) it.next(); 142 | value(e.getKey()); 143 | add(":"); 144 | value(e.getValue()); 145 | if (it.hasNext()) add(','); 146 | } 147 | add("}"); 148 | } 149 | 150 | private void array(Iterator it) { 151 | add("["); 152 | while (it.hasNext()) { 153 | value(it.next()); 154 | if (it.hasNext()) add(","); 155 | } 156 | add("]"); 157 | } 158 | 159 | private void array(Object object) { 160 | add("["); 161 | int length = Array.getLength(object); 162 | for (int i = 0; i < length; ++i) { 163 | value(Array.get(object, i)); 164 | if (i < length - 1) add(','); 165 | } 166 | add("]"); 167 | } 168 | 169 | private void bool(boolean b) { 170 | add(b ? "true" : "false"); 171 | } 172 | 173 | private void date(Date date) { 174 | if (this.format == null) { 175 | this.format = new SimpleDateFormat(WXPayConstants.DATE_TIME_FORMAT); 176 | this.format.setTimeZone(TimeZone.getTimeZone(WXPayConstants.DATE_TIMEZONE)); 177 | } 178 | add("\""); 179 | add(format.format(date)); 180 | add("\""); 181 | } 182 | 183 | private void string(Object obj) { 184 | add('"'); 185 | CharacterIterator it = new StringCharacterIterator(obj.toString()); 186 | for (char c = it.first(); c != CharacterIterator.DONE; c = it.next()) { 187 | if (c == '"') add("\\\""); 188 | else if (c == '\\') add("\\\\"); 189 | else if (c == '/') add("\\/"); 190 | else if (c == '\b') add("\\b"); 191 | else if (c == '\f') add("\\f"); 192 | else if (c == '\n') add("\\n"); 193 | else if (c == '\r') add("\\r"); 194 | else if (c == '\t') add("\\t"); 195 | else if (Character.isISOControl(c)) { 196 | unicode(c); 197 | } else { 198 | add(c); 199 | } 200 | } 201 | add('"'); 202 | } 203 | 204 | private void add(Object obj) { 205 | buf.append(obj); 206 | } 207 | 208 | private void add(char c) { 209 | buf.append(c); 210 | } 211 | 212 | static char[] hex = "0123456789ABCDEF".toCharArray(); 213 | 214 | private void unicode(char c) { 215 | add("\\u"); 216 | int n = c; 217 | for (int i = 0; i < 4; ++i) { 218 | int digit = (n & 0xf000) >> 12; 219 | add(hex[digit]); 220 | n <<= 4; 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 背景 2 | 3 | 让使用微信支付的朋友最快速度接入微信支付. 4 | 5 | ## 核心 6 | 7 | 两行代码解决微信支付提供的各种服务, 开箱即用, 可扩展性超强(只需根据服务的上下行协议定义协议类后, 放入工厂即可获取调用结果). 8 | 9 | ## 最近发布 10 | 11 | ``` 12 | 13 | net.arccode 14 | wechat-pay-sdk 15 | 1.1.1 16 | 17 | ``` 18 | 19 | ## 架构图 20 | 21 | ![](http://www.arccode.net/images/wx-pay-sdk-2.jpg) 22 | 23 | ## 依赖 24 | 25 | * JDK >= 1.7 26 | 27 | 目前发送`http`采用`okhttp`(更简洁, 更高效), 而`okhttp`依赖的JDK版本必须大于或等于`1.7`, 有需要兼容JDK 1.6的同学可以提`Issues`或`Pull requests`. 28 | 29 | ## 项目源代码 30 | 31 | * 源码地址 [https://github.com/arccode/wechat-pay-sdk](https://github.com/arccode/wechat-pay-sdk) 32 | 33 | ## 目前支持的服务及调用示例 34 | 35 | 所有服务在单元测试类(WXPayClientTest.java)中均已测试通过, 下行参数`response.isSuccess == true`表示服务调用成功. 36 | 37 | ### 初始化 38 | 39 | ``` 40 | private WXPayClient wxPayClient; 41 | 42 | private WXPayClient wxPayVIPClient; 43 | 44 | private String asyncNotifyUrl = "http://domain:port/path"; 45 | 46 | @Before 47 | public void before() { 48 | 49 | // 以下配置参数根据公司申请的微信支付帐号填写 50 | 51 | String appId = ""; 52 | String mchId = ""; 53 | String key = ""; 54 | String certPwd = ""; 55 | // 绝对路径, 用于退款和商户支付 56 | String certPath = ""; 57 | 58 | wxPayClient = new WXPayClient(appId, mchId, key); 59 | wxPayVIPClient = new WXPayClient(appId, mchId, key, certPwd, certPath); 60 | } 61 | ``` 62 | 63 | ### 扫码支付 64 | 65 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1) 66 | 67 | ``` 68 | String nonceStr = SDKUtils.genRandomStringByLength(32); 69 | UnifiedOrderRequest request = new UnifiedOrderRequest("donate-899",SDKUtils.genOutTradeNo(),1, "192.168.1.1", asyncNotifyUrl, "NATIVE", nonceStr); 70 | UnifiedOrderResponse response = wxPayClient.execute(request); 71 | Assert.assertNotNull(response); 72 | LOG.info(JSON.toJSONString(response)); 73 | // TODO 开发人员根据 response中的属性值处理业务逻辑, 此处可完美嵌入业务层(小型系统)或服务层(大型系统) 74 | ``` 75 | 76 | ### 公众号支付 77 | 78 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1) 79 | 80 | ``` 81 | String nonceStr = SDKUtils.genRandomStringByLength(32); 82 | UnifiedOrderRequest request = new UnifiedOrderRequest("donate-899",SDKUtils.genOutTradeNo(), 83 | 1, "192.168.1.1", asyncNotifyUrl, "JSAPI", nonceStr); 84 | request.setOpenId("oKVmeuHht8J0Ni58CSNe474AHA3E"); 85 | UnifiedOrderResponse response = wxPayClient.execute(request); 86 | Assert.assertNotNull(response); 87 | LOG.info(JSON.toJSONString(response)); 88 | // TODO 开发人员根据 response中的属性值处理业务逻辑, 此处可完美嵌入业务层(小型系统)或服务层(大型系统) 89 | ``` 90 | 91 | ### APP支付 92 | 93 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1) 94 | 95 | ``` 96 | String nonceStr = SDKUtils.genRandomStringByLength(32); 97 | UnifiedOrderRequest request = new UnifiedOrderRequest("donate-899",SDKUtils.genOutTradeNo(), 98 | 1, "192.168.1.1", asyncNotifyUrl, "APP", nonceStr); 99 | UnifiedOrderResponse response = wxPayClient.execute(request); 100 | Assert.assertNotNull(response); 101 | LOG.info(JSON.toJSONString(response)); 102 | // TODO 开发人员根据 response中的属性值处理业务逻辑, 此处可完美嵌入业务层(小型系统)或服务层(大型系统) 103 | ``` 104 | 105 | ### 商家支付 106 | 107 | 108 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2](https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2) 109 | 110 | ``` 111 | String nonceStr = SDKUtils.genRandomStringByLength(32); 112 | String customerOpenId = "oKVmeuHht8J0Ni58CSNe474AHA3E"; 113 | MchPayRequest mchPayRequest = new MchPayRequest(SDKUtils.genOutTradeNo(), 114 | customerOpenId, "NO_CHECK", 100, "xxxx年xx月结算", "192.168.1.1", nonceStr); 115 | MchPayResponse response = wxPayVIPClient.execute(mchPayRequest); 116 | Assert.assertNotNull(response); 117 | LOG.info(JSON.toJSONString(response)); 118 | // TODO 开发人员根据 response中的属性值处理业务逻辑, 此处可完美嵌入业务层(小型系统)或服务层(大型系统) 119 | ``` 120 | 121 | ### 退款 122 | 123 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_4&index=6](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_4&index=6) 124 | 125 | ``` 126 | String nonceStr = SDKUtils.genRandomStringByLength(32); 127 | RefundRequest request = new RefundRequest("T15121416014891124211768", 128 | SDKUtils.genOutRefundNo(), 1, 1, "112102020", nonceStr); 129 | RefundResponse response = wxPayVIPClient.execute(request); 130 | Assert.assertNotNull(response); 131 | LOG.info(JSON.toJSONString(response)); 132 | // TODO 开发人员根据 response中的属性值处理业务逻辑, 此处可完美嵌入业务层(小型系统)或服务层(大型系统) 133 | ``` 134 | 135 | ### 查询订单 136 | 137 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_2&index=4](https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_2&index=4) 138 | 139 | ``` 140 | /** 141 | * 查询订单详情 142 | */ 143 | @Test 144 | public void queryOrder() throws WXPayApiException { 145 | 146 | String nonceStr = SDKUtils.genRandomStringByLength(32); 147 | QueryOrderRequest request = new QueryOrderRequest(null, "T18042215145391412971763", 148 | nonceStr); 149 | 150 | QueryOrderResponse response = wxPayClient.execute(request); 151 | Assert.assertNotNull(response); 152 | 153 | LOG.info(JSON.toJSONString(response)); 154 | 155 | // TODO 开发人员根据 response中的属性值处理业务逻辑, 此处可完美嵌入业务层(小型系统)或服务层(大型系统) 156 | 157 | } 158 | ``` 159 | 160 | 161 | ### 支付异步通知解析 162 | 163 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7](https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7) 164 | 165 | ``` 166 | String notifyTxt = "\n" + 167 | " \n" + 168 | " \n" + 169 | " \n" + 170 | " \n" + 171 | " \n" + 172 | " \n" + 173 | " \n" + 174 | " \n" + 175 | " \n" + 176 | " \n" + 177 | " \n" + 178 | " \n" + 179 | " \n" + 180 | " \n" + 181 | " 1\n" + 182 | " \n" + 183 | " \n" + 184 | ""; 185 | PayNotifyResponse response = wxPayClient.parseNotify(notifyTxt, PayNotifyResponse.class); 186 | Assert.assertNotNull(response); 187 | LOG.info(JSON.toJSONString(response)); 188 | // TODO 开发人员根据 response中的属性值处理业务逻辑, 此处可完美嵌入业务层(小型系统)或服务层(大型系统) 189 | ``` 190 | 191 | ### 刷卡支付 192 | 193 | 官方文档详见: [https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1](https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1) 194 | 195 | 目前未使用, 待续...... 196 | 197 | ## 扩展 198 | 199 | 该SDK设计了一个服务工厂, 该工厂中包含HTTP执行器/返回数据解析方式(json/xml)/入参数据格式(json/xml)构造等, 开发人员需要增加服务仅需要根据服务协议文档编写上下行协议, 并在协议中指明API接口和返回数据类型, 再将上行协议放入工厂中执行即可; 可参考已完成的服务协议进行扩展编写. 200 | 201 | ## 版本发布历史 202 | 203 | ### v1.0.0 204 | 205 | 初始化项目, 提供完整的开发模式和部分支付接口. 206 | 207 | * 扫码支付 208 | * 公众号支付 209 | * app支付 210 | * 商家支付 211 | * 退款 212 | * 异步通知解析 213 | 214 | ### v1.1.0 215 | 216 | * 增加查询订单接口 217 | * 微重构, 简化各接口上行参数 218 | 219 | ### v1.1.1 220 | 221 | * XXXRequest中恢复get/set方法 222 | * 发布v1.1.1至Maven中央库 223 | 224 | ## License 225 | 226 | [MIT](http://opensource.org/licenses/MIT) 227 | 228 | Copyright (c) 2017-present, arccode -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/json/JSONReader.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util.json; 2 | 3 | import java.math.BigDecimal; 4 | import java.math.BigInteger; 5 | import java.text.CharacterIterator; 6 | import java.text.StringCharacterIterator; 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * 不能直接使用JSONReader,请用JSONValidatingReader,所以这里改为abstract修饰。 14 | */ 15 | public abstract class JSONReader { 16 | 17 | private static final Object OBJECT_END = new Object(); 18 | private static final Object ARRAY_END = new Object(); 19 | private static final Object COLON = new Object(); 20 | private static final Object COMMA = new Object(); 21 | public static final int FIRST = 0; 22 | public static final int CURRENT = 1; 23 | public static final int NEXT = 2; 24 | 25 | private static Map escapes = new HashMap(); 26 | 27 | static { 28 | escapes.put(Character.valueOf('"'), Character.valueOf('"')); 29 | escapes.put(Character.valueOf('\\'), Character.valueOf('\\')); 30 | escapes.put(Character.valueOf('/'), Character.valueOf('/')); 31 | escapes.put(Character.valueOf('b'), Character.valueOf('\b')); 32 | escapes.put(Character.valueOf('f'), Character.valueOf('\f')); 33 | escapes.put(Character.valueOf('n'), Character.valueOf('\n')); 34 | escapes.put(Character.valueOf('r'), Character.valueOf('\r')); 35 | escapes.put(Character.valueOf('t'), Character.valueOf('\t')); 36 | } 37 | 38 | private CharacterIterator it; 39 | private char c; 40 | private Object token; 41 | private StringBuffer buf = new StringBuffer(); 42 | 43 | private char next() { 44 | c = it.next(); 45 | return c; 46 | } 47 | 48 | private void skipWhiteSpace() { 49 | while (Character.isWhitespace(c)) { 50 | next(); 51 | } 52 | } 53 | 54 | public Object read(CharacterIterator ci, int start) { 55 | it = ci; 56 | switch (start) { 57 | case FIRST: 58 | c = it.first(); 59 | break; 60 | case CURRENT: 61 | c = it.current(); 62 | break; 63 | case NEXT: 64 | c = it.next(); 65 | break; 66 | } 67 | return read(); 68 | } 69 | 70 | public Object read(CharacterIterator it) { 71 | return read(it, NEXT); 72 | } 73 | 74 | public Object read(String string) { 75 | return read(new StringCharacterIterator(string), FIRST); 76 | } 77 | 78 | private Object read() { 79 | skipWhiteSpace(); 80 | char ch = c; 81 | next(); 82 | switch (ch) { 83 | case '"': 84 | token = string(); 85 | break; 86 | case '[': 87 | token = array(); 88 | break; 89 | case ']': 90 | token = ARRAY_END; 91 | break; 92 | case ',': 93 | token = COMMA; 94 | break; 95 | case '{': 96 | token = object(); 97 | break; 98 | case '}': 99 | token = OBJECT_END; 100 | break; 101 | case ':': 102 | token = COLON; 103 | break; 104 | case 't': 105 | next(); 106 | next(); 107 | next(); // assumed r-u-e 108 | token = Boolean.TRUE; 109 | break; 110 | case 'f': 111 | next(); 112 | next(); 113 | next(); 114 | next(); // assumed a-l-s-e 115 | token = Boolean.FALSE; 116 | break; 117 | case 'n': 118 | next(); 119 | next(); 120 | next(); // assumed u-l-l 121 | token = null; 122 | break; 123 | default: 124 | c = it.previous(); 125 | if (Character.isDigit(c) || c == '-') { 126 | token = number(); 127 | } 128 | } 129 | // System.out.println("token: " + token); // enable this line to see the token stream 130 | return token; 131 | } 132 | 133 | private Object object() { 134 | Map ret = new HashMap(); 135 | Object key = read(); 136 | while (token != OBJECT_END) { 137 | read(); // should be a colon 138 | if (token != OBJECT_END) { 139 | ret.put(key, read()); 140 | if (read() == COMMA) { 141 | key = read(); 142 | } 143 | } 144 | } 145 | 146 | return ret; 147 | } 148 | 149 | private Object array() { 150 | List ret = new ArrayList(); 151 | Object value = read(); 152 | while (token != ARRAY_END) { 153 | ret.add(value); 154 | if (read() == COMMA) { 155 | value = read(); 156 | } 157 | } 158 | return ret; 159 | } 160 | 161 | private Object number() { 162 | int length = 0; 163 | boolean isFloatingPoint = false; 164 | buf.setLength(0); 165 | 166 | if (c == '-') { 167 | add(); 168 | } 169 | length += addDigits(); 170 | if (c == '.') { 171 | add(); 172 | length += addDigits(); 173 | isFloatingPoint = true; 174 | } 175 | if (c == 'e' || c == 'E') { 176 | add(); 177 | if (c == '+' || c == '-') { 178 | add(); 179 | } 180 | addDigits(); 181 | isFloatingPoint = true; 182 | } 183 | 184 | String s = buf.toString(); 185 | return isFloatingPoint 186 | ? (length < 17) ? (Object) Double.valueOf(s) : new BigDecimal(s) 187 | : (length < 19) ? (Object) Long.valueOf(s) : new BigInteger(s); 188 | } 189 | 190 | private int addDigits() { 191 | int ret; 192 | for (ret = 0; Character.isDigit(c); ++ret) { 193 | add(); 194 | } 195 | return ret; 196 | } 197 | 198 | private Object string() { 199 | buf.setLength(0); 200 | while (c != '"') { 201 | if (c == '\\') { 202 | next(); 203 | if (c == 'u') { 204 | add(unicode()); 205 | } else { 206 | Object value = escapes.get(Character.valueOf(c)); 207 | if (value != null) { 208 | add(((Character) value).charValue()); 209 | } 210 | } 211 | } else { 212 | add(); 213 | } 214 | } 215 | next(); 216 | 217 | return buf.toString(); 218 | } 219 | 220 | private void add(char cc) { 221 | buf.append(cc); 222 | next(); 223 | } 224 | 225 | private void add() { 226 | add(c); 227 | } 228 | 229 | private char unicode() { 230 | int value = 0; 231 | for (int i = 0; i < 4; ++i) { 232 | switch (next()) { 233 | case '0': 234 | case '1': 235 | case '2': 236 | case '3': 237 | case '4': 238 | case '5': 239 | case '6': 240 | case '7': 241 | case '8': 242 | case '9': 243 | value = (value << 4) + c - '0'; 244 | break; 245 | case 'a': 246 | case 'b': 247 | case 'c': 248 | case 'd': 249 | case 'e': 250 | case 'f': 251 | value = (value << 4) + c - 'k'; 252 | break; 253 | case 'A': 254 | case 'B': 255 | case 'C': 256 | case 'D': 257 | case 'E': 258 | case 'F': 259 | value = (value << 4) + c - 'K'; 260 | break; 261 | } 262 | } 263 | return (char) value; 264 | } 265 | } -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/pay_notify/PayNotifyResponse.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.pay_notify; 2 | 3 | import net.arccode.wechat.pay.api.common.annotation.ApiField; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | 6 | /** 7 | * 微信支付异步通知结果 8 | * 9 | * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_7 10 | * 11 | * @author http://arccode.net 12 | * @since 2015-11-11 13 | */ 14 | public class PayNotifyResponse extends WXPayResponse { 15 | private static final long serialVersionUID = 5980237292242838244L; 16 | 17 | /**==================== 以下字段在return_code为SUCCESS的时候有返回 ====================**/ 18 | 19 | /** 20 | * 公众账号ID 不可空* 21 | */ 22 | @ApiField("appid") 23 | private String appId; 24 | 25 | /** 26 | * 商户号 不可空* 27 | */ 28 | @ApiField("mch_id") 29 | private String mchId; 30 | 31 | /** 32 | * 设备号 可空* 33 | */ 34 | @ApiField("device_info") 35 | private String deviceInfo; 36 | 37 | /** 38 | * 随机字符串 不可空* 39 | */ 40 | @ApiField("nonce_str") 41 | private String nonceStr; 42 | 43 | /** 44 | * 签名 不可空* 45 | */ 46 | @ApiField("sign") 47 | private String sign; 48 | 49 | /** 50 | * 业务结果 不可空* 51 | */ 52 | @ApiField("result_code") 53 | private String resultCode; 54 | 55 | /** 56 | * 错误代码 可空* 57 | */ 58 | @ApiField("err_code") 59 | private String errCode; 60 | 61 | /** 62 | * 错误代码描述 可空* 63 | */ 64 | @ApiField("err_code_des") 65 | private String errCodeDes; 66 | 67 | /** 68 | * 用户标识 不可空* 69 | */ 70 | @ApiField("openid") 71 | private String openId; 72 | 73 | /** 74 | * 是否关注公众账号 可空* 75 | */ 76 | @ApiField("is_subscrib") 77 | private String isSubscrib; 78 | /** 79 | * 交易类型 不可空* 80 | */ 81 | @ApiField("trade_type") 82 | private String tradeType; 83 | 84 | /** 85 | * 付款银行 不可空* 86 | */ 87 | @ApiField("bank_type") 88 | private String bankType; 89 | 90 | /** 91 | * 总金额 不可空* 92 | */ 93 | @ApiField("total_fee") 94 | private Integer totalFee; 95 | /** 96 | * 货币种类 可空* 97 | */ 98 | @ApiField("fee_type") 99 | private String feeType; 100 | 101 | /** 102 | * 现金支付金额 不可空* 103 | */ 104 | @ApiField("cash_fee") 105 | private String cashFee; 106 | 107 | /** 108 | * 现金支付货币类型 可空* 109 | */ 110 | @ApiField("cash_fee_type") 111 | private Integer cashFeeType; 112 | /** 113 | * 代金券或立减优惠金额 可空* 114 | */ 115 | @ApiField("coupon_fee") 116 | private Integer couponFee; 117 | /** 118 | * 代金券或立减优惠使用数量 可空* 119 | */ 120 | @ApiField("coupon_count") 121 | private Integer couponCount; 122 | 123 | /** 124 | * 代金券或立减优惠ID 可空* 125 | */ 126 | @ApiField("coupon_id_$n") 127 | private String couponIdN; 128 | 129 | /** 130 | * 单个代金券或立减优惠支付金额 可空* 131 | */ 132 | @ApiField("coupon_fee_$n") 133 | private Integer couponFeeN; 134 | 135 | /** 136 | * 微信支付订单号 不可空* 137 | */ 138 | @ApiField("transaction_id") 139 | private String transactionId; 140 | 141 | /** 142 | * 商户订单号 不可空* 143 | */ 144 | @ApiField("out_trade_no") 145 | private String outTradeNo; 146 | 147 | /** 148 | * 商家数据包 可空* 149 | */ 150 | @ApiField("attach") 151 | private String attach; 152 | 153 | /** 154 | * 支付完成时间 不可空* 155 | */ 156 | @ApiField("time_end") 157 | private String timeEnd; 158 | 159 | public String getAppId() { 160 | return appId; 161 | } 162 | 163 | public void setAppId(String appId) { 164 | this.appId = appId; 165 | } 166 | 167 | public String getMchId() { 168 | return mchId; 169 | } 170 | 171 | public void setMchId(String mchId) { 172 | this.mchId = mchId; 173 | } 174 | 175 | public String getDeviceInfo() { 176 | return deviceInfo; 177 | } 178 | 179 | public void setDeviceInfo(String deviceInfo) { 180 | this.deviceInfo = deviceInfo; 181 | } 182 | 183 | public String getNonceStr() { 184 | return nonceStr; 185 | } 186 | 187 | public void setNonceStr(String nonceStr) { 188 | this.nonceStr = nonceStr; 189 | } 190 | 191 | public String getSign() { 192 | return sign; 193 | } 194 | 195 | public void setSign(String sign) { 196 | this.sign = sign; 197 | } 198 | 199 | public String getResultCode() { 200 | return resultCode; 201 | } 202 | 203 | public void setResultCode(String resultCode) { 204 | this.resultCode = resultCode; 205 | } 206 | 207 | public String getErrCode() { 208 | return errCode; 209 | } 210 | 211 | public void setErrCode(String errCode) { 212 | this.errCode = errCode; 213 | } 214 | 215 | public String getErrCodeDes() { 216 | return errCodeDes; 217 | } 218 | 219 | public void setErrCodeDes(String errCodeDes) { 220 | this.errCodeDes = errCodeDes; 221 | } 222 | 223 | public String getOpenId() { 224 | return openId; 225 | } 226 | 227 | public void setOpenId(String openId) { 228 | this.openId = openId; 229 | } 230 | 231 | public String getIsSubscrib() { 232 | return isSubscrib; 233 | } 234 | 235 | public void setIsSubscrib(String isSubscrib) { 236 | this.isSubscrib = isSubscrib; 237 | } 238 | 239 | public String getTradeType() { 240 | return tradeType; 241 | } 242 | 243 | public void setTradeType(String tradeType) { 244 | this.tradeType = tradeType; 245 | } 246 | 247 | public String getBankType() { 248 | return bankType; 249 | } 250 | 251 | public void setBankType(String bankType) { 252 | this.bankType = bankType; 253 | } 254 | 255 | public Integer getTotalFee() { 256 | return totalFee; 257 | } 258 | 259 | public void setTotalFee(Integer totalFee) { 260 | this.totalFee = totalFee; 261 | } 262 | 263 | public String getFeeType() { 264 | return feeType; 265 | } 266 | 267 | public void setFeeType(String feeType) { 268 | this.feeType = feeType; 269 | } 270 | 271 | public String getCashFee() { 272 | return cashFee; 273 | } 274 | 275 | public void setCashFee(String cashFee) { 276 | this.cashFee = cashFee; 277 | } 278 | 279 | public Integer getCashFeeType() { 280 | return cashFeeType; 281 | } 282 | 283 | public void setCashFeeType(Integer cashFeeType) { 284 | this.cashFeeType = cashFeeType; 285 | } 286 | 287 | public Integer getCouponFee() { 288 | return couponFee; 289 | } 290 | 291 | public void setCouponFee(Integer couponFee) { 292 | this.couponFee = couponFee; 293 | } 294 | 295 | public Integer getCouponCount() { 296 | return couponCount; 297 | } 298 | 299 | public void setCouponCount(Integer couponCount) { 300 | this.couponCount = couponCount; 301 | } 302 | 303 | public String getCouponIdN() { 304 | return couponIdN; 305 | } 306 | 307 | public void setCouponIdN(String couponIdN) { 308 | this.couponIdN = couponIdN; 309 | } 310 | 311 | public Integer getCouponFeeN() { 312 | return couponFeeN; 313 | } 314 | 315 | public void setCouponFeeN(Integer couponFeeN) { 316 | this.couponFeeN = couponFeeN; 317 | } 318 | 319 | public String getTransactionId() { 320 | return transactionId; 321 | } 322 | 323 | public void setTransactionId(String transactionId) { 324 | this.transactionId = transactionId; 325 | } 326 | 327 | public String getOutTradeNo() { 328 | return outTradeNo; 329 | } 330 | 331 | public void setOutTradeNo(String outTradeNo) { 332 | this.outTradeNo = outTradeNo; 333 | } 334 | 335 | public String getAttach() { 336 | return attach; 337 | } 338 | 339 | public void setAttach(String attach) { 340 | this.attach = attach; 341 | } 342 | 343 | public String getTimeEnd() { 344 | return timeEnd; 345 | } 346 | 347 | public void setTimeEnd(String timeEnd) { 348 | this.timeEnd = timeEnd; 349 | } 350 | 351 | public boolean isBizSuccess() { 352 | return "SUCCESS".equalsIgnoreCase(this.resultCode); 353 | } 354 | 355 | 356 | } 357 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/refund/RefundResponse.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.refund; 2 | 3 | import net.arccode.wechat.pay.api.common.annotation.ApiField; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | 6 | /** 7 | * 微信支付退款响应参数 8 | * 9 | * @author http://arccode.net 10 | * @since 2015-11-02 11 | */ 12 | public class RefundResponse extends WXPayResponse { 13 | private static final long serialVersionUID = 6252751420895289657L; 14 | 15 | /**==================== 以下字段在return_code为SUCCESS的时候有返回 ====================**/ 16 | 17 | /** 18 | * 业务结果 19 | * 必填: 是 String(16) SUCCESS 20 | * SUCCESS/FAIL 21 | * SUCCESS退款申请接收成功,结果通过退款查询接口查询 22 | * FAIL 提交业务失败 23 | */ 24 | @ApiField("result_code") 25 | private String resultCode; 26 | /** 27 | * 错误代码 28 | * 必填: 否 String(32) SYSTEMERROR 列表详见第6节 29 | */ 30 | @ApiField("err_code") 31 | private String errCode; 32 | /** 33 | * 错误代码描述 34 | * 必填: 否 String(128) 系统超时 结果信息描述 35 | */ 36 | @ApiField("err_code_des") 37 | private String errCodeDes; 38 | /** 39 | * 公众账号ID 40 | * 必填: 是 String(32) wx8888888888888888 微信分配的公众账号ID 41 | */ 42 | @ApiField("appid") 43 | private String appId; 44 | /** 45 | * 商户号 46 | * 必填: 是 String(32) 1900000109 微信支付分配的商户号 47 | */ 48 | @ApiField("mch_id") 49 | private String mchId; 50 | /** 51 | * 设备号 52 | * 必填: 否 String(32) 013467007045764 微信支付分配的终端设备号,与下单一致 53 | */ 54 | @ApiField("device_info") 55 | private String deviceInfo; 56 | /** 57 | * 随机字符串 58 | * 必填: 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位 59 | */ 60 | @ApiField("nonce_str") 61 | private String nonceStr; 62 | /** 63 | * 签名 64 | * 必填: 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 签名,详见签名算法 65 | */ 66 | @ApiField("sign") 67 | private String sign; 68 | /** 69 | * 微信订单号 70 | * 必填: 是 String(28) 1217752501201407033233368018 微信订单号 71 | */ 72 | @ApiField("transaction_id") 73 | private String transactionId; 74 | /** 75 | * 商户订单号 76 | * 必填: 是 String(32) 1217752501201407033233368018 商户系统内部的订单号 77 | */ 78 | @ApiField("out_trade_no") 79 | private String outTradeNo; 80 | /** 81 | * 商户退款单号 82 | * 必填: 是 String(32) 1217752501201407033233368018 商户退款单号 83 | */ 84 | @ApiField("out_refund_no") 85 | private String outRefundNo; 86 | /** 87 | * 微信退款单号 88 | * 必填: 是 String(28) 1217752501201407033233368018 微信退款单号 89 | */ 90 | @ApiField("refund_id") 91 | private String refundId; 92 | /** 93 | * 退款渠道 94 | * 必填: 否 String(16) ORIGINAL—原路退款, BALANCE—退回到余额 95 | */ 96 | @ApiField("refund_channel") 97 | private String refundChannel; 98 | 99 | /** 100 | * 退款金额 101 | * 必填: 是 Int 100 退款总金额,单位为分,可以做部分退款 102 | */ 103 | @ApiField("refund_fee") 104 | private String refundFee; 105 | /** 106 | * 订单总金额 107 | * 必填: 是 Int 100 订单总金额,单位为分,只能为整数,详见支付金额 108 | */ 109 | @ApiField("total_fee") 110 | private String totalFee; 111 | /** 112 | * 订单金额货币种类 113 | * 必填: 否 String(8) CNY 订单金额货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 114 | */ 115 | @ApiField("fee_type") 116 | private String feeType; 117 | /** 118 | * 现金支付金额 119 | * 必填: 是 Int 100 现金支付金额,单位为分,只能为整数,详见支付金额 120 | */ 121 | @ApiField("cash_fee") 122 | private String cashFee; 123 | /** 124 | * 现金退款金额 125 | * 必填: 否 Int 100 现金退款金额,单位为分,只能为整数,详见支付金额 126 | */ 127 | @ApiField("cash_refund_fee") 128 | private String cashRefundFee; 129 | /** 130 | * 代金券或立减优惠退款金额 131 | * 必填: 否 Int 100 代金券或立减优惠退款金额=订单金额-现金退款金额,注意:立减优惠金额不会退回 132 | */ 133 | @ApiField("coupon_refund_fee") 134 | private String couponRefundFee; 135 | /** 136 | * 代金券或立减优惠使用数量 137 | * 必填: 否 Int 1 代金券或立减优惠使用数量 138 | */ 139 | @ApiField("coupon_refund_count") 140 | private String couponRefundCount; 141 | /** 142 | * 代金券或立减优惠ID 143 | * 否 String(20) 10000 代金券或立减优惠ID 144 | */ 145 | @ApiField("coupon_refund_id") 146 | private String couponRefundId; 147 | 148 | public String getResultCode() { 149 | return resultCode; 150 | } 151 | 152 | public void setResultCode(String resultCode) { 153 | this.resultCode = resultCode; 154 | } 155 | 156 | public String getErrCode() { 157 | return errCode; 158 | } 159 | 160 | public void setErrCode(String errCode) { 161 | this.errCode = errCode; 162 | } 163 | 164 | public String getErrCodeDes() { 165 | return errCodeDes; 166 | } 167 | 168 | public void setErrCodeDes(String errCodeDes) { 169 | this.errCodeDes = errCodeDes; 170 | } 171 | 172 | public String getAppId() { 173 | return appId; 174 | } 175 | 176 | public void setAppId(String appId) { 177 | this.appId = appId; 178 | } 179 | 180 | public String getMchId() { 181 | return mchId; 182 | } 183 | 184 | public void setMchId(String mchId) { 185 | this.mchId = mchId; 186 | } 187 | 188 | public String getDeviceInfo() { 189 | return deviceInfo; 190 | } 191 | 192 | public void setDeviceInfo(String deviceInfo) { 193 | this.deviceInfo = deviceInfo; 194 | } 195 | 196 | public String getNonceStr() { 197 | return nonceStr; 198 | } 199 | 200 | public void setNonceStr(String nonceStr) { 201 | this.nonceStr = nonceStr; 202 | } 203 | 204 | public String getSign() { 205 | return sign; 206 | } 207 | 208 | public void setSign(String sign) { 209 | this.sign = sign; 210 | } 211 | 212 | public String getTransactionId() { 213 | return transactionId; 214 | } 215 | 216 | public void setTransactionId(String transactionId) { 217 | this.transactionId = transactionId; 218 | } 219 | 220 | public String getOutTradeNo() { 221 | return outTradeNo; 222 | } 223 | 224 | public void setOutTradeNo(String outTradeNo) { 225 | this.outTradeNo = outTradeNo; 226 | } 227 | 228 | public String getOutRefundNo() { 229 | return outRefundNo; 230 | } 231 | 232 | public void setOutRefundNo(String outRefundNo) { 233 | this.outRefundNo = outRefundNo; 234 | } 235 | 236 | public String getRefundId() { 237 | return refundId; 238 | } 239 | 240 | public void setRefundId(String refundId) { 241 | this.refundId = refundId; 242 | } 243 | 244 | public String getRefundChannel() { 245 | return refundChannel; 246 | } 247 | 248 | public void setRefundChannel(String refundChannel) { 249 | this.refundChannel = refundChannel; 250 | } 251 | 252 | public String getRefundFee() { 253 | return refundFee; 254 | } 255 | 256 | public void setRefundFee(String refundFee) { 257 | this.refundFee = refundFee; 258 | } 259 | 260 | public String getTotalFee() { 261 | return totalFee; 262 | } 263 | 264 | public void setTotalFee(String totalFee) { 265 | this.totalFee = totalFee; 266 | } 267 | 268 | public String getFeeType() { 269 | return feeType; 270 | } 271 | 272 | public void setFeeType(String feeType) { 273 | this.feeType = feeType; 274 | } 275 | 276 | public String getCashFee() { 277 | return cashFee; 278 | } 279 | 280 | public void setCashFee(String cashFee) { 281 | this.cashFee = cashFee; 282 | } 283 | 284 | public String getCashRefundFee() { 285 | return cashRefundFee; 286 | } 287 | 288 | public void setCashRefundFee(String cashRefundFee) { 289 | this.cashRefundFee = cashRefundFee; 290 | } 291 | 292 | public String getCouponRefundFee() { 293 | return couponRefundFee; 294 | } 295 | 296 | public void setCouponRefundFee(String couponRefundFee) { 297 | this.couponRefundFee = couponRefundFee; 298 | } 299 | 300 | public String getCouponRefundCount() { 301 | return couponRefundCount; 302 | } 303 | 304 | public void setCouponRefundCount(String couponRefundCount) { 305 | this.couponRefundCount = couponRefundCount; 306 | } 307 | 308 | public String getCouponRefundId() { 309 | return couponRefundId; 310 | } 311 | 312 | public void setCouponRefundId(String couponRefundId) { 313 | this.couponRefundId = couponRefundId; 314 | } 315 | } 316 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | net.arccode 8 | wechat-pay-sdk 9 | 1.1.1 10 | jar 11 | 12 | wechat-pay-sdk 13 | wechat pay SDK, out of the box. 14 | https://github.com/arccode/wechat-pay-sdk 15 | 16 | 17 | UTF-8 18 | 1.7 19 | 20 | 2.5.0 21 | 1.4.7 22 | 2.0.6 23 | 1.7.9 24 | 1.0.9 25 | 4.11 26 | 1.1.46 27 | 4.5 28 | 29 | 30 | 31 | 32 | MIT 33 | https://opensource.org/licenses/MIT 34 | 35 | 36 | 37 | 38 | 39 | arccode 40 | zuitiku@gmail.com 41 | 42 | 43 | 44 | 45 | https://github.com/arccode/wechat-pay-sdk 46 | wechat-pay-sdk-1.0.0 47 | 48 | 49 | 50 | 51 | 52 | com.squareup.okhttp 53 | okhttp 54 | ${okhttp.version} 55 | 56 | 57 | 58 | org.slf4j 59 | slf4j-api 60 | ${slf4j.version} 61 | provided 62 | 63 | 64 | 65 | ch.qos.logback 66 | logback-classic 67 | ${logback.version} 68 | provided 69 | 70 | 71 | 72 | junit 73 | junit 74 | ${junit.version} 75 | test 76 | 77 | 78 | 79 | com.alibaba 80 | fastjson 81 | ${fast.json.version} 82 | test 83 | 84 | 85 | 86 | 87 | org.apache.httpcomponents 88 | httpclient 89 | ${http.client.version} 90 | 91 | 92 | 93 | 94 | 95 | ${project.name} 96 | 97 | 98 | 99 | org.apache.maven.plugins 100 | maven-compiler-plugin 101 | 3.5.1 102 | 103 | UTF-8 104 | ${jdk.version} 105 | ${jdk.version} 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-site-plugin 112 | 3.5 113 | 114 | 115 | prepare-package 116 | 117 | site 118 | 119 | 120 | 121 | 122 | 123 | org.apache.maven.plugins 124 | maven-source-plugin 125 | 3.0.0 126 | 127 | 128 | prepare-package 129 | 130 | jar-no-fork 131 | 132 | 133 | 134 | 135 | 136 | org.apache.maven.plugins 137 | maven-javadoc-plugin 138 | 2.10.3 139 | 140 | 141 | prepare-package 142 | 143 | jar 144 | 145 | 149 | 150 | 151 | 152 | 153 | org.apache.maven.plugins 154 | maven-jar-plugin 155 | 2.6 156 | 157 | 158 | 159 | org.mybatis.generator.api.ShellRunner 160 | 161 | 162 | 163 | 164 | 165 | org.apache.maven.plugins 166 | maven-assembly-plugin 167 | 2.6 168 | 169 | 170 | ${project.basedir}/src/main/assembly/src.xml 171 | 172 | 173 | 174 | 175 | bundle 176 | 177 | single 178 | 179 | package 180 | 181 | 182 | 183 | 184 | org.apache.maven.plugins 185 | maven-release-plugin 186 | 2.5.3 187 | 188 | -Prelease 189 | 190 | 191 | 192 | 193 | org.apache.maven.plugins 194 | maven-gpg-plugin 195 | 1.5 196 | 197 | 198 | verify 199 | 200 | sign 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | org.codehaus.mojo 212 | jdepend-maven-plugin 213 | 2.0 214 | 215 | 216 | 217 | 218 | 219 | 220 | master 221 | Wechat pay SDK GitHub Pages 222 | https://github.com/arccode/wechat-pay-sdk 223 | 224 | 225 | oss 226 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 227 | 228 | 229 | 230 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/log/ACLogger.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.log; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | import net.arccode.wechat.pay.api.common.util.ACHashMap; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | import java.io.IOException; 10 | import java.io.UnsupportedEncodingException; 11 | import java.net.HttpURLConnection; 12 | import java.net.InetAddress; 13 | import java.text.DateFormat; 14 | import java.text.SimpleDateFormat; 15 | import java.util.*; 16 | 17 | /** 18 | * 日志记录 19 | *

20 | * 通讯错误格式:time^_^api^_^app^_^ip^_^os^_^sdk^_^returnCode 21 | * 业务错误格式:time^_^response 22 | * 23 | * @author http://arccode.net 24 | * @since 2015-11-05 25 | */ 26 | public class ACLogger { 27 | 28 | /** 29 | * 通用错误日志 30 | */ 31 | private static final Logger CLOG = LoggerFactory.getLogger("sdk.comm.err"); 32 | 33 | /** 34 | * 业务错误日志 35 | */ 36 | private static final Logger BLOG = LoggerFactory.getLogger("sdk.biz.err"); 37 | 38 | private static String osName = System.getProperties().getProperty("os.name"); 39 | private static String ip = null; 40 | private static boolean needEnableLogger = true; 41 | 42 | public static void setNeedEnableLogger(boolean needEnableLogger) { 43 | ACLogger.needEnableLogger = needEnableLogger; 44 | } 45 | 46 | public static String getIp() { 47 | if (ip == null) { 48 | try { 49 | ip = InetAddress.getLocalHost().getHostAddress(); 50 | } catch (Exception e) { 51 | e.printStackTrace(); 52 | } 53 | } 54 | return ip; 55 | } 56 | 57 | public static void setIp(String ip) { 58 | ACLogger.ip = ip; 59 | } 60 | 61 | /** 62 | * 通讯错误日志 63 | */ 64 | public static void logCommError(Exception e, HttpURLConnection conn, String appKey, String method, byte[] content) { 65 | if (!needEnableLogger) { 66 | return; 67 | } 68 | String contentString = null; 69 | try { 70 | contentString = new String(content, "UTF-8"); 71 | logCommError(e, conn, appKey, method, contentString); 72 | } catch (Exception e1) { 73 | e1.printStackTrace(); 74 | } 75 | } 76 | 77 | /** 78 | * 通讯错误日志 79 | */ 80 | public static void logCommError(Exception e, String url, String appKey, String method, byte[] content) { 81 | if (!needEnableLogger) { 82 | return; 83 | } 84 | String contentString = null; 85 | try { 86 | contentString = new String(content, "UTF-8"); 87 | logCommError(e, url, appKey, method, contentString); 88 | } catch (UnsupportedEncodingException e1) { 89 | e1.printStackTrace(); 90 | } 91 | } 92 | 93 | /** 94 | * 通讯错误日志 95 | */ 96 | public static void logCommError(Exception e, HttpURLConnection conn, String appKey, String method, Map params) { 97 | if (!needEnableLogger) { 98 | return; 99 | } 100 | _logCommError(e, conn, null, appKey, method, params); 101 | } 102 | 103 | public static void logCommError(Exception e, String url, String appKey, String method, Map params) { 104 | if (!needEnableLogger) { 105 | return; 106 | } 107 | _logCommError(e, null, url, appKey, method, params); 108 | } 109 | 110 | /** 111 | * 通讯错误日志 112 | */ 113 | private static void logCommError(Exception e, HttpURLConnection conn, String appKey, String method, String content) { 114 | Map params = parseParam(content); 115 | _logCommError(e, conn, null, appKey, method, params); 116 | } 117 | 118 | /** 119 | * 通讯错误日志 120 | */ 121 | private static void logCommError(Exception e, String url, String appKey, String method, String content) { 122 | Map params = parseParam(content); 123 | _logCommError(e, null, url, appKey, method, params); 124 | } 125 | 126 | /** 127 | * 通讯错误日志 128 | */ 129 | private static void _logCommError(Exception e, HttpURLConnection conn, String url, String appKey, String method, Map params) { 130 | DateFormat df = new SimpleDateFormat(WXPayConstants.DATE_TIME_FORMAT); 131 | df.setTimeZone(TimeZone.getTimeZone(WXPayConstants.DATE_TIMEZONE)); 132 | String sdkName = WXPayConstants.SDK_VERSION; 133 | String urlStr = null; 134 | String rspCode = ""; 135 | if (conn != null) { 136 | try { 137 | urlStr = conn.getURL().toString(); 138 | rspCode = "HTTP_ERROR_" + conn.getResponseCode(); 139 | } catch (IOException ioe) { 140 | } 141 | } else { 142 | urlStr = url; 143 | rspCode = ""; 144 | } 145 | StringBuilder sb = new StringBuilder(); 146 | sb.append(df.format(new Date()));// 时间 147 | sb.append("^_^"); 148 | sb.append(method);// API 149 | sb.append("^_^"); 150 | sb.append(appKey);// APP 151 | sb.append("^_^"); 152 | sb.append(getIp());// IP地址 153 | sb.append("^_^"); 154 | sb.append(osName);// 操作系统 155 | sb.append("^_^"); 156 | sb.append(sdkName);// SDK名字 157 | sb.append("^_^"); 158 | sb.append(urlStr);// 请求URL 159 | sb.append("^_^"); 160 | sb.append(rspCode); 161 | sb.append("^_^"); 162 | sb.append((e.getMessage() + "").replaceAll("\r\n", " ")); 163 | CLOG.error(sb.toString()); 164 | } 165 | 166 | private static Map parseParam(String contentString) { 167 | Map params = new HashMap(); 168 | if (contentString == null || contentString.trim().equals("")) { 169 | return params; 170 | } 171 | String[] paramsArray = contentString.split("\\&"); 172 | if (paramsArray != null) { 173 | for (String param : paramsArray) { 174 | String[] keyValue = param.split("="); 175 | if (keyValue != null && keyValue.length == 2) { 176 | params.put(keyValue[0], keyValue[1]); 177 | } 178 | } 179 | } 180 | return params; 181 | } 182 | 183 | /** 184 | * 业务/系统错误日志 185 | */ 186 | public static void logBizError(String rsp) { 187 | if (!needEnableLogger) { 188 | return; 189 | } 190 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 191 | df.setTimeZone(TimeZone.getTimeZone(WXPayConstants.DATE_TIMEZONE)); 192 | StringBuilder sb = new StringBuilder(); 193 | sb.append(df.format(new Date())); 194 | sb.append("^_^"); 195 | sb.append(rsp); 196 | BLOG.error(sb.toString()); 197 | } 198 | 199 | /** 200 | * 发生特别错误时记录完整错误现场 201 | */ 202 | public static void logErrorScene(Map rt, WXPayResponse tResp, String appSecret) { 203 | if (!needEnableLogger) { 204 | return; 205 | } 206 | DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 207 | df.setTimeZone(TimeZone.getTimeZone(WXPayConstants.DATE_TIMEZONE)); 208 | StringBuilder sb = new StringBuilder(); 209 | sb.append("ErrorScene"); 210 | sb.append("^_^"); 211 | sb.append(rt.get("url")); 212 | sb.append("^_^"); 213 | sb.append(tResp.getReturnCode()); 214 | sb.append("^_^"); 215 | sb.append(tResp.getReturnMsg()); 216 | sb.append("^_^"); 217 | sb.append(getIp()); 218 | sb.append("^_^"); 219 | sb.append(osName); 220 | sb.append("^_^"); 221 | sb.append(df.format(new Date())); 222 | sb.append("^_^"); 223 | sb.append("ProtocolMustParams:"); 224 | appendLog((ACHashMap) rt.get("protocolMustParams"), sb); 225 | sb.append("^_^"); 226 | sb.append("ProtocolOptParams:"); 227 | appendLog((ACHashMap) rt.get("protocolOptParams"), sb); 228 | sb.append("^_^"); 229 | sb.append("ApplicationParams:"); 230 | appendLog((ACHashMap) rt.get("params"), sb); 231 | sb.append("^_^"); 232 | sb.append("Body:"); 233 | sb.append((String) rt.get("resp")); 234 | BLOG.error(sb.toString()); 235 | } 236 | 237 | private static void appendLog(ACHashMap map, StringBuilder sb) { 238 | boolean first = true; 239 | Set> set = map.entrySet(); 240 | for (Map.Entry entry : set) { 241 | if (!first) { 242 | sb.append("&"); 243 | } else { 244 | first = false; 245 | } 246 | sb.append(entry.getKey()).append("=").append(entry.getValue()); 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/protocol/query_order/QueryOrderResponse.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.protocol.query_order; 2 | 3 | import net.arccode.wechat.pay.api.common.annotation.ApiField; 4 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 5 | 6 | /** 7 | * 查询清单详情响应参数 8 | * 9 | * @author http://arccode.net 10 | * @since 2018-04-22 11 | */ 12 | public class QueryOrderResponse extends WXPayResponse { 13 | 14 | private static final long serialVersionUID = 8009307874140691769L; 15 | 16 | 17 | /**==================== 以下字段在return_code为SUCCESS的时候有返回 ====================**/ 18 | 19 | 20 | /** 21 | * 应用APPID 22 | * 必填: 是 String(32) wxd678efh567hg6787 微信开放平台审核通过的应用APPID 23 | */ 24 | @ApiField("appid") 25 | private String appId; 26 | /** 27 | * 商户号 28 | * 必填: 是 String(32) 1230000109 微信支付分配的商户号 29 | */ 30 | @ApiField("mch_id") 31 | private String mchId; 32 | /** 33 | * 随机字符串 34 | * 必填: 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法 35 | */ 36 | @ApiField("nonce_str") 37 | private String nonceStr; 38 | /** 39 | * 签名 40 | * 必填: 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法 41 | */ 42 | @ApiField("sign") 43 | private String sign; 44 | /** 45 | * 业务结果 46 | * 必填: 是 String(16) SUCCESS SUCCESS/FAIL 47 | */ 48 | @ApiField("result_code") 49 | private String resultCode; 50 | /** 51 | * 错误代码 52 | * 必填: 否 String(32) SYSTEMERROR 错误码 53 | */ 54 | @ApiField("err_code") 55 | private String errCode; 56 | /** 57 | * 错误代码描述 58 | * 必填: 否 String(128) 系统错误 结果信息描述 59 | */ 60 | @ApiField("err_code_des") 61 | private String errCodeDes; 62 | 63 | 64 | /**==================== 以下字段在return_code 和result_code都为SUCCESS的时候有返回 ====================**/ 65 | 66 | /** 67 | * 设备号 68 | * 否 String(32) 013467007045764 微信支付分配的终端设备号, 69 | */ 70 | @ApiField("device_info") 71 | private String deviceInfo; 72 | /** 73 | * 用户标识 74 | * 是 String(128) oUpF8uMuAJO_M2pxb1Q9zNjWeS6o 用户在商户appid下的唯一标识 75 | */ 76 | @ApiField("openid") 77 | private String openId; 78 | /** 79 | * 是否关注公众账号 80 | * 否 String(1) Y 用户是否关注公众账号,Y-关注,N-未关注,仅在公众账号类型支付有效 81 | */ 82 | @ApiField("is_subscribe") 83 | private String isSubscribe; 84 | /** 85 | * 交易类型 86 | * 是 String(16) APP 调用接口提交的交易类型 87 | */ 88 | @ApiField("trade_type") 89 | private String tradeType; 90 | /** 91 | * 交易状态 92 | * 是 String(32) SUCCESS 93 | * SUCCESS—支付成功 94 | * REFUND—转入退款 95 | * NOTPAY—未支付 96 | * CLOSED—已关闭 97 | * REVOKED—已撤销(刷卡支付) 98 | * USERPAYING--用户支付中 99 | * PAYERROR--支付失败(其他原因,如银行返回失败) 100 | */ 101 | @ApiField("trade_state") 102 | private String tradeState; 103 | 104 | /** 105 | * 付款银行 106 | * 是 String(16) CMC 银行类型,采用字符串类型的银行标识 107 | */ 108 | @ApiField("bank_type") 109 | private String bankType; 110 | /** 111 | * 总金额 112 | * 是 Int 100 订单总金额,单位为分 113 | */ 114 | @ApiField("total_fee") 115 | private Integer totalFee; 116 | /** 117 | * 货币种类 118 | * 否 String(8) CNY 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 119 | */ 120 | @ApiField("fee_type") 121 | private String feeType; 122 | /** 123 | * 现金支付金额 124 | * 是 Int 100 现金支付金额订单现金支付金额,详见支付金额 125 | */ 126 | @ApiField("cash_fee") 127 | private Integer cashFee; 128 | /** 129 | * 现金支付货币类型 130 | * 否 String(16) CNY 货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY,其他值列表详见货币类型 131 | */ 132 | @ApiField("cash_fee_type") 133 | private String cashFeeType; 134 | /** 135 | * 应结订单金额 136 | * 否 Int 100 当订单使用了免充值型优惠券后返回该参数,应结订单金额=订单金额-免充值优惠券金额。 137 | */ 138 | @ApiField("settlement_total_fee") 139 | private Integer settlementTotalFee; 140 | /** 141 | * 代金券金额 142 | * 否 Int 100 “代金券或立减优惠”金额<=订单总金额,订单总金额-“代金券或立减优惠”金额=现金支付金额,详见支付金额 143 | */ 144 | @ApiField("coupon_fee") 145 | private Integer couponFee; 146 | /** 147 | * 代金券使用数量 148 | * 否 Int 1 代金券或立减优惠使用数量 149 | */ 150 | @ApiField("coupon_count") 151 | private Integer couponCount; 152 | /** 153 | * 代金券ID 154 | * 否 String(20) 10000 代金券或立减优惠ID, $n为下标,从0开始编号 155 | */ 156 | @ApiField("coupon_id_$n") 157 | private String couponIdN; 158 | /** 159 | * 代金券类型 160 | * 否 String CASH 161 | * CASH--充值代金券 162 | * NO_CASH---非充值优惠券 163 | * 开通免充值券功能,并且订单使用了优惠券后有返回(取值:CASH、NO_CASH)。$n为下标,从0开始编号,举例:coupon_type_$0 164 | */ 165 | @ApiField("coupon_type_$n") 166 | private String couponTypeN; 167 | /** 168 | * 单个代金券支付金额 169 | * 否 Int 100 单个代金券或立减优惠支付金额, $n为下标,从0开始编号 170 | */ 171 | @ApiField("coupon_fee_$n") 172 | private Integer couponFeeN; 173 | /** 174 | * 微信支付订单号 175 | * 是 String(32) 1009660380201506130728806387 微信支付订单号 176 | */ 177 | @ApiField("transaction_id") 178 | private String transactionId; 179 | /** 180 | * 商户订单号 181 | * 是 String(32) 20150806125346 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。 182 | */ 183 | @ApiField("out_trade_no") 184 | private String outTradeNo; 185 | /** 186 | * 附加数据 187 | * 否 String(128) 深圳分店 附加数据,原样返回 188 | */ 189 | @ApiField("attach") 190 | private String attach; 191 | /** 192 | * 支付完成时间 193 | * 是 String(14) 20141030133525 194 | * 订单支付时间,格式为yyyyMMddHHmmss,如2009年12月25日9点10分10秒表示为20091225091010。其他详见时间规则 195 | */ 196 | @ApiField("time_end") 197 | private String timeEnd; 198 | /** 199 | * 交易状态描述 200 | * 是 String(256) 支付失败,请重新下单支付 对当前查询订单状态的描述和下一步操作的指引 201 | */ 202 | @ApiField("trade_state_desc") 203 | private String tradeStateDesc; 204 | 205 | public String getAppId() { 206 | return appId; 207 | } 208 | 209 | public void setAppId(String appId) { 210 | this.appId = appId; 211 | } 212 | 213 | public String getMchId() { 214 | return mchId; 215 | } 216 | 217 | public void setMchId(String mchId) { 218 | this.mchId = mchId; 219 | } 220 | 221 | public String getNonceStr() { 222 | return nonceStr; 223 | } 224 | 225 | public void setNonceStr(String nonceStr) { 226 | this.nonceStr = nonceStr; 227 | } 228 | 229 | public String getSign() { 230 | return sign; 231 | } 232 | 233 | public void setSign(String sign) { 234 | this.sign = sign; 235 | } 236 | 237 | public String getResultCode() { 238 | return resultCode; 239 | } 240 | 241 | public void setResultCode(String resultCode) { 242 | this.resultCode = resultCode; 243 | } 244 | 245 | public String getErrCode() { 246 | return errCode; 247 | } 248 | 249 | public void setErrCode(String errCode) { 250 | this.errCode = errCode; 251 | } 252 | 253 | public String getErrCodeDes() { 254 | return errCodeDes; 255 | } 256 | 257 | public void setErrCodeDes(String errCodeDes) { 258 | this.errCodeDes = errCodeDes; 259 | } 260 | 261 | public String getDeviceInfo() { 262 | return deviceInfo; 263 | } 264 | 265 | public void setDeviceInfo(String deviceInfo) { 266 | this.deviceInfo = deviceInfo; 267 | } 268 | 269 | public String getOpenId() { 270 | return openId; 271 | } 272 | 273 | public void setOpenId(String openId) { 274 | this.openId = openId; 275 | } 276 | 277 | public String getIsSubscribe() { 278 | return isSubscribe; 279 | } 280 | 281 | public void setIsSubscribe(String isSubscribe) { 282 | this.isSubscribe = isSubscribe; 283 | } 284 | 285 | public String getTradeType() { 286 | return tradeType; 287 | } 288 | 289 | public void setTradeType(String tradeType) { 290 | this.tradeType = tradeType; 291 | } 292 | 293 | public String getTradeState() { 294 | return tradeState; 295 | } 296 | 297 | public void setTradeState(String tradeState) { 298 | this.tradeState = tradeState; 299 | } 300 | 301 | public String getBankType() { 302 | return bankType; 303 | } 304 | 305 | public void setBankType(String bankType) { 306 | this.bankType = bankType; 307 | } 308 | 309 | public Integer getTotalFee() { 310 | return totalFee; 311 | } 312 | 313 | public void setTotalFee(Integer totalFee) { 314 | this.totalFee = totalFee; 315 | } 316 | 317 | public String getFeeType() { 318 | return feeType; 319 | } 320 | 321 | public void setFeeType(String feeType) { 322 | this.feeType = feeType; 323 | } 324 | 325 | public Integer getCashFee() { 326 | return cashFee; 327 | } 328 | 329 | public void setCashFee(Integer cashFee) { 330 | this.cashFee = cashFee; 331 | } 332 | 333 | public String getCashFeeType() { 334 | return cashFeeType; 335 | } 336 | 337 | public void setCashFeeType(String cashFeeType) { 338 | this.cashFeeType = cashFeeType; 339 | } 340 | 341 | public Integer getSettlementTotalFee() { 342 | return settlementTotalFee; 343 | } 344 | 345 | public void setSettlementTotalFee(Integer settlementTotalFee) { 346 | this.settlementTotalFee = settlementTotalFee; 347 | } 348 | 349 | public Integer getCouponFee() { 350 | return couponFee; 351 | } 352 | 353 | public void setCouponFee(Integer couponFee) { 354 | this.couponFee = couponFee; 355 | } 356 | 357 | public Integer getCouponCount() { 358 | return couponCount; 359 | } 360 | 361 | public void setCouponCount(Integer couponCount) { 362 | this.couponCount = couponCount; 363 | } 364 | 365 | public String getCouponIdN() { 366 | return couponIdN; 367 | } 368 | 369 | public void setCouponIdN(String couponIdN) { 370 | this.couponIdN = couponIdN; 371 | } 372 | 373 | public String getCouponTypeN() { 374 | return couponTypeN; 375 | } 376 | 377 | public void setCouponTypeN(String couponTypeN) { 378 | this.couponTypeN = couponTypeN; 379 | } 380 | 381 | public Integer getCouponFeeN() { 382 | return couponFeeN; 383 | } 384 | 385 | public void setCouponFeeN(Integer couponFeeN) { 386 | this.couponFeeN = couponFeeN; 387 | } 388 | 389 | public String getTransactionId() { 390 | return transactionId; 391 | } 392 | 393 | public void setTransactionId(String transactionId) { 394 | this.transactionId = transactionId; 395 | } 396 | 397 | public String getOutTradeNo() { 398 | return outTradeNo; 399 | } 400 | 401 | public void setOutTradeNo(String outTradeNo) { 402 | this.outTradeNo = outTradeNo; 403 | } 404 | 405 | public String getAttach() { 406 | return attach; 407 | } 408 | 409 | public void setAttach(String attach) { 410 | this.attach = attach; 411 | } 412 | 413 | public String getTimeEnd() { 414 | return timeEnd; 415 | } 416 | 417 | public void setTimeEnd(String timeEnd) { 418 | this.timeEnd = timeEnd; 419 | } 420 | 421 | public String getTradeStateDesc() { 422 | return tradeStateDesc; 423 | } 424 | 425 | public void setTradeStateDesc(String tradeStateDesc) { 426 | this.tradeStateDesc = tradeStateDesc; 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/service/WXPayClient.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.service; 2 | 3 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 4 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 5 | import net.arccode.wechat.pay.api.common.log.ACLogger; 6 | import net.arccode.wechat.pay.api.common.parser.xml.ObjectXmlParser; 7 | import net.arccode.wechat.pay.api.common.util.*; 8 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 9 | import net.arccode.wechat.pay.api.common.parser.WXPayParser; 10 | import net.arccode.wechat.pay.api.protocol.base.WXPayRequest; 11 | import com.squareup.okhttp.*; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import java.io.IOException; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | /** 20 | * @author http://arccode.net 21 | * @since 2015-11-05 22 | */ 23 | public class WXPayClient implements IWXPayClient { 24 | 25 | private static final Logger LOG = LoggerFactory.getLogger(WXPayClient.class); 26 | 27 | /** 28 | * 公众号appId 29 | */ 30 | private String appId; 31 | 32 | /** 33 | * 商户号 34 | */ 35 | private String mchId; 36 | 37 | /** 38 | * 随机字符串 39 | */ 40 | private String nonceStr; 41 | 42 | /** 43 | * 签名key 44 | */ 45 | private String key; 46 | 47 | /** 48 | * 针对高级接口需要加载证书, 证书密码 49 | */ 50 | private String certPwd; 51 | 52 | /** 53 | * 针对高级接口需要加载证书, 证书路径 54 | */ 55 | private String certPath; 56 | 57 | /** 58 | * 签名类型 59 | */ 60 | private String signType = WXPayConstants.SIGN_TYPE_MD5; 61 | 62 | /** 63 | * 响应格式 64 | */ 65 | private String format = WXPayConstants.FORMAT_XML; 66 | 67 | /** 68 | * 签名字符集 69 | */ 70 | private String charset = WXPayConstants.CHARSET_UTF8; 71 | 72 | /** 73 | * media type json 74 | */ 75 | private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json;charset=utf-8"); 76 | 77 | /** 78 | * media type text 79 | */ 80 | private static final MediaType MEDIA_TYPE_TEXT = MediaType.parse("text/plain;charset=utf-8"); 81 | 82 | 83 | public WXPayClient(String appId, String mchId, String key) { 84 | this.appId = appId; 85 | this.mchId = mchId; 86 | this.key = key; 87 | } 88 | 89 | public WXPayClient(String appId, String mchId, String key, String certPwd, String certPath) { 90 | this.appId = appId; 91 | this.mchId = mchId; 92 | this.key = key; 93 | this.certPwd = certPwd; 94 | this.certPath = certPath; 95 | } 96 | 97 | @Override 98 | public T execute(WXPayRequest request) throws WXPayApiException { 99 | 100 | WXPayParser parser = null; 101 | if (WXPayConstants.FORMAT_XML.equals(this.format)) { 102 | parser = new ObjectXmlParser(request.getResponseClass()); 103 | } 104 | 105 | return _execute(request, parser); 106 | } 107 | 108 | @Override 109 | public T parseNotify(String notifyData, Class clazz) throws WXPayApiException { 110 | WXPayParser parser = new ObjectXmlParser(clazz); 111 | 112 | T tResp = null; 113 | try { 114 | tResp = parser.parse(notifyData); 115 | } catch (WXPayApiException e) { 116 | ACLogger.logBizError(notifyData); 117 | throw new WXPayApiException(e); 118 | } 119 | 120 | tResp.setBody(notifyData); 121 | 122 | return tResp; 123 | } 124 | 125 | /** 126 | * 解析返回内容 127 | * 128 | * @param request 129 | * @param parser 130 | * @param 131 | * @return 132 | */ 133 | private T _execute(WXPayRequest request, WXPayParser parser) throws WXPayApiException { 134 | 135 | Map result = new HashMap(); 136 | if (WXPayConstants.HTTP_POST.equalsIgnoreCase(request.getHttpVerb())) { 137 | result = doPost(request); 138 | } else if (WXPayConstants.HTTPS_POST_CA_MCH_PAY.equalsIgnoreCase(request.getHttpVerb())) { 139 | result = doHTTPSPostMchPay(request); 140 | } else if (WXPayConstants.HTTPS_POST_CA_REFUND.equalsIgnoreCase(request.getHttpVerb())) { 141 | result = doHTTPSPostRefund(request); 142 | } 143 | 144 | T tResp = null; 145 | 146 | // TODO 针对部分接口进行签名校验 147 | 148 | try { 149 | tResp = parser.parse((String)result.get("resp")); 150 | } catch (WXPayApiException e) { 151 | ACLogger.logBizError((String) result.get("resp")); 152 | throw new WXPayApiException(e); 153 | } 154 | tResp.setBody((String)result.get("resp")); 155 | tResp.setParams((Map)result.get("params")); 156 | 157 | if (!tResp.isSuccess()) { 158 | ACLogger.logErrorScene(result, tResp, ""); 159 | } 160 | 161 | 162 | return tResp; 163 | } 164 | 165 | /** 166 | * post 请求 167 | * 168 | * @param request 169 | * @param 170 | * @return 171 | */ 172 | private Map doPost(WXPayRequest request) throws WXPayApiException { 173 | 174 | Map result = new HashMap(); 175 | RequestParametersHolder requestParametersHolder = new RequestParametersHolder(); 176 | 177 | // 应用参数 178 | ACHashMap appParams = new ACHashMap(request.getApplicationParams()); 179 | requestParametersHolder.setApplicationParams(appParams); 180 | 181 | // 协议必选参数 182 | ACHashMap protocolMustParams = new ACHashMap(); 183 | protocolMustParams.put(WXPayConstants.APP_ID, this.appId); 184 | protocolMustParams.put(WXPayConstants.MCH_ID, this.mchId); 185 | requestParametersHolder.setProtocalMustParams(protocolMustParams); 186 | 187 | // 协议可选参数 188 | ACHashMap protocolOptParams = new ACHashMap(); 189 | requestParametersHolder.setProtocalOptParams(protocolOptParams); 190 | 191 | // 签名 192 | if (WXPayConstants.SIGN_TYPE_MD5.equals(this.signType)) { 193 | String signContent = WXPaySignUtils.getSignatureContent(requestParametersHolder); 194 | protocolMustParams.put(WXPayConstants.SIGN, WXPaySignUtils.md5Sign(signContent, key, charset).toUpperCase()); 195 | } 196 | 197 | // 将应用参数 协议必选参数 协议可选参数合并, 转换为xml string 198 | ACHashMap requestParamsMap = new ACHashMap(); 199 | requestParamsMap.putAll(appParams); 200 | requestParamsMap.putAll(protocolMustParams); 201 | requestParamsMap.putAll(protocolOptParams); 202 | 203 | String requestBoyStr = MapUtils.map2XmlString(requestParamsMap); 204 | 205 | // 发送http post 206 | RequestBody requestBody = RequestBody.create(MEDIA_TYPE_TEXT, requestBoyStr); 207 | Request httpRequest = new Request.Builder() 208 | .url(request.getApiURL()) 209 | .post(requestBody) 210 | .build(); 211 | 212 | Response response = null; 213 | try { 214 | response = HttpUtils.execute(httpRequest); 215 | } catch (IOException e) { 216 | throw new WXPayApiException(e); 217 | } 218 | 219 | if (response != null && response.isSuccessful()) { 220 | try { 221 | String resp = response.body().string(); 222 | result.put("resp", resp); 223 | } catch (IOException e) { 224 | throw new WXPayApiException(e); 225 | } 226 | } 227 | 228 | result.put("params", requestParamsMap); 229 | result.put("protocolMustParams", protocolMustParams); 230 | result.put("protocolOptParams", protocolOptParams); 231 | result.put("url", request.getApiURL()); 232 | 233 | return result; 234 | } 235 | 236 | 237 | /** 238 | * HTTPS POST 请求,带证书; 目前用于企业付款 239 | * 240 | * @param request 241 | * @param 242 | * @return 243 | */ 244 | private Map doHTTPSPostMchPay(WXPayRequest request) throws WXPayApiException { 245 | 246 | Map result = new HashMap(); 247 | RequestParametersHolder requestParametersHolder = new RequestParametersHolder(); 248 | 249 | // 应用参数 250 | ACHashMap appParams = new ACHashMap(request.getApplicationParams()); 251 | requestParametersHolder.setApplicationParams(appParams); 252 | 253 | // 协议必选参数 254 | ACHashMap protocolMustParams = new ACHashMap(); 255 | protocolMustParams.put(WXPayConstants.MCH_PAY_APPID, this.appId); 256 | protocolMustParams.put(WXPayConstants.MCH_PAY_ID, this.mchId); 257 | protocolMustParams.put(WXPayConstants.NONCE_STR, SDKUtils.genRandomStringByLength(32)); 258 | requestParametersHolder.setProtocalMustParams(protocolMustParams); 259 | 260 | // 协议可选参数 261 | ACHashMap protocolOptParams = new ACHashMap(); 262 | requestParametersHolder.setProtocalOptParams(protocolOptParams); 263 | 264 | // 签名 265 | if (WXPayConstants.SIGN_TYPE_MD5.equals(this.signType)) { 266 | String signContent = WXPaySignUtils.getSignatureContent(requestParametersHolder); 267 | protocolMustParams.put(WXPayConstants.SIGN, WXPaySignUtils.md5Sign(signContent, key, charset)); 268 | } 269 | 270 | // 将应用参数 协议必选参数 协议可选参数合并, 转换为xml string 271 | ACHashMap requestParamsMap = new ACHashMap(); 272 | requestParamsMap.putAll(appParams); 273 | requestParamsMap.putAll(protocolMustParams); 274 | requestParamsMap.putAll(protocolOptParams); 275 | 276 | String requestBoyStr = MapUtils.map2XmlString(requestParamsMap); 277 | 278 | String response = null; 279 | try { 280 | response = HttpUtils.executeAttachCA(request.getApiURL(), requestBoyStr, this.certPwd, this.certPath); 281 | } catch (Exception e) { 282 | throw new WXPayApiException(e); 283 | } 284 | 285 | String resp = response; 286 | result.put("resp", resp); 287 | 288 | result.put("params", requestParamsMap); 289 | result.put("protocolMustParams", protocolMustParams); 290 | result.put("protocolOptParams", protocolOptParams); 291 | result.put("url", request.getApiURL()); 292 | 293 | return result; 294 | } 295 | 296 | /** 297 | * HTTPS POST 请求,带证书; 目前用于客户退款 298 | * 299 | * @param request 300 | * @param 301 | * @return 302 | */ 303 | private Map doHTTPSPostRefund(WXPayRequest request) throws WXPayApiException { 304 | 305 | Map result = new HashMap(); 306 | RequestParametersHolder requestParametersHolder = new RequestParametersHolder(); 307 | 308 | // 应用参数 309 | ACHashMap appParams = new ACHashMap(request.getApplicationParams()); 310 | requestParametersHolder.setApplicationParams(appParams); 311 | 312 | // 协议必选参数 313 | ACHashMap protocolMustParams = new ACHashMap(); 314 | protocolMustParams.put(WXPayConstants.APP_ID, this.appId); 315 | protocolMustParams.put(WXPayConstants.MCH_ID, this.mchId); 316 | protocolMustParams.put(WXPayConstants.NONCE_STR, SDKUtils.genRandomStringByLength(32)); 317 | requestParametersHolder.setProtocalMustParams(protocolMustParams); 318 | 319 | // 协议可选参数 320 | ACHashMap protocolOptParams = new ACHashMap(); 321 | requestParametersHolder.setProtocalOptParams(protocolOptParams); 322 | 323 | // 签名 324 | if (WXPayConstants.SIGN_TYPE_MD5.equals(this.signType)) { 325 | String signContent = WXPaySignUtils.getSignatureContent(requestParametersHolder); 326 | protocolMustParams.put(WXPayConstants.SIGN, WXPaySignUtils.md5Sign(signContent, key, charset)); 327 | } 328 | 329 | // 将应用参数 协议必选参数 协议可选参数合并, 转换为xml string 330 | ACHashMap requestParamsMap = new ACHashMap(); 331 | requestParamsMap.putAll(appParams); 332 | requestParamsMap.putAll(protocolMustParams); 333 | requestParamsMap.putAll(protocolOptParams); 334 | 335 | String requestBoyStr = MapUtils.map2XmlString(requestParamsMap); 336 | 337 | String response = null; 338 | try { 339 | response = HttpUtils.executeAttachCA(request.getApiURL(), requestBoyStr, this.certPwd, this.certPath); 340 | } catch (Exception e) { 341 | throw new WXPayApiException(e); 342 | } 343 | 344 | String resp = response; 345 | result.put("resp", resp); 346 | 347 | result.put("params", requestParamsMap); 348 | result.put("protocolMustParams", protocolMustParams); 349 | result.put("protocolOptParams", protocolOptParams); 350 | result.put("url", request.getApiURL()); 351 | 352 | return result; 353 | } 354 | 355 | public String getAppId() { 356 | return appId; 357 | } 358 | 359 | public String getKey() { 360 | return key; 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/main/java/net/arccode/wechat/pay/api/common/util/Converters.java: -------------------------------------------------------------------------------- 1 | package net.arccode.wechat.pay.api.common.util; 2 | 3 | import net.arccode.wechat.pay.api.common.annotation.ApiListField; 4 | import net.arccode.wechat.pay.api.common.constant.WXPayConstants; 5 | import net.arccode.wechat.pay.api.common.exception.WXPayApiException; 6 | import net.arccode.wechat.pay.api.protocol.base.WXPayResponse; 7 | import net.arccode.wechat.pay.api.common.annotation.ApiField; 8 | import net.arccode.wechat.pay.api.common.mapping.ACFieldMethod; 9 | import net.arccode.wechat.pay.api.common.parser.Reader; 10 | 11 | import java.beans.BeanInfo; 12 | import java.beans.Introspector; 13 | import java.beans.PropertyDescriptor; 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.Method; 16 | import java.lang.reflect.ParameterizedType; 17 | import java.lang.reflect.Type; 18 | import java.text.DateFormat; 19 | import java.text.SimpleDateFormat; 20 | import java.util.*; 21 | 22 | /** 23 | * 转换工具类 24 | * 25 | * @author http://arccode.net 26 | * @since 2015-11-05 27 | */ 28 | public class Converters { 29 | 30 | // 是否对JSON返回的数据类型进行校验,默认不校验。给内部测试JSON返回时用的开关。 31 | //规则:返回的"基本"类型只有String,Long,Boolean,Date,采取严格校验方式,如果类型不匹配,报错 32 | public static boolean isCheckJsonType = false; 33 | 34 | /** 35 | * 父类中的属性 36 | */ 37 | private static final Set baseFields = new HashSet(); 38 | 39 | /** 40 | * 排除的属性 41 | */ 42 | private static final Set excludeFields = new HashSet(); 43 | 44 | /** 45 | * 被子类覆盖的属性 46 | */ 47 | private static final Set overideFields = new HashSet(); 48 | 49 | static { 50 | baseFields.add("returnCode"); 51 | baseFields.add("returnMsg"); 52 | } 53 | 54 | static { 55 | excludeFields.add("body"); 56 | excludeFields.add("params"); 57 | } 58 | 59 | static { 60 | 61 | } 62 | 63 | private Converters() { 64 | } 65 | 66 | /** 67 | * 使用指定 的读取器去转换字符串为对象。 68 | * 69 | * @param 领域泛型 70 | * @param clazz 领域类型 71 | * @param reader 读取器 72 | * @return 领域对象 73 | * @throws WXPayApiException 74 | */ 75 | public static T convert(Class clazz, Reader reader) throws WXPayApiException { 76 | T rsp = null; 77 | 78 | try { 79 | rsp = clazz.newInstance(); 80 | BeanInfo beanInfo = Introspector.getBeanInfo(clazz); 81 | PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); 82 | 83 | boolean isResponseClazz = WXPayResponse.class.isAssignableFrom(clazz); 84 | 85 | for (PropertyDescriptor pd : pds) { 86 | Method writeMethod = pd.getWriteMethod(); 87 | if (writeMethod == null) { // ignore read-only fields 88 | continue; 89 | } 90 | 91 | String itemName = pd.getName(); 92 | String listName = null; 93 | 94 | // 之前errorCode的属性要剔除掉 95 | if (isResponseClazz && excludeFields.contains(itemName)) { 96 | 97 | continue; 98 | } 99 | 100 | List ACFieldMethods = new ArrayList(); 101 | 102 | if (baseFields.contains(itemName) && isResponseClazz) { 103 | 104 | Field field = WXPayResponse.class.getDeclaredField(itemName); 105 | 106 | ACFieldMethod ACFieldMethod = new ACFieldMethod(); 107 | ACFieldMethod.setField(field); 108 | 109 | // writeMethod属于父类,则直接使用 110 | if (writeMethod.getDeclaringClass().getName().contains("WXPayResponse")) { 111 | 112 | ACFieldMethod.setMethod(writeMethod); 113 | } else { 114 | // 否则从父类再取一次 115 | writeMethod = tryGetSetMethod(WXPayResponse.class, field, 116 | writeMethod.getName()); 117 | if (writeMethod == null) { 118 | continue; 119 | } 120 | 121 | ACFieldMethod.setMethod(writeMethod); 122 | } 123 | ACFieldMethods.add(ACFieldMethod); 124 | 125 | // 如果被子类覆盖的,则从尝试从子类中获取 126 | if (overideFields.contains(itemName)) { 127 | 128 | field = tryGetFieldWithoutExp(clazz, itemName); 129 | // 属性存在则需要重新从子类获取访问方法 130 | if (field != null) { 131 | 132 | writeMethod = tryGetSetMethod(clazz, field, writeMethod.getName()); 133 | if (writeMethod == null) { 134 | continue; 135 | } 136 | ACFieldMethod = new ACFieldMethod(); 137 | ACFieldMethod.setField(field); 138 | ACFieldMethod.setMethod(writeMethod); 139 | ACFieldMethods.add(ACFieldMethod); 140 | } 141 | } 142 | 143 | } else { 144 | 145 | Field field = clazz.getDeclaredField(itemName); 146 | 147 | ACFieldMethod ACFieldMethod = new ACFieldMethod(); 148 | ACFieldMethod.setField(field); 149 | ACFieldMethod.setMethod(writeMethod); 150 | ACFieldMethods.add(ACFieldMethod); 151 | } 152 | 153 | // 迭代设置属性 154 | for (ACFieldMethod ACFieldMethod : ACFieldMethods) { 155 | 156 | Field field = ACFieldMethod.getField(); 157 | Method method = ACFieldMethod.getMethod(); 158 | 159 | ApiField jsonField = field.getAnnotation(ApiField.class); 160 | if (jsonField != null) { 161 | itemName = jsonField.value(); 162 | } 163 | ApiListField jsonListField = field.getAnnotation(ApiListField.class); 164 | if (jsonListField != null) { 165 | listName = jsonListField.value(); 166 | } 167 | 168 | if (!reader.hasReturnField(itemName)) { 169 | if (listName == null || !reader.hasReturnField(listName)) { 170 | continue; // ignore non-return field 171 | } 172 | } 173 | 174 | Class typeClass = field.getType(); 175 | // 目前 176 | if (String.class.isAssignableFrom(typeClass)) { 177 | Object value = reader.getPrimitiveObject(itemName); 178 | if (value instanceof String) { 179 | method.invoke(rsp, value.toString()); 180 | } else { 181 | if (isCheckJsonType && value != null) { 182 | throw new WXPayApiException(itemName + " is not a String"); 183 | } 184 | if (value != null) { 185 | method.invoke(rsp, value.toString()); 186 | } else { 187 | method.invoke(rsp, ""); 188 | } 189 | } 190 | } else if (Long.class.isAssignableFrom(typeClass)) { 191 | Object value = reader.getPrimitiveObject(itemName); 192 | if (value instanceof Long) { 193 | method.invoke(rsp, (Long) value); 194 | } else { 195 | if (isCheckJsonType && value != null) { 196 | throw new WXPayApiException(itemName + " is not a Number(Long)"); 197 | } 198 | if (StringUtils.isNumeric(value)) { 199 | method.invoke(rsp, Long.valueOf(value.toString())); 200 | } 201 | } 202 | } else if (Integer.class.isAssignableFrom(typeClass)) { 203 | Object value = reader.getPrimitiveObject(itemName); 204 | if (value instanceof Integer) { 205 | method.invoke(rsp, (Integer) value); 206 | } else { 207 | if (isCheckJsonType && value != null) { 208 | throw new WXPayApiException(itemName + " is not a Number(Integer)"); 209 | } 210 | if (StringUtils.isNumeric(value)) { 211 | method.invoke(rsp, Integer.valueOf(value.toString())); 212 | } 213 | } 214 | } else if (Boolean.class.isAssignableFrom(typeClass)) { 215 | Object value = reader.getPrimitiveObject(itemName); 216 | if (value instanceof Boolean) { 217 | method.invoke(rsp, (Boolean) value); 218 | } else { 219 | if (isCheckJsonType && value != null) { 220 | throw new WXPayApiException(itemName + " is not a Boolean"); 221 | } 222 | if (value != null) { 223 | method.invoke(rsp, Boolean.valueOf(value.toString())); 224 | } 225 | } 226 | } else if (Double.class.isAssignableFrom(typeClass)) { 227 | Object value = reader.getPrimitiveObject(itemName); 228 | if (value instanceof Double) { 229 | method.invoke(rsp, (Double) value); 230 | } else { 231 | if (isCheckJsonType && value != null) { 232 | throw new WXPayApiException(itemName + " is not a Double"); 233 | } 234 | } 235 | } else if (Number.class.isAssignableFrom(typeClass)) { 236 | Object value = reader.getPrimitiveObject(itemName); 237 | if (value instanceof Number) { 238 | method.invoke(rsp, (Number) value); 239 | } else { 240 | if (isCheckJsonType && value != null) { 241 | throw new WXPayApiException(itemName + " is not a Number"); 242 | } 243 | } 244 | } else if (Date.class.isAssignableFrom(typeClass)) { 245 | DateFormat format = new SimpleDateFormat(WXPayConstants.DATE_TIME_FORMAT); 246 | format.setTimeZone(TimeZone.getTimeZone(WXPayConstants.DATE_TIMEZONE)); 247 | Object value = reader.getPrimitiveObject(itemName); 248 | if (value instanceof String) { 249 | method.invoke(rsp, format.parse(value.toString())); 250 | } 251 | } else if (List.class.isAssignableFrom(typeClass)) { 252 | Type fieldType = field.getGenericType(); 253 | if (fieldType instanceof ParameterizedType) { 254 | ParameterizedType paramType = (ParameterizedType) fieldType; 255 | Type[] genericTypes = paramType.getActualTypeArguments(); 256 | if (genericTypes != null && genericTypes.length > 0) { 257 | if (genericTypes[0] instanceof Class) { 258 | Class subType = (Class) genericTypes[0]; 259 | List listObjs = reader.getListObjects(listName, itemName, 260 | subType); 261 | if (listObjs != null) { 262 | method.invoke(rsp, listObjs); 263 | } 264 | } 265 | } 266 | } 267 | } else { 268 | Object obj = reader.getObject(itemName, typeClass); 269 | if (obj != null) { 270 | method.invoke(rsp, obj); 271 | } 272 | } 273 | } 274 | 275 | } 276 | } catch (Exception e) { 277 | throw new WXPayApiException(e); 278 | } 279 | 280 | return rsp; 281 | } 282 | 283 | /** 284 | * 尝试获取属性 285 | *

286 | * 不会抛出异常,不存在则返回null 287 | * 288 | * @param clazz 289 | * @param itemName 290 | * @return 291 | */ 292 | private static Field tryGetFieldWithoutExp(Class clazz, String itemName) { 293 | 294 | try { 295 | 296 | return clazz.getDeclaredField(itemName); 297 | 298 | } catch (Exception e) { 299 | return null; 300 | } 301 | } 302 | 303 | /** 304 | * 获取属性设置属性 305 | * 306 | * @param clazz 307 | * @param field 308 | * @return 309 | */ 310 | private static Method tryGetSetMethod(Class clazz, Field field, String methodName) { 311 | 312 | try { 313 | return clazz.getDeclaredMethod(methodName, field.getType()); 314 | } catch (Exception e) { 315 | 316 | return null; 317 | } 318 | 319 | } 320 | 321 | } 322 | --------------------------------------------------------------------------------