瀏覽代碼

init from phab

xielq 4 年之前
父節點
當前提交
d400ed3e6d

+ 0 - 0
README.md


+ 22 - 0
build.gradle

@@ -0,0 +1,22 @@
+group = 'com.uas.weixin'
+version = '0.0.1-SNAPSHOT'
+
+apply plugin: 'java'
+apply from: "$projectDir/gradle/publish.gradle"
+
+sourceCompatibility = 1.8
+
+repositories {
+    jcenter()
+    mavenCentral()
+}
+
+dependencies {
+    compile('org.apache.commons:commons-lang3:3.7')
+    compile('commons-codec:commons-codec:1.11')
+    compile('com.thoughtworks.xstream:xstream:1.4.10')
+    compile('com.fasterxml.jackson.core:jackson-core:2.9.4')
+    compile('com.fasterxml.jackson.core:jackson-databind:2.9.4')
+    compile('org.apache.httpcomponents:httpclient:4.5.3')
+    compile('org.apache.logging.log4j:log4j-api:2.10.0')
+}

+ 42 - 0
gradle/publish.gradle

@@ -0,0 +1,42 @@
+ext {
+    artifactoryBaseUrl = 'http://113.105.74.141:8081/artifactory'
+    artifactorySnapshotRepoUrl = "$artifactoryBaseUrl/libs-snapshot-local"
+    artifactoryReleaseRepoUrl = "$artifactoryBaseUrl/libs-release-local"
+}
+
+apply plugin: 'maven-publish'
+
+tasks.withType(JavaCompile) {
+    options.encoding = 'UTF-8'
+}
+
+task sourcesJar(type: Jar) {
+    baseName "${project.name}"
+    classifier 'sources'
+    from sourceSets.main.allSource
+}
+
+publishing {
+    publications {
+        plugin(MavenPublication) {
+            groupId "${project.group}"
+            artifactId "${project.name}"
+            version "${project.version}"
+
+            from components.java
+            artifact sourcesJar
+        }
+    }
+
+    repositories {
+        maven {
+            name 'remoteArtifactory'
+            url project.version.endsWith('-SNAPSHOT') ? artifactorySnapshotRepoUrl : artifactoryReleaseRepoUrl
+
+            credentials {
+                username = 'yingp'
+                password = '111111'
+            }
+        }
+    }
+}

+ 215 - 0
src/main/java/com/uas/weixin/pay/WeixinPay.java

@@ -0,0 +1,215 @@
+package com.uas.weixin.pay;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
+import com.uas.weixin.pay.model.OrderReturnInfo;
+import com.uas.weixin.pay.support.HttpRequest;
+import com.uas.weixin.pay.support.HttpRequest.MapEntryConverter;
+import com.uas.weixin.pay.support.JacksonUtils;
+import com.uas.weixin.pay.support.RandomStringGenerator;
+import com.uas.weixin.pay.support.Signature;
+import java.io.IOException;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.commons.lang3.Validate;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * 微信支付接口抽象层,默认使用MD5签名
+ *
+ * @author huxz
+ */
+public class WeixinPay {
+
+  private static final Logger logger = LogManager.getLogger();
+
+  private static final String SIGN_TYPE = "MD5";
+
+  /**
+   * 微信分配的小程序ID
+   */
+  private final String appId;
+
+  /**
+   * 小程序的 app secret
+   */
+  private final String secret;
+
+  /**
+   * 微信支付分配的商户号
+   */
+  private final String mchId;
+
+  /**
+   * 微信支付用户秘钥
+   */
+  private final String key;
+
+  /**
+   * 异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url
+   */
+  private String notifyUrl;
+
+  /**
+   * 交易类型	,小程序取值如下:JSAPI
+   */
+  private String tradeType;
+
+  public WeixinPay(String appId, String secret, String mchId, String key) {
+    this.appId = appId;
+    this.secret = secret;
+    this.mchId = mchId;
+    this.key = key;
+  }
+
+  public void setNotifyUrl(String notifyUrl) {
+    this.notifyUrl = notifyUrl;
+  }
+
+  public void setTradeType(String tradeType) {
+    this.tradeType = tradeType;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  /**
+   * 小程序获取用户openId
+   *
+   * @param code 用户登录code
+   * @return openId
+   */
+  public String getOpenId(String code) throws IOException {
+    String requestUrl = String.format("https://api.weixin.qq.com/sns/jscode2session?" +
+        "appid=%s&secret=%s&js_code=%s&grant_type=authorization_code", this.appId, this.secret, code);
+    HttpGet httpGet = new HttpGet(requestUrl);
+
+    HttpClient httpClient = HttpClients.createDefault();
+    HttpResponse res = httpClient.execute(httpGet);
+    HttpEntity entity = res.getEntity();
+    String result = EntityUtils.toString(entity, "UTF-8");
+
+    Map map = JacksonUtils.fromJson(result, Map.class);
+    Validate.notNull(map, "获取用户唯一标识响应数据失败");
+
+    Validate.isTrue(map.get("errcode") == null, (String) map.get("errmsg"));
+    return (String) map.get("openid");
+  }
+
+  /**
+   * 获取用户预支付id
+   *
+   * @param openId   用户位置标识,用于小程序
+   * @param orderNo  订单编号
+   * @param body     商品简单描述
+   * @param totalFee 订单总金额,单位为分
+   * @param ip       APP和网页支付提交用户端ip
+   * @return 预支付交易会话标识
+   */
+  public String getPrepareId(String openId, String orderNo, String body, Integer totalFee, String ip) {
+    Validate.notBlank(openId, "用户唯一标识不能为空");
+    Validate.notBlank(orderNo, "用户订单编号不能为空");
+    Validate.notBlank(body, "商品简单描述不能为空");
+    Validate.isTrue(totalFee != null && totalFee >= 0, "订单总金额不能为空或负数");
+    Validate.notBlank(ip, "终端IP不能为空");
+
+    TreeMap<String, Object> params = new TreeMap<>();
+    params.put("appid", this.appId);
+    params.put("mch_id", this.mchId);
+    params.put("nonce_str", RandomStringGenerator.getRandomStringByLength(32));
+    params.put("sign_type", SIGN_TYPE);
+    params.put("body", body);
+    params.put("out_trade_no", orderNo);
+    params.put("total_fee", totalFee);
+    params.put("spbill_create_ip", ip);
+    params.put("notify_url", this.notifyUrl);
+    params.put("trade_type", this.tradeType);
+    params.put("openid", openId);
+
+    params.put("sign", Signature.sign(params, this.key));
+
+    try {
+      String result = HttpRequest.sendPost("https://api.mch.weixin.qq.com/pay/unifiedorder", params);
+      logger.debug("统一下单接口响应结果: {}", result);
+
+      XStream xStream = new XStream();
+      XStream.setupDefaultSecurity(xStream);
+      xStream.allowTypes(new Class[]{OrderReturnInfo.class});
+      xStream.alias("xml", OrderReturnInfo.class);
+
+      OrderReturnInfo returnInfo = (OrderReturnInfo) xStream.fromXML(result);
+
+      Validate.notNull(returnInfo, "响应结果解析失败");
+      Validate.isTrue("SUCCESS".equals(returnInfo.getReturn_code()), returnInfo.getReturn_msg());
+      Validate.isTrue("SUCCESS".equals(returnInfo.getResult_code()), returnInfo.getErr_code_des());
+
+      logger.debug("用户预支付ID: {}", returnInfo.getPrepay_id());
+      return returnInfo.getPrepay_id();
+    } catch (IOException e) {
+      e.printStackTrace();
+      return null;
+    }
+  }
+
+  /**
+   * 小程序调起支付数据签名
+   *
+   * @param prepayId 预支付ID
+   * @return 调起支付所需参数
+   */
+  public Map<String, Object> resign(String prepayId) {
+    Validate.notBlank(prepayId, "用户预支付Id不能为空");
+
+    TreeMap<String, Object> params = new TreeMap<>();
+    params.put("appId", this.appId);
+    long time = System.currentTimeMillis() / 1000;
+    params.put("timeStamp", time);
+    params.put("nonceStr", RandomStringGenerator.getRandomStringByLength(32));
+    params.put("package", "prepay_id=" + prepayId);
+    params.put("signType", SIGN_TYPE);
+
+    params.put("paySign", Signature.sign(params, this.key));
+
+    // 删除参数appId
+    params.remove("appId");
+    return params;
+  }
+
+  /**
+   * 将Map数据转换成XML字符串
+   *
+   * @param params  转换参数
+   * @return  xml字符串
+   */
+  public static String translateXML(Map<String, Object> params) {
+    XStream stream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
+    XStream.setupDefaultSecurity(stream);
+    stream.allowTypes(new Class[]{Map.class});
+
+    stream.alias("xml", Map.class);
+    stream.registerConverter(new MapEntryConverter());
+
+    return stream.toXML(params);
+  }
+
+  @SuppressWarnings("unchecked")
+  public static Map<String, Object> translateMap(String xml) {
+    XStream xStream = new XStream();
+    XStream.setupDefaultSecurity(xStream);
+    xStream.allowTypes(new Class[]{Map.class});
+
+    xStream.alias("xml", Map.class);
+    xStream.registerConverter(new MapEntryConverter());
+
+    return (Map<String, Object>) xStream.fromXML(xml);
+  }
+}

+ 103 - 0
src/main/java/com/uas/weixin/pay/model/OrderReturnInfo.java

@@ -0,0 +1,103 @@
+package com.uas.weixin.pay.model;
+
+public class OrderReturnInfo {
+  private String return_code;
+  private String return_msg;
+  private String result_code;
+  private String appid;
+  private String mch_id;
+  private String nonce_str;
+  private String sign;
+  private String prepay_id;
+  private String trade_type;
+  private String err_code;
+  private String err_code_des;
+
+  public String getReturn_code() {
+    return return_code;
+  }
+
+  public void setReturn_code(String return_code) {
+    this.return_code = return_code;
+  }
+
+  public String getReturn_msg() {
+    return return_msg;
+  }
+
+  public void setReturn_msg(String return_msg) {
+    this.return_msg = return_msg;
+  }
+
+  public String getResult_code() {
+    return result_code;
+  }
+
+  public void setResult_code(String result_code) {
+    this.result_code = result_code;
+  }
+
+  public String getAppid() {
+    return appid;
+  }
+
+  public void setAppid(String appid) {
+    this.appid = appid;
+  }
+
+  public String getMch_id() {
+    return mch_id;
+  }
+
+  public void setMch_id(String mch_id) {
+    this.mch_id = mch_id;
+  }
+
+  public String getNonce_str() {
+    return nonce_str;
+  }
+
+  public void setNonce_str(String nonce_str) {
+    this.nonce_str = nonce_str;
+  }
+
+  public String getSign() {
+    return sign;
+  }
+
+  public void setSign(String sign) {
+    this.sign = sign;
+  }
+
+  public String getPrepay_id() {
+    return prepay_id;
+  }
+
+  public void setPrepay_id(String prepay_id) {
+    this.prepay_id = prepay_id;
+  }
+
+  public String getTrade_type() {
+    return trade_type;
+  }
+
+  public void setTrade_type(String trade_type) {
+    this.trade_type = trade_type;
+  }
+
+  public String getErr_code() {
+    return err_code;
+  }
+
+  public void setErr_code(String err_code) {
+    this.err_code = err_code;
+  }
+
+  public String getErr_code_des() {
+    return err_code_des;
+  }
+
+  public void setErr_code_des(String err_code_des) {
+    this.err_code_des = err_code_des;
+  }
+}

+ 138 - 0
src/main/java/com/uas/weixin/pay/support/HttpRequest.java

@@ -0,0 +1,138 @@
+package com.uas.weixin.pay.support;
+
+import com.thoughtworks.xstream.XStream;
+import com.thoughtworks.xstream.converters.Converter;
+import com.thoughtworks.xstream.converters.MarshallingContext;
+import com.thoughtworks.xstream.converters.UnmarshallingContext;
+import com.thoughtworks.xstream.io.HierarchicalStreamReader;
+import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
+import com.thoughtworks.xstream.io.xml.DomDriver;
+import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
+import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import javax.net.ssl.X509TrustManager;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Http 请求
+ *
+ * @author huxz
+ */
+public class HttpRequest {
+
+  private static final Logger logger = LogManager.getLogger();
+
+  /**
+   * 连接超时时间,默认10秒
+   */
+  private static final int SOCKET_TIMEOUT = 10000;
+
+  /**
+   * 传输超时时间,默认30秒
+   */
+  private static final int CONNECT_TIMEOUT = 30000;
+
+  /**
+   * post请求
+   *
+   * @throws IOException
+   * @throws ClientProtocolException
+   */
+  public static String sendPost(String url, Object xmlObj) throws IOException, ClientProtocolException {
+
+    HttpPost httpPost = new HttpPost(url);
+
+    //解决XStream对出现双下划线的bug
+    XStream stream = new XStream(new DomDriver("UTF-8", new XmlFriendlyNameCoder("-_", "_")));
+    XStream.setupDefaultSecurity(stream);
+    stream.allowTypes(new Class[]{Map.class});
+
+    stream.alias("xml", Map.class);
+    stream.registerConverter(new MapEntryConverter());
+
+    //将要提交给API的数据对象转换成XML格式数据Post给API
+    String postDataXML = stream.toXML(xmlObj);
+
+    logger.debug("post xml data: {}", postDataXML);
+
+    //得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
+    StringEntity postEntity = new StringEntity(postDataXML, "UTF-8");
+    httpPost.addHeader("Content-Type", "text/xml");
+    httpPost.setEntity(postEntity);
+
+    //设置请求器的配置
+    RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SOCKET_TIMEOUT).setConnectTimeout(CONNECT_TIMEOUT).build();
+    httpPost.setConfig(requestConfig);
+
+    HttpClient httpClient = HttpClients.createDefault();
+    HttpResponse response = httpClient.execute(httpPost);
+    HttpEntity entity = response.getEntity();
+    return EntityUtils.toString(entity, "UTF-8");
+  }
+
+  /**
+   * 自定义证书管理器,信任所有证书
+   *
+   * @author pc
+   */
+  public static class MyX509TrustManager implements X509TrustManager {
+
+    @Override
+    public void checkClientTrusted(
+        java.security.cert.X509Certificate[] arg0, String arg1) {
+    }
+
+    @Override
+    public void checkServerTrusted(
+        java.security.cert.X509Certificate[] arg0, String arg1) {
+    }
+
+    @Override
+    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+      return null;
+    }
+  }
+
+  public static class MapEntryConverter implements Converter {
+
+    @Override
+    public boolean canConvert(Class clazz) {
+      return AbstractMap.class.isAssignableFrom(clazz);
+    }
+
+    @Override
+    public void marshal(Object value, HierarchicalStreamWriter writer, MarshallingContext context) {
+      AbstractMap map = (AbstractMap) value;
+      for (Object obj : map.entrySet()) {
+        Entry entry = (Entry) obj;
+        writer.startNode(entry.getKey().toString());
+        writer.setValue(entry.getValue().toString());
+        writer.endNode();
+      }
+    }
+
+    @Override
+    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
+      Map<String, String> map = new HashMap<>(16);
+      while(reader.hasMoreChildren()) {
+        reader.moveDown();
+        map.put(reader.getNodeName(), reader.getValue());
+        reader.moveUp();
+      }
+      return map;
+    }
+  }
+}

+ 70 - 0
src/main/java/com/uas/weixin/pay/support/JacksonUtils.java

@@ -0,0 +1,70 @@
+package com.uas.weixin.pay.support;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * JSON 工具类
+ *
+ * @author huxz
+ */
+public final class JacksonUtils {
+
+  private static final Logger logger = LogManager.getLogger();
+
+  private static ObjectMapper objectMapper;
+
+  /**
+   * 把JSON文本parse为JavaBean
+   */
+  public static <T> T fromJson(String text, Class<T> clazz) {
+    if (objectMapper == null) {
+      objectMapper = new ObjectMapper();
+    }
+    try {
+      return objectMapper.readValue(text, clazz);
+    } catch (IOException e) {
+      logger.error(e);
+    }
+    return null;
+  }
+
+  /**
+   * 把JSON文本parse成JavaBean集合
+   */
+  public static <T> List<T> fromJsonArray(String text, Class<T> clazz) {
+    if (objectMapper == null) {
+      objectMapper = new ObjectMapper();
+    }
+    JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, clazz);
+    try {
+      return objectMapper.readValue(text, javaType);
+    } catch (IOException e) {
+      logger.error(e);
+    }
+    return Collections.emptyList();
+  }
+
+  /**
+   * 将JavaBean序列化为JSON文本
+   */
+  public static String toJson(Object object) {
+    if (objectMapper == null) {
+      objectMapper = new ObjectMapper();
+    }
+    try {
+      return objectMapper.writeValueAsString(object);
+    } catch (JsonProcessingException e) {
+      logger.error(e);
+    }
+    return null;
+  }
+
+}

+ 27 - 0
src/main/java/com/uas/weixin/pay/support/RandomStringGenerator.java

@@ -0,0 +1,27 @@
+package com.uas.weixin.pay.support;
+
+import java.util.Random;
+
+/**
+ * 随机字符串生成
+ *
+ * @author zuoliangzhu
+ */
+public class RandomStringGenerator {
+  /**
+   * 获取一定长度的随机字符串
+   *
+   * @param length 指定字符串长度
+   * @return 一定长度的字符串
+   */
+  public static String getRandomStringByLength(int length) {
+    String base = "abcdefghijklmnopqrstuvwxyz0123456789";
+    Random random = new Random();
+    StringBuffer sb = new StringBuffer();
+    for (int i = 0; i < length; i++) {
+      int number = random.nextInt(base.length());
+      sb.append(base.charAt(number));
+    }
+    return sb.toString();
+  }
+}

+ 45 - 0
src/main/java/com/uas/weixin/pay/support/Signature.java

@@ -0,0 +1,45 @@
+package com.uas.weixin.pay.support;
+
+import java.util.Map.Entry;
+import java.util.TreeMap;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+
+/**
+ * 签名
+ *
+ * @author huxz
+ */
+public class Signature {
+
+  private static final Logger logger = LogManager.getLogger();
+
+  /**
+   * 微信支付签名算法(MD5)
+   *
+   * @param params 签名参数
+   * @param key    签名秘钥
+   * @return 签名字符串
+   */
+  public static String sign(TreeMap<String, Object> params, String key) {
+    Validate.notEmpty(params, "签名数据集不能为空");
+    Validate.notBlank(key, "商户平台密钥key不能为空");
+
+    StringBuilder builder = new StringBuilder();
+    for (Entry<String, Object> entry : params.entrySet()) {
+      if (StringUtils.isNotBlank(entry.getKey()) && entry.getValue() != null) {
+        builder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
+      }
+    }
+    logger.debug("参数键值对字符串: {}", builder.toString());
+
+    builder.append("key=").append(key);
+
+    return DigestUtils.md5Hex(builder.toString());
+  }
+
+}