赞
踩
地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3
springboot项目pom.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.majker</groupId> <artifactId>wxPayDemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>wxPayDemo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <commons-lang3.version>3.5</commons-lang3.version> <!-- tools version setting --> <commons-io.version>2.4</commons-io.version> <!-- lombok插件 --> <lombok.version>1.16.18</lombok.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient --> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.3</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>io.swagger</groupId> <artifactId>swagger-annotations</artifactId> <version>1.5.9</version> <scope>compile</scope> </dependency> <!-- fastjson json--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.1.40</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons-lang3.version}</version> </dependency> <!--log--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> <!-- GENERAL UTILS begin --> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>${commons-io.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
package com.majker.common.config; /**************************************************** * * 微信小程序 配置 * * * @author majker * @date 2019-02-25 10:17 * @version 1.0 **************************************************/ public class WxProgramPayConfig { /** * AppID(小程序ID) */ public static final String APPID = ""; /** * AppSecret(小程序密钥) */ public static final String SECRET=""; public static String MCH_ID = ""; /** * 回调地址 */ public static String NOTIFY_URL = ""; public static String KEY = ""; }
参数详情请看下图,(来源:小程序账号申请微信支付过程中获取的)
package com.majker.common.util; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import java.io.Serializable; import java.security.SecureRandom; import java.util.UUID; /**************************************************** * * 封装各种生成唯一性ID算法的工具类. * * @author majker * @date: 2016-01-15 * @version 1.0 **************************************************/ @Service @Lazy(false) public class IdGen implements Serializable{ private static SecureRandom random = new SecureRandom(); /** * 封装JDK自带的UUID, 通过Random数字生成, 中间无-分割. */ public static String uuid() { return UUID.randomUUID().toString().replaceAll("-", ""); } /** * 使用SecureRandom随机生成Long. */ public static long randomLong() { return Math.abs(random.nextLong()); } }
package com.majker.common.util; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.annotation.JsonIgnore; import java.io.Serializable; import java.lang.reflect.Field; import java.util.*; /**************************************************** * * 响应结果 * * @author majker * @date: 2018/2/10 * @version 1.0 **************************************************/ public class Render<T> implements Serializable { private String msg; private T data; private int code; private Boolean error; private Long timestamp; private static Map map; public String getMsg() { return msg; } public int getCode() { return code; } public T getData() { return data; } public Boolean getError() { return error; } public Long getTimestamp() { return timestamp; } public static <T> Render<T> fail(String message) { Render<T> msg = new Render<>(); msg.msg = message; msg.code(ResultCode.EXCEPTION.val()); msg.error(true); return msg.putTimeStamp(); } public static <T> Render<T> fail(ResultCode resultCode) { Render<T> msg = new Render<>(); msg.msg = resultCode.msg(); msg.code(resultCode.val()); msg.error(true); return msg.putTimeStamp(); } public static <T> Render<T> ok() { return ok(null); } private Render<T> putTimeStamp() { this.timestamp = System.currentTimeMillis(); return this; } public static <T> Render<T> ok(T data) { return new Render<T>() .data(data) .putTimeStamp() .error(false) .msg(ResultCode.SUCCESS.msg()) .code(ResultCode.SUCCESS.val()); } public static <T> Render<T> ok(T data, String msg) { return new Render<T>() .data(data) .putTimeStamp() .error(false) .msg(msg) .code(ResultCode.SUCCESS.val()); } public static Map okMap(Object data) { map = new HashMap(); map.put("data", data); map.put("error", false); map.put("code", ResultCode.SUCCESS.val()); map.put("msg", ResultCode.SUCCESS.msg()); map.put("timestamp", System.currentTimeMillis()); return map; } public static Map failMap(String msg) { map = new HashMap(); map.put("data", null); map.put("error", true); map.put("code", ResultCode.EXCEPTION.val()); map.put("msg", msg); map.put("timestamp", System.currentTimeMillis()); return map; } public static Map failMap(ResultCode resultCode) { map = new HashMap(); map.put("data", null); map.put("error", true); map.put("code", resultCode.val()); map.put("msg", resultCode.msg()); map.put("timestamp", System.currentTimeMillis()); return map; } public Render<T> data(T data) { this.data = data; return this; } public Render<T> code(int code) { this.code = code; return this; } public Render<T> error(Boolean error) { this.error = error; return this; } public Render<T> msg(String msg) { this.msg = msg; return this; } /** * 过滤字段:指定需要序列化的字段 */ @JsonIgnore private transient Map<Class<?>, Set<String>> includes; /** * 过滤字段:指定不需要序列化的字段 */ @JsonIgnore private transient Map<Class<?>, Set<String>> excludes; public Render() { } public Render<T> include(Class<?> type, String... fields) { return include(type, Arrays.asList(fields)); } public Render<T> include(Class<?> type, Collection<String> fields) { if (includes == null) includes = new HashMap<>(); if (fields == null || fields.isEmpty()) return this; fields.forEach(field -> { if (field.contains(".")) { String tmp[] = field.split("[.]", 2); try { Field field1 = type.getDeclaredField(tmp[0]); if (field1 != null) { include(field1.getType(), tmp[1]); } } catch (Throwable e) { } } else { getStringListFromMap(includes, type).add(field); } }); return this; } public Render<T> exclude(Class type, Collection<String> fields) { if (excludes == null) excludes = new HashMap<>(); if (fields == null || fields.isEmpty()) return this; fields.forEach(field -> { if (field.contains(".")) { String tmp[] = field.split("[.]", 2); try { Field field1 = type.getDeclaredField(tmp[0]); if (field1 != null) { exclude(field1.getType(), tmp[1]); } } catch (Throwable e) { } } else { getStringListFromMap(excludes, type).add(field); } }); return this; } public Render<T> exclude(Collection<String> fields) { if (excludes == null) excludes = new HashMap<>(); if (fields == null || fields.isEmpty()) return this; Class type; if (getData() != null) type = getData().getClass(); else return this; exclude(type, fields); return this; } public Render<T> include(Collection<String> fields) { if (includes == null) includes = new HashMap<>(); if (fields == null || fields.isEmpty()) return this; Class type; if (getData() != null) type = getData().getClass(); else return this; include(type, fields); return this; } public Render<T> exclude(Class type, String... fields) { return exclude(type, Arrays.asList(fields)); } public Render<T> exclude(String... fields) { return exclude(Arrays.asList(fields)); } public Render<T> include(String... fields) { return include(Arrays.asList(fields)); } protected Set<String> getStringListFromMap(Map<Class<?>, Set<String>> map, Class type) { return map.computeIfAbsent(type, k -> new HashSet<>()); } @Override public String toString() { return JSON.toJSONStringWithDateFormat(this, "yyyy-MM-dd HH:mm:ss"); } public Map<Class<?>, Set<String>> getExcludes() { return excludes; } public Map<Class<?>, Set<String>> getIncludes() { return includes; } }
package com.majker.common.util; import java.io.Serializable; /**************************************************** * * 响应码 * * @author majker * @date: 2019/3/10 * @version 1.0 **************************************************/ public enum ResultCode implements Serializable{ /** 成功 */ SUCCESS(200, "成功"), /** 发生异常 */ EXCEPTION(500, "发生异常"), NO_AUTH(401,"请重新登录"), FORBIDDEN(403,"未认证"), NULL(403,"禁止"), NOT_BIND(1234,"请先绑定手机号"), NOT_FOUND(404,"未找到相应文件"); ResultCode(int value, String msg){ this.val = value; this.msg = msg; } public int val() { return val; } public String msg() { return msg; } private int val; private String msg; }
使用的官方的sdk和自己编写的代码,相关类如下,下载下方给的demo
package com.majker.common.sdk; import javax.net.ssl.*; import javax.servlet.http.HttpServletRequest; import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.security.*; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Iterator; import java.util.Map; public class HttpKit { private static final String GET = "GET"; private static final String POST = "POST"; private static String CHARSET = "UTF-8"; private static final SSLSocketFactory sslSocketFactory = initSSLSocketFactory(); private static final TrustAnyHostnameVerifier trustAnyHostnameVerifier = new HttpKit().new TrustAnyHostnameVerifier(); // public static final OkHttp3Delegate delegate = new OkHttp3Delegate(); private HttpKit() { } private static SSLSocketFactory initSSLSocketFactory() { try { TrustManager[] e = new TrustManager[]{new HttpKit().new TrustAnyTrustManager()}; SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init((KeyManager[])null, e, new SecureRandom()); return sslContext.getSocketFactory(); } catch (Exception var2) { throw new RuntimeException(var2); } } public static void setCharSet(String charSet) { if(charSet!=null && !charSet.equals("")) { throw new IllegalArgumentException("charSet can not be blank."); } else { CHARSET = charSet; } } private static HttpURLConnection getHttpConnection(String url, String method, Map<String, String> headers) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException { URL _url = new URL(url); HttpURLConnection conn = (HttpURLConnection)_url.openConnection(); if(conn instanceof HttpsURLConnection) { ((HttpsURLConnection)conn).setSSLSocketFactory(sslSocketFactory); ((HttpsURLConnection)conn).setHostnameVerifier(trustAnyHostnameVerifier); } conn.setRequestMethod(method); conn.setDoOutput(true); conn.setDoInput(true); conn.setConnectTimeout(19000); conn.setReadTimeout(19000); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("AuthUser-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.146 Safari/537.36"); if(headers != null && !headers.isEmpty()) { Iterator i$ = headers.entrySet().iterator(); while(i$.hasNext()) { Map.Entry entry = (Map.Entry)i$.next(); conn.setRequestProperty((String)entry.getKey(), (String)entry.getValue()); } } return conn; } public static String get(String url, Map<String, String> queryParas, Map<String, String> headers) { HttpURLConnection conn = null; String e; try { conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), "GET", headers); conn.connect(); e = readResponseString(conn); } catch (Exception var8) { throw new RuntimeException(var8); } finally { if(conn != null) { conn.disconnect(); } } return e; } public static String get(String url, Map<String, String> queryParas) { return get(url, queryParas, (Map)null); } public static String get(String url) { return get(url, (Map)null, (Map)null); } public static String post(String url, Map<String, String> queryParas, String data, Map<String, String> headers) { HttpURLConnection conn = null; String var6; try { conn = getHttpConnection(buildUrlWithQueryString(url, queryParas), "POST", headers); conn.connect(); OutputStream e = conn.getOutputStream(); e.write(data.getBytes(CHARSET)); e.flush(); e.close(); var6 = readResponseString(conn); } catch (Exception var10) { throw new RuntimeException(var10); } finally { if(conn != null) { conn.disconnect(); } } return var6; } public static String post(String url, Map<String, String> queryParas, String data) { return post(url, queryParas, data, (Map)null); } public static String post(String url, String data, Map<String, String> headers) { return post(url, (Map)null, data, headers); } public static String post(String url, String data) { return post(url, (Map)null, data, (Map)null); } private static String readResponseString(HttpURLConnection conn) { StringBuilder sb = new StringBuilder(); InputStream inputStream = null; try { inputStream = conn.getInputStream(); BufferedReader e = new BufferedReader(new InputStreamReader(inputStream, CHARSET)); String line = null; while((line = e.readLine()) != null) { sb.append(line).append("\n"); } String var5 = sb.toString(); return var5; } catch (Exception var14) { throw new RuntimeException(var14); } finally { if(inputStream != null) { try { inputStream.close(); } catch (IOException var13) { } } } } private static String buildUrlWithQueryString(String url, Map<String, String> queryParas) { if(queryParas != null && !queryParas.isEmpty()) { StringBuilder sb = new StringBuilder(url); boolean isFirst; if(url.indexOf("?") == -1) { isFirst = true; sb.append("?"); } else { isFirst = false; } String key; String value; for(Iterator i$ = queryParas.entrySet().iterator(); i$.hasNext(); sb.append(key).append("=").append(value)) { Map.Entry entry = (Map.Entry)i$.next(); if(isFirst) { isFirst = false; } else { sb.append("&"); } key = (String)entry.getKey(); value = (String)entry.getValue(); if(value!=null && !value.equals("")) { try { value = URLEncoder.encode(value, CHARSET); } catch (UnsupportedEncodingException var9) { throw new RuntimeException(var9); } } } return sb.toString(); } else { return url; } } public static String readData(HttpServletRequest request) { BufferedReader br = null; try { StringBuilder e = new StringBuilder(); br = request.getReader(); String line = null; while((line = br.readLine()) != null) { e.append(line).append("\n"); } line = e.toString(); return line; } catch (IOException var12) { throw new RuntimeException(var12); } finally { if(br != null) { try { br.close(); } catch (IOException var11) { } } } } /** @deprecated */ @Deprecated public static String readIncommingRequestData(HttpServletRequest request) { return readData(request); } private class TrustAnyTrustManager implements X509TrustManager { private TrustAnyTrustManager() { } public X509Certificate[] getAcceptedIssuers() { return null; } public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } } private class TrustAnyHostnameVerifier implements HostnameVerifier { private TrustAnyHostnameVerifier() { } public boolean verify(String hostname, SSLSession session) { return true; } } }
package com.majker.common.sdk; import org.apache.commons.io.Charsets; import java.io.*; import java.nio.charset.Charset; /** * IOUtils * @author majker */ public abstract class IOUtils { private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; /** * closeQuietly * @param closeable 自动关闭 */ public static void closeQuietly(Closeable closeable) { try { if (closeable != null) { closeable.close(); } } catch (IOException ioe) { // ignore } } /** * InputStream to String utf-8 * * @param input the <code>InputStream</code> to read from * @return the requested String * @throws NullPointerException if the input is null * @throws IOException if an I/O error occurs */ public static String toString(InputStream input) throws IOException { return toString(input, Charsets.UTF_8); } /** * InputStream to String * * @param input the <code>InputStream</code> to read from * @param charset the <code>Charset</code> * @return the requested String * @throws NullPointerException if the input is null * @throws IOException if an I/O error occurs */ public static String toString(InputStream input, Charset charset) throws IOException { InputStreamReader in = new InputStreamReader(input, charset); StringBuffer out = new StringBuffer(); char[] c = new char[DEFAULT_BUFFER_SIZE]; for (int n; (n = in.read(c)) != -1;) { out.append(new String(c, 0, n)); } IOUtils.closeQuietly(in); IOUtils.closeQuietly(input); return out.toString(); } /** * InputStream to File * @param input the <code>InputStream</code> to read from * @param file the File to write * @throws IOException id异常 */ public static void toFile(InputStream input, File file) throws IOException { OutputStream os = new FileOutputStream(file); int bytesRead = 0; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; while ((bytesRead = input.read(buffer, 0, DEFAULT_BUFFER_SIZE)) != -1) { os.write(buffer, 0, bytesRead); } IOUtils.closeQuietly(os); IOUtils.closeQuietly(input); } }
package com.majker.common.sdk; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; /**************************************************** * * 微信支付api * * @author majker * @version 1.0 **************************************************/ public class PaymentApi { private PaymentApi() {} // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1 private static String unifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** * 交易类型枚举 * WAP的文档:https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_1 * @author L.cm * <pre> * email: 596392912@qq.com * site: http://www.dreamlu.net * date: 2015年10月27日 下午9:46:27 * </pre> */ public enum TradeType { JSAPI, NATIVE, APP, WAP, MWEB } /** * 统一下单 * @param params 参数map * @return String */ public static String pushOrder(Map<String, String> params) { return HttpKit.post(unifiedOrderUrl, PaymentKit.toXml(params)); } private static Map<String, String> request(String url, Map<String, String> params, String paternerKey) { params.put("nonce_str", System.currentTimeMillis() + ""); String sign = PaymentKit.createSign(params, paternerKey); params.put("sign", sign); String xmlStr = HttpKit.post(url, PaymentKit.toXml(params)); return PaymentKit.xmlToMap(xmlStr); } /** * 文档说明:https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_4 * <pre> * @param appId 公众账号ID 是 String(32) wx8888888888888888 微信分配的公众账号ID * 随机字符串 noncestr 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法 * 订单详情扩展字符串 package 是 String(32) WAP 扩展字段,固定填写WAP * @param prepayId 预支付交易会话标识 是 String(64) wx201410272009395522657a690389285100 微信统一下单接口返回的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时 * 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法 * 时间戳 timestamp 是 String(32) 1414561699 当前的时间,其他详见时间戳规则 * @param paternerKey 签名密匙 * </pre> * @return {String} */ public static String getDeepLink(String appId, String prepayId, String paternerKey) { Map<String, String> params = new HashMap<String, String>(); params.put("appid", appId); params.put("noncestr", System.currentTimeMillis() + ""); params.put("package", "WAP"); params.put("prepayid", prepayId); params.put("timestamp", System.currentTimeMillis() / 1000 + ""); String sign = PaymentKit.createSign(params, paternerKey); params.put("sign", sign); String string1 = PaymentKit.packageSign(params, true); String string2 = ""; try { string2 = PaymentKit.urlEncode(string1); } catch (UnsupportedEncodingException e) {} return "weixin://wap/pay?" + string2; } // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2 private static String orderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery"; /** * 根据商户订单号查询信息 * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 商户密钥 * @param transaction_id 微信订单号 * @return 回调信息 */ public static Map<String, String> queryByTransactionId(String appid, String mch_id, String paternerKey, String transaction_id) { Map<String, String> params = new HashMap<String, String>(); params.put("appid", appid); params.put("mch_id", mch_id); params.put("transaction_id", transaction_id); return request(orderQueryUrl, params, paternerKey); } /** * 根据商户订单号查询信息 * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 商户密钥 * @param out_trade_no 商户订单号 * @return 回调信息 */ public static Map<String, String> queryByOutTradeNo(String appid, String mch_id, String paternerKey, String out_trade_no) { Map<String, String> params = new HashMap<String, String>(); params.put("appid", appid); params.put("mch_id", mch_id); params.put("out_trade_no", out_trade_no); return request(orderQueryUrl, params, paternerKey); } // 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3 private static String closeOrderUrl = "https://api.mch.weixin.qq.com/pay/closeorder"; /** * 关闭订单 * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 商户密钥 * @param out_trade_no 商户订单号 * @return 回调信息 */ public static Map<String, String> closeOrder(String appid, String mch_id, String paternerKey, String out_trade_no) { Map<String, String> params = new HashMap<String, String>(); params.put("appid", appid); params.put("mch_id", mch_id); params.put("out_trade_no", out_trade_no); return request(closeOrderUrl, params, paternerKey); } // 申请退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4 public static String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund"; // 查询退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5 private static String refundQueryUrl = "https://api.mch.weixin.qq.com/pay/refundquery"; private static Map<String, String> baseRefundQuery(Map<String, String> params, String appid, String mch_id, String paternerKey) { params.put("appid", appid); params.put("mch_id", mch_id); return request(refundQueryUrl, params, paternerKey); } /** * 根据微信订单号查询退款 * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 商户密钥 * @param transaction_id 微信订单号 * @return map */ public static Map<String, String> refundQueryByTransactionId(String appid, String mch_id, String paternerKey, String transaction_id) { Map<String, String> params = new HashMap<String, String>(); params.put("transaction_id", transaction_id); return baseRefundQuery(params, appid, mch_id, paternerKey); } /** * 根据微信订单号查询退款 * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 商户密钥 * @param out_trade_no 商户订单号 * @return map */ public static Map<String, String> refundQueryByOutTradeNo(String appid, String mch_id, String paternerKey, String out_trade_no) { Map<String, String> params = new HashMap<String, String>(); params.put("out_trade_no", out_trade_no); return baseRefundQuery(params, appid, mch_id, paternerKey); } /** * 根据微信订单号查询退款 * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 商户密钥 * @param out_refund_no 商户退款单号 * @return map */ public static Map<String, String> refundQueryByOutRefundNo(String appid, String mch_id, String paternerKey, String out_refund_no) { Map<String, String> params = new HashMap<String, String>(); params.put("out_refund_no", out_refund_no); return baseRefundQuery(params, appid, mch_id, paternerKey); } /** * 根据微信订单号查询退款 * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 商户密钥 * @param refund_id 微信退款单号 * @return map */ public static Map<String, String> refundQueryByRefundId(String appid, String mch_id, String paternerKey, String refund_id) { Map<String, String> params = new HashMap<String, String>(); params.put("refund_id", refund_id); return baseRefundQuery(params, appid, mch_id, paternerKey); } private static String downloadBillUrl = "https://api.mch.weixin.qq.com/pay/downloadbill"; /** * <pre> * ALL,返回当日所有订单信息,默认值 * SUCCESS,返回当日成功支付的订单 * REFUND,返回当日退款订单 * REVOKED,已撤销的订单 * </pre> */ public static enum BillType { ALL, SUCCESS, REFUND, REVOKED } /** * 下载对账单 * <pre> * 公众账号ID appid 是 String(32) wx8888888888888888 微信分配的公众账号ID(企业号corpid即为此appId) * 商户号 mch_id 是 String(32) 1900000109 微信支付分配的商户号 * 设备号 device_info 否 String(32) 013467007045764 微信支付分配的终端设备号 * 随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法 * 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法 * 对账单日期 bill_date 是 String(8) 20140603 下载对账单的日期,格式:20140603 * 账单类型 bill_type 否 String(8) * </pre> * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 签名密匙 * @param billDate 对账单日期 * @return String */ public static String downloadBill(String appid, String mch_id, String paternerKey, String billDate) { return downloadBill(appid, mch_id, paternerKey, billDate, null); } /** * 下载对账单 * <pre> * 公众账号ID appid 是 String(32) wx8888888888888888 微信分配的公众账号ID(企业号corpid即为此appId) * 商户号 mch_id 是 String(32) 1900000109 微信支付分配的商户号 * 设备号 device_info 否 String(32) 013467007045764 微信支付分配的终端设备号 * 随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法 * 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法 * 对账单日期 bill_date 是 String(8) 20140603 下载对账单的日期,格式:20140603 * 账单类型 bill_type 否 String(8) * </pre> * @param appid 公众账号ID * @param mch_id 商户号 * @param paternerKey 签名密匙 * @param billDate 对账单日期 * @param billType 账单类型 * @return String */ public static String downloadBill(String appid, String mch_id, String paternerKey, String billDate, BillType billType) { Map<String, String> params = new HashMap<String, String>(); params.put("appid", appid); params.put("mch_id", mch_id); params.put("nonce_str", System.currentTimeMillis() + ""); params.put("bill_date", billDate); if (null != billType) { params.put("bill_type", billType.name()); } else { params.put("bill_type", BillType.ALL.name()); } String sign = PaymentKit.createSign(params, paternerKey); params.put("sign", sign); return HttpKit.post(downloadBillUrl, PaymentKit.toXml(params)); } }
package com.majker.common.sdk; import org.apache.commons.io.Charsets; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; /**************************************************** * * 微信支付的统一下单工具类 * * @author majker * @version 1.0 **************************************************/ public class PaymentKit { /** * 组装签名的字段 * @param params 参数 * @param urlEncoder 是否urlEncoder * @return String */ public static String packageSign(Map<String, String> params, boolean urlEncoder) { // 先将参数以其参数名的字典序升序进行排序 TreeMap<String, String> sortedParams = new TreeMap<String, String>(params); // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起 StringBuilder sb = new StringBuilder(); boolean first = true; for (Entry<String, String> param : sortedParams.entrySet()) { String value = param.getValue(); if (Tools.isEmpty(value)) { continue; } if (first) { first = false; } else { sb.append("&"); } sb.append(param.getKey()).append("="); if (urlEncoder) { try { value = urlEncode(value); } catch (UnsupportedEncodingException e) {} } sb.append(value); } return sb.toString(); } /** * urlEncode * @param src 微信参数 * @return String * @throws UnsupportedEncodingException 编码错误 */ public static String urlEncode(String src) throws UnsupportedEncodingException { return URLEncoder.encode(src, Charsets.UTF_8.name()).replace("+", "%20"); } /** * 生成签名 * @param params 参数 * @param paternerKey 支付密钥 * @return sign */ public static String createSign(Map<String, String> params, String paternerKey) { // 生成签名前先去除sign params.remove("sign"); String stringA = packageSign(params, false); String stringSignTemp = stringA + "&key=" + paternerKey; return Tools.md5(stringSignTemp).toUpperCase(); } /** * 支付异步通知时校验sign * @param params 参数 * @param paternerKey 支付密钥 * @return {boolean} */ public static boolean verifyNotify(Map<String, String> params, String paternerKey){ String sign = params.get("sign"); String localSign = PaymentKit.createSign(params, paternerKey); return sign.equals(localSign); } /** * 微信下单,map to xml * @param params 参数 * @return String */ public static String toXml(Map<String, String> params) { StringBuilder xml = new StringBuilder(); xml.append("<xml>"); for (Entry<String, String> entry : params.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); // 略过空值 if (Tools.isEmpty(value)) continue; xml.append("<").append(key).append(">"); xml.append(entry.getValue()); xml.append("</").append(key).append(">"); } xml.append("</xml>"); return xml.toString(); } /** * 针对支付的xml,没有嵌套节点的简单处理 * @param xmlStr xml字符串 * @return map集合 */ public static Map<String, String> xmlToMap(String xmlStr) { XmlHelper xmlHelper = XmlHelper.of(xmlStr); return xmlHelper.toMap(); } }
常用工具
package com.majker.common.sdk; import java.security.MessageDigest; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /**************************************************** * * 常用工具 * * @author majker * @version 1.0 **************************************************/ public class Tools { /** * 随机生成六位数验证码 * @return */ public static int getRandomNum(){ Random r = new Random(); return r.nextInt(900000)+100000;//(Math.random()*(999999-100000)+100000) } /** * 检测字符串是否不为空(null,"","null") * @param s * @return 不为空则返回true,否则返回false */ public static boolean notEmpty(String s){ return s!=null && !"".equals(s) && !"null".equals(s); } /** * 检测字符串是否为空(null,"","null") * @param s * @return 为空则返回true,不否则返回false */ public static boolean isEmpty(String s){ return s==null || "".equals(s) || "null".equals(s); } /** * 字符串转换为字符串数组 * @param str 字符串 * @param splitRegex 分隔符 * @return */ public static String[] str2StrArray(String str,String splitRegex){ if(isEmpty(str)){ return null; } return str.split(splitRegex); } /** * 用默认的分隔符(,)将字符串转换为字符串数组 * @param str 字符串 * @return */ public static String[] str2StrArray(String str){ return str2StrArray(str,",\\s*"); } /** * 按照yyyy-MM-dd HH:mm:ss的格式,日期转字符串 * @param date * @return yyyy-MM-dd HH:mm:ss */ public static String date2Str(Date date){ return date2Str(date,"yyyy-MM-dd HH:mm:ss"); } /** * 按照yyyy-MM-dd HH:mm:ss的格式,字符串转日期 * @param date * @return */ public static Date str2Date(String date){ if(notEmpty(date)){ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { return sdf.parse(date); } catch (ParseException e) { e.printStackTrace(); } return new Date(); }else{ return null; } } /** * 按照参数format的格式,日期转字符串 * @param date * @param format * @return */ public static String date2Str(Date date,String format){ if(date!=null){ SimpleDateFormat sdf = new SimpleDateFormat(format); return sdf.format(date); }else{ return ""; } } /** * 把时间根据时、分、秒转换为时间段 * @param StrDate */ public static String getTimes(String StrDate){ String resultTimes = ""; SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date now; try { now = new Date(); Date date=df.parse(StrDate); long times = now.getTime()-date.getTime(); long day = times/(24*60*60*1000); long hour = (times/(60*60*1000)-day*24); long min = ((times/(60*1000))-day*24*60-hour*60); long sec = (times/1000-day*24*60*60-hour*60*60-min*60); StringBuffer sb = new StringBuffer(); //sb.append("发表于:"); if(hour>0 ){ sb.append(hour+"小时前"); } else if(min>0){ sb.append(min+"分钟前"); } else{ sb.append(sec+"秒前"); } resultTimes = sb.toString(); } catch (ParseException e) { e.printStackTrace(); } return resultTimes; } /** * 验证邮箱 * @param email * @return */ public static boolean checkEmail(String email){ boolean flag = false; try{ String check = "^([a-z0-9A-Z]+[-|_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; Pattern regex = Pattern.compile(check); Matcher matcher = regex.matcher(email); flag = matcher.matches(); }catch(Exception e){ flag = false; } return flag; } /** * 验证手机号码 * @return */ public static boolean checkMobileNumber(String mobileNumber){ Pattern p = null; Matcher m = null; boolean b = false; p = Pattern.compile("^[1][3,4,5,7,8][0-9]{9}$"); // 验证手机号 m = p.matcher(mobileNumber); b = m.matches(); return b; } /** * 将驼峰转下划线 * @param param * @return */ public static String camelToUnderline(String param){ if (param==null||"".equals(param.trim())){ return ""; } int len=param.length(); StringBuilder sb=new StringBuilder(len); for (int i = 0; i < len; i++) { char c=param.charAt(i); if (Character.isUpperCase(c)){ sb.append("_"); sb.append(Character.toLowerCase(c)); }else{ sb.append(c); } } return sb.toString(); } /** * 去掉下划线并将下划线后的首字母转为大写 * @param str * @return */ public static String transformStr(String str){ //去掉数据库字段的下划线 if(str.contains("_")) { String[] names = str.split("_"); String firstPart = names[0]; String otherPart = ""; for (int i = 1; i < names.length; i++) { String word = names[i].replaceFirst(names[i].substring(0, 1), names[i].substring(0, 1).toUpperCase()); otherPart += word; } str = firstPart + otherPart; } return str; } /** * 转换为map * @param list * @return */ public static List<Map<String,Object>> transformMap(List<Map<String,Object>> list){ List<Map<String,Object>> resultMapList = new ArrayList<>(); for (Map<String, Object> map : list) { Map<String,Object> tempMap = new HashMap<>(); for (String s : map.keySet()) { tempMap.put(transformStr(s),map.get(s)); } resultMapList.add(tempMap); } return resultMapList; } public static String clearHtml(String content,int p) { if(null==content) return ""; if(0==p) return ""; Pattern p_script; Matcher m_script; Pattern p_style; Matcher m_style; Pattern p_html; Matcher m_html; try { String regEx_script = "<[\\s]*?script[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?script[\\s]*?>"; //定义script的正则表达式{或<script[^>]*?>[\\s\\S]*?<\\/script> } String regEx_style = "<[\\s]*?style[^>]*?>[\\s\\S]*?<[\\s]*?\\/[\\s]*?style[\\s]*?>"; //定义style的正则表达式{或<style[^>]*?>[\\s\\S]*?<\\/style> } String regEx_html = "<[^>]+>"; //定义HTML标签的正则表达式 p_script = Pattern.compile(regEx_script,Pattern.CASE_INSENSITIVE); m_script = p_script.matcher(content); content = m_script.replaceAll(""); //过滤script标签 p_style = Pattern.compile(regEx_style,Pattern.CASE_INSENSITIVE); m_style = p_style.matcher(content); content = m_style.replaceAll(""); //过滤style标签 p_html = Pattern.compile(regEx_html,Pattern.CASE_INSENSITIVE); m_html = p_html.matcher(content); content = m_html.replaceAll(""); //过滤html标签 }catch(Exception e) { return ""; } if(content.length()>p){ content = content.substring(0, p)+"..."; }else{ content = content + "..."; } return content; } public static String md5(String str) { try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(str.getBytes()); byte b[] = md.digest(); int i; StringBuffer buf = new StringBuffer(""); for (int offset = 0; offset < b.length; offset++) { i = b[offset]; if (i < 0) i += 256; if (i < 16) buf.append("0"); buf.append(Integer.toHexString(i)); } str = buf.toString(); } catch (Exception e) { e.printStackTrace(); } return str; } }
微信支付的一些常量配置
package com.majker.common.sdk; /** * 常量 */ public class WXPayConstants { public enum SignType { MD5, HMACSHA256 } public static final String DOMAIN_API = "api.mch.weixin.qq.com"; public static final String DOMAIN_API2 = "api2.mch.weixin.qq.com"; public static final String DOMAIN_APIHK = "apihk.mch.weixin.qq.com"; public static final String DOMAIN_APIUS = "apius.mch.weixin.qq.com"; public static final String FAIL = "FAIL"; public static final String SUCCESS = "SUCCESS"; public static final String HMACSHA256 = "HMAC-SHA256"; public static final String MD5 = "MD5"; public static final String FIELD_SIGN = "sign"; public static final String FIELD_SIGN_TYPE = "sign_type"; public static final String MICROPAY_URL_SUFFIX = "/pay/micropay"; public static final String UNIFIEDORDER_URL_SUFFIX = "/pay/unifiedorder"; public static final String ORDERQUERY_URL_SUFFIX = "/pay/orderquery"; public static final String REVERSE_URL_SUFFIX = "/secapi/pay/reverse"; public static final String CLOSEORDER_URL_SUFFIX = "/pay/closeorder"; public static final String REFUND_URL_SUFFIX = "/secapi/pay/refund"; public static final String REFUNDQUERY_URL_SUFFIX = "/pay/refundquery"; public static final String DOWNLOADBILL_URL_SUFFIX = "/pay/downloadbill"; public static final String REPORT_URL_SUFFIX = "/payitil/report"; public static final String SHORTURL_URL_SUFFIX = "/tools/shorturl"; public static final String AUTHCODETOOPENID_URL_SUFFIX = "/tools/authcodetoopenid"; // sandbox public static final String SANDBOX_MICROPAY_URL_SUFFIX = "/sandboxnew/pay/micropay"; public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder"; public static final String SANDBOX_ORDERQUERY_URL_SUFFIX = "/sandboxnew/pay/orderquery"; public static final String SANDBOX_REVERSE_URL_SUFFIX = "/sandboxnew/secapi/pay/reverse"; public static final String SANDBOX_CLOSEORDER_URL_SUFFIX = "/sandboxnew/pay/closeorder"; public static final String SANDBOX_REFUND_URL_SUFFIX = "/sandboxnew/secapi/pay/refund"; public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX = "/sandboxnew/pay/refundquery"; public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill"; public static final String SANDBOX_REPORT_URL_SUFFIX = "/sandboxnew/payitil/report"; public static final String SANDBOX_SHORTURL_URL_SUFFIX = "/sandboxnew/tools/shorturl"; public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid"; }
package com.majker.common.sdk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.StringWriter; import java.security.MessageDigest; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; /**************************************************** * * * * @author majker * @date: 2019/3/7 * @version 1.0 **************************************************/ public class WXPayUtil { /** * XML格式字符串转换为Map * * @param strXML XML字符串 * @return XML数据转换后的Map * @throws Exception */ public static Map<String, String> xmlToMap(String strXML) throws Exception { try { Map<String, String> data = new HashMap<String, String>(); DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8")); org.w3c.dom.Document doc = documentBuilder.parse(stream); doc.getDocumentElement().normalize(); NodeList nodeList = doc.getDocumentElement().getChildNodes(); for (int idx = 0; idx < nodeList.getLength(); ++idx) { Node node = nodeList.item(idx); if (node.getNodeType() == Node.ELEMENT_NODE) { org.w3c.dom.Element element = (org.w3c.dom.Element) node; data.put(element.getNodeName(), element.getTextContent()); } } try { stream.close(); } catch (Exception ex) { // do nothing } return data; } catch (Exception ex) { WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML); throw ex; } } /** * 将Map转换为XML格式的字符串 * * @param data Map类型数据 * @return XML格式的字符串 * @throws Exception */ public static String mapToXml(Map<String, String> data) throws Exception { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder(); org.w3c.dom.Document document = documentBuilder.newDocument(); org.w3c.dom.Element root = document.createElement("xml"); document.appendChild(root); for (String key: data.keySet()) { String value = data.get(key); if (value == null) { value = ""; } value = value.trim(); org.w3c.dom.Element filed = document.createElement(key); filed.appendChild(document.createTextNode(value)); root.appendChild(filed); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); DOMSource source = new DOMSource(document); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StringWriter writer = new StringWriter(); StreamResult result = new StreamResult(writer); transformer.transform(source, result); String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", ""); try { writer.close(); } catch (Exception ex) { } return output; } /** * 生成带有 sign 的 XML 格式字符串 * * @param data Map类型数据 * @param key API密钥 * @return 含有sign字段的XML */ public static String generateSignedXml(final Map<String, String> data, String key) throws Exception { return generateSignedXml(data, key, WXPayConstants.SignType.MD5); } /** * 生成带有 sign 的 XML 格式字符串 * * @param data Map类型数据 * @param key API密钥 * @param signType 签名类型 * @return 含有sign字段的XML */ public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { String sign = generateSignature(data, key, signType); data.put(WXPayConstants.FIELD_SIGN, sign); return mapToXml(data); } /** * 判断签名是否正确 * * @param xmlStr XML格式数据 * @param key API密钥 * @return 签名是否正确 * @throws Exception */ public static boolean isSignatureValid(String xmlStr, String key) throws Exception { Map<String, String> data = xmlToMap(xmlStr); if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { return false; } String sign = data.get(WXPayConstants.FIELD_SIGN); return generateSignature(data, key).equals(sign); } /** * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。 * * @param data Map类型数据 * @param key API密钥 * @return 签名是否正确 * @throws Exception */ public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception { return isSignatureValid(data, key, WXPayConstants.SignType.MD5); } /** * 判断签名是否正确,必须包含sign字段,否则返回false。 * * @param data Map类型数据 * @param key API密钥 * @param signType 签名方式 * @return 签名是否正确 * @throws Exception */ public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) { return false; } String sign = data.get(WXPayConstants.FIELD_SIGN); return generateSignature(data, key, signType).equals(sign); } /** * 生成签名 * * @param data 待签名数据 * @param key API密钥 * @return 签名 */ public static String generateSignature(final Map<String, String> data, String key) throws Exception { return generateSignature(data, key, WXPayConstants.SignType.MD5); } /** * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。 * * @param data 待签名数据 * @param key API密钥 * @param signType 签名方式 * @return 签名 */ public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception { Set<String> keySet = data.keySet(); String[] keyArray = keySet.toArray(new String[keySet.size()]); Arrays.sort(keyArray); StringBuilder sb = new StringBuilder(); for (String k : keyArray) { if (k.equals(WXPayConstants.FIELD_SIGN)) { continue; } if(data.get(k).trim().length() > 0) // 参数值为空,则不参与签名 sb.append(k).append("=").append(data.get(k).trim()).append("&"); } sb.append("key=").append(key); if (WXPayConstants.SignType.MD5.equals(signType)) { return MD5(sb.toString()).toUpperCase(); } else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) { return HMACSHA256(sb.toString(), key); } else { throw new Exception(String.format("Invalid sign_type: %s", signType)); } } /** * 获取随机字符串 Nonce Str * * @return String 随机字符串 */ public static String generateNonceStr() { return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); } /** * 生成 MD5 * * @param data 待处理数据 * @return MD5结果 */ public static String MD5(String data) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] array = md.digest(data.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); } return sb.toString().toUpperCase(); } /** * 生成 HMACSHA256 * @param data 待处理数据 * @param key 密钥 * @return 加密结果 * @throws Exception */ public static String HMACSHA256(String data, String key) throws Exception { Mac sha256_HMAC = Mac.getInstance("HmacSHA256"); SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256"); sha256_HMAC.init(secret_key); byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte item : array) { sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); } return sb.toString().toUpperCase(); } /** * 日志 * @return */ public static Logger getLogger() { Logger logger = LoggerFactory.getLogger("wxpay java sdk"); return logger; } /** * 获取当前时间戳,单位秒 * @return */ public static long getCurrentTimestamp() { return System.currentTimeMillis()/1000; } /** * 获取当前时间戳,单位毫秒 * @return */ public static long getCurrentTimestampMs() { return System.currentTimeMillis(); } /** * 生成 uuid, 即用来标识一笔单,也用做 nonce_str * @return */ public static String generateUUID() { return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); } }
package com.majker.common.sdk; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.HashMap; import java.util.Map; /** * xpath解析xml * <pre> * 文档地址: * http://www.w3school.com.cn/xpath/index.asp * </pre> * @author majker */ public class XmlHelper { private final XPath path; private final Document doc; private XmlHelper(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException { DocumentBuilderFactory dbf = getDocumentBuilderFactory(); DocumentBuilder db = dbf.newDocumentBuilder(); doc = db.parse(inputSource); path = getXPathFactory().newXPath(); } private static XmlHelper create(InputSource inputSource) { try { return new XmlHelper(inputSource); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } catch (SAXException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } public static XmlHelper of(InputStream is) { InputSource inputSource = new InputSource(is); return create(inputSource); } public static XmlHelper of(String xmlStr) { StringReader sr = new StringReader(xmlStr.trim()); InputSource inputSource = new InputSource(sr); XmlHelper xmlHelper = create(inputSource); IOUtils.closeQuietly(sr); return xmlHelper; } private Object evalXPath(String expression, Object item, QName returnType) { item = null == item ? doc : item; try { return path.evaluate(expression, item, returnType); } catch (XPathExpressionException e) { throw new RuntimeException(e); } } /** * 获取String * @param expression 路径 * @return String */ public String getString(String expression) { return (String) evalXPath(expression, null, XPathConstants.STRING); } /** * 获取Boolean * @param expression 路径 * @return String */ public Boolean getBoolean(String expression) { return (Boolean) evalXPath(expression, null, XPathConstants.BOOLEAN); } /** * 获取Number * @param expression 路径 * @return {Number} */ public Number getNumber(String expression) { return (Number) evalXPath(expression, null, XPathConstants.NUMBER); } /** * 获取某个节点 * @param expression 路径 * @return {Node} */ public Node getNode(String expression) { return (Node) evalXPath(expression, null, XPathConstants.NODE); } /** * 获取子节点 * @param expression 路径 * @return NodeList */ public NodeList getNodeList(String expression) { return (NodeList) evalXPath(expression, null, XPathConstants.NODESET); } /** * 获取String * @param node 节点 * @param expression 相对于node的路径 * @return String */ public String getString(Object node, String expression) { return (String) evalXPath(expression, node, XPathConstants.STRING); } /** * 获取 * @param node 节点 * @param expression 相对于node的路径 * @return String */ public Boolean getBoolean(Object node, String expression) { return (Boolean) evalXPath(expression, node, XPathConstants.BOOLEAN); } /** * 获取 * @param node 节点 * @param expression 相对于node的路径 * @return {Number} */ public Number getNumber(Object node, String expression) { return (Number) evalXPath(expression, node, XPathConstants.NUMBER); } /** * 获取某个节点 * @param node 节点 * @param expression 路径 * @return {Node} */ public Node getNode(Object node, String expression) { return (Node) evalXPath(expression, node, XPathConstants.NODE); } /** * 获取子节点 * @param node 节点 * @param expression 相对于node的路径 * @return NodeList */ public NodeList getNodeList(Object node, String expression) { return (NodeList) evalXPath(expression, node, XPathConstants.NODESET); } /** * 针对没有嵌套节点的简单处理 * @return map集合 */ public Map<String, String> toMap() { Element root = doc.getDocumentElement(); Map<String, String> params = new HashMap<String, String>(); // 将节点封装成map形式 NodeList list = root.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { Node node = list.item(i); if (node instanceof Element) { params.put(node.getNodeName(), node.getTextContent()); } } return params; } private static DocumentBuilderFactory getDocumentBuilderFactory(){ return XmlHelperHolder.documentBuilderFactory; } private static XPathFactory getXPathFactory() { return XmlHelperHolder.xPathFactory; } /** * 内部类单例 */ private static class XmlHelperHolder { private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); private static XPathFactory xPathFactory = XPathFactory.newInstance(); } }
开放给小程序端的 充值接口
1.调用工具类中的 获取openId 方法
2.使用openID 调用业务层的下订单的方法
package com.majker.modules.wechat.applet.web; import com.majker.common.util.IdGen; import com.majker.common.util.Render; import com.majker.modules.wechat.applet.dto.RechargeDto; import com.majker.modules.wechat.applet.service.PayService; import com.majker.modules.wechat.applet.util.AppletPayUtil; import com.majker.modules.wechat.base.BjddController; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /**************************************************** * * * * @author majker * @date 2019-03-07 21:21 * @version 1.0 **************************************************/ @RestController @RequestMapping("/m/recharge") public class RechargeAPI extends BjddController { @Autowired private PayService payService; @ApiOperation(value = "创建充值订单", notes = "创建充值订单") @PostMapping public Object order(@RequestBody RechargeDto rechargeDto) throws Exception { /** 微信小程序支付 */ //获取code String code = rechargeDto.getCode(); //调用接口获取openId String openId = AppletPayUtil.getOpenIdByCode(code); /* 生成订单....,这里只创建了订单号 */ //订单号 uuid String outTradeNo= IdGen.uuid(); return Render.ok(payService.unifiedOrder(outTradeNo,rechargeDto.getRechargeMoney(),openId)); } }
微信支付成功后,微信要调用接口,微信自动调用
package com.majker.modules.wechat.applet.web; import com.majker.common.sdk.HttpKit; import com.majker.common.sdk.PaymentKit; import com.majker.modules.wechat.base.BjddController; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Map; /**************************************************** * * 小程序回调控制器 * * @author majker * @version 1.0 **************************************************/ @RestController @RequestMapping("m/pay/") public class PayApi extends BjddController { /** * 成功的标识 */ private final static String SUCCESS="SUCCESS"; /** * 返回状态码的变量名 * */ private final static String RETURN_CODE="RETURN_CODE"; /** * 功能描述: <小程序回调> * @return: * @auther: majker * @date: 2019/3/10 **/ @RequestMapping("/wxProPayNotify/anon") public void wxProPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception { logger.info("进入微信小程序支付回调"); String xmlMsg = HttpKit.readData(request); logger.info("微信小程序通知信息"+xmlMsg); Map<String, String> resultMap = PaymentKit.xmlToMap(xmlMsg); if(resultMap.get(RETURN_CODE).equals(SUCCESS)){ String orderNo = resultMap.get("out_trade_no"); logger.info("微信小程序支付成功,订单号{}",orderNo); /** * 通过订单号 修改数据库中的记录,此处省略n行代码 */ } String result = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"; try { response.getWriter().write(result); } catch (IOException e) { e.printStackTrace(); } } }
该类中只有个获取openId 的方法,暂时写在工具类中。
package com.majker.modules.wechat.applet.util; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.majker.common.config.WxProgramPayConfig; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; /**************************************************** * * * * @author majker * @date 2019-03-07 21:24 * @version 1.0 **************************************************/ @Slf4j public class AppletPayUtil { /** * 根据 临时登录凭证获取openId * 文档:https://developers.weixin.qq.com/miniprogram/dev/api/code2Session.html * * @param code * @return * @author majker */ public static String getOpenIdByCode(String code) { log.info("获取code成功!{}", code); //登录凭证校验 // String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + WxProgramPayConfig.APPID + "&secret=" + WxProgramPayConfig.SECRET + "&js_code=" + code + "&grant_type=authorization_code"; //发送请求给微信后端 CloseableHttpClient httpClient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); InputStream inputStream = null; CloseableHttpResponse httpResponse = null; StringBuilder result = new StringBuilder(); String openId = null; try { httpResponse = httpClient.execute(httpGet); HttpEntity entity = httpResponse.getEntity(); inputStream = entity.getContent(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String line = ""; while ((line = bufferedReader.readLine()) != null) { //这里需要使用fastjson来提取一下内容 System.out.println(line); JSONObject jsonObject = JSON.parseObject(line); openId = jsonObject.getString("openid"); String sessionKey = jsonObject.getString("session_key"); log.info("openId={},sessionKey={}", openId, sessionKey); } } catch (IOException e) { log.error("获取openId失败" + e.getMessage()); } return openId; } }
package com.majker.modules.wechat.applet.service; import com.majker.common.config.WxProgramPayConfig; import com.majker.common.sdk.PaymentApi; import com.majker.common.sdk.PaymentKit; import com.majker.common.sdk.WXPayUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.util.HashMap; import java.util.Map; /**************************************************** * * * * @author majker * @date 2019-03-07 17:23 * @version 1.0 **************************************************/ @Slf4j @Service public class PayService { @Value("${com.majker.project.name}") public static String projectName; public void setProjectName(String projectName) { projectName = projectName; } /** * 功能描述: <调用统一下单的接口> * @return: * @auther: majker * @date: 2019/3/7 **/ public Object unifiedOrder(String outTradeNo, BigDecimal money, String openid) throws Exception { Map<String, String> reqParams = new HashMap<>(); //微信分配的小程序ID reqParams.put("appid", WxProgramPayConfig.APPID); //微信支付分配的商户号 reqParams.put("mch_id", WxProgramPayConfig.MCH_ID); //随机字符串 reqParams.put("nonce_str", System.currentTimeMillis() / 1000 + ""); //签名类型 reqParams.put("sign_type", "MD5"); //充值订单 商品描述 reqParams.put("body", projectName + "-充值订单-微信小程序"); //商户订单号 reqParams.put("out_trade_no", outTradeNo); //订单总金额,单位为分 reqParams.put("total_fee", money.multiply(BigDecimal.valueOf(100)).intValue() + ""); //终端IP reqParams.put("spbill_create_ip", "127.0.0.1"); //通知地址 reqParams.put("notify_url", WxProgramPayConfig.NOTIFY_URL); //交易类型 reqParams.put("trade_type", "JSAPI"); //用户标识 reqParams.put("openid", openid); //签名 String sign = WXPayUtil.generateSignature(reqParams, WxProgramPayConfig.KEY); reqParams.put("sign", sign); /* 调用支付定义下单API,返回预付单信息 prepay_id */ String xmlResult = PaymentApi.pushOrder(reqParams); log.info(xmlResult); Map<String, String> result = PaymentKit.xmlToMap(xmlResult); //预付单信息 String prepay_id = result.get("prepay_id"); /* 小程序调起支付数据签名 */ Map<String, String> packageParams = new HashMap<String, String>(); packageParams.put("appId", WxProgramPayConfig.APPID); packageParams.put("timeStamp", System.currentTimeMillis() / 1000 + ""); packageParams.put("nonceStr", System.currentTimeMillis() + ""); packageParams.put("package", "prepay_id=" + prepay_id); packageParams.put("signType", "MD5"); String packageSign = WXPayUtil.generateSignature(packageParams, WxProgramPayConfig.KEY); packageParams.put("paySign", packageSign); return packageParams; } }
package com.majker.modules.wechat.applet.dto; import lombok.Data; import java.math.BigDecimal; /**************************************************** * * 充值实体 * * @author majker * @date: 2019/3/10 * @version 1.0 **************************************************/ @Data public class RechargeDto { /** * 充值 支付类型 * 0 微信 1 支付宝 2.公众号微信 3.微信小程序 */ private int payType; /** * 用户id */ private String userId; /** * 充值金额 */ private BigDecimal rechargeMoney; /** * 临时凭证code * 小程序支付调用wx.login();获取到登录临时凭证code */ private String code; }
创建成功后的目录如下
//域名+端口号
var WYY_HOST_URL = "https://majker.com:80";
var type = "Fitment";
module.exports = {
wyy_host_api_url: WYY_HOST_URL,
wyy_user_wxappid: "6",
wyy_share_info: '',
wyy_config_version: 2567,
//获取充值信息
user_recharge: WYY_HOST_URL + "/m/recharge/money/anon",
//确定充值
user_recharge_re: WYY_HOST_URL + "/m/recharge"
}
该文件中requestLoading 可以在head 里面传递参数
function request(url, params, success, fail) { this.requestLoading(url, params, "", success, fail) } function requestLoading(url, params, message, header, method, success, fail) { wx.showNavigationBarLoading() if (message != "") { wx.showLoading({ title: message, }) } wx.request({ url: url, data: params, header: { 'Authorization': header, 'content-type': 'application/x-www-form-urlencoded' }, method: method, success: function (res) { //console.log(res.data) wx.hideNavigationBarLoading() if (message != "") { wx.hideLoading() } if (res.statusCode == 200) { success(res.data) } else { fail() } }, fail: function (res) { wx.hideNavigationBarLoading() if (message != "") { wx.hideLoading() } fail() }, complete: function (res) { }, }) } module.exports = { request: request, requestLoading: requestLoading, }
// pages/recharge/recharge.js //链接加载 var con = require("../../utils/data.js"); //方法 var network = require("../../utils/network.js"); Page({ /** * 页面的初始数据 */ data: { balance: "1000.00" , mealList: [{ "id": "6d163c287cfd42cbb7cf6783e40875ae", "label": "100", "sort": "2", "value": "25" }, { "id": "56d180dc933a4a329631d50703b5ed5c", "label": "50", "sort": "1", "value": "10" }, { "id": "3d1a37867618459b8bd4f096c798c803", "label": "30", "sort": "0", "value": "5" } ], give: "0", num: 0, rechargeMoney: "0" }, loadData: function(message) { var that = this; //渲染充值列表(动态) // network.requestLoading(con.user_recharge, null, message, "", "GET", function(res) { // if (res.code == 200) { // console.log(res.data) // that.setData({ // mealList: res.data, // give: res.data[0].value, // rechargeMoney: res.data[0].label // }) // } else { // wx.showToast({ // title: res.msg, // icon: 'none', // duration: 2000 // }) // } // }, function(res) { // wx.showToast({ // title: '加载数据失败', // }) // }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function(options) { this.loadData("加载数据"); }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady: function() { }, /** * 生命周期函数--监听页面显示 */ onShow: function() { }, /** * 生命周期函数--监听页面隐藏 */ onHide: function() { }, /** * 生命周期函数--监听页面卸载 */ onUnload: function() { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh: function() { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function() { }, //选择日期后加样式 select_date: function(e) { console.log(this.data.mealList) this.setData({ num: e.target.dataset.num, give: this.data.mealList[e.target.dataset.num].value }) }, recharge: function() { var that = this; // console.log(parmas); //********************************************* 小测试************************************// //小程序调用登录接口 获取code wx.login({ success(res) { if (res.code) { var paramsData = { "payType": "3", "rechargeMoney": that.data.mealList[that.data.num].label, //用户id 请调用相应接口 本地只作测试处理 "userId": "c39993421bca559c99736a67bb29c526", "code": res.code } //小程序请求服务器接口 start wx.request({ url: con.user_recharge_re, method: "post", data: paramsData, header: { 'content-type': 'application/json' }, success: function(res) { //小程序调用 支付API start wx.requestPayment({ timeStamp: res.data.data.timeStamp, nonceStr: res.data.data.nonceStr, package: res.data.data.package, signType: res.data.data.signType, paySign: res.data.data.paySign, success: function(res) { console.log(res); }, fail: function(res) { console.log(res); }, complete: function(res) { console.log(res); } }) //小程序调用 支付API end } }) //小程序请求服务器接口 end } else { console.log('登录失败!' + res.errMsg) } } }) //********************************************* 小测试************************************// }, /** * 用户点击右上角分享 */ onShareAppMessage: function() { } })
<view class='recharge_top'> 充值 </view> <view class='recharge_balance'> <view style='color:#b504cc'> <text style='font-size:90rpx'> {{balance}}</text>元</view> <view>当前账号余额</view> </view> <view class='recharge_meal'> <view class='recharge_meal_title'>充 值 金 额</view> <view class='mealList'> <view class='{{num==index?"active":"unactive"}}' wx:key="index" bindtap="select_date" data-num='{{index}}' wx:for="{{mealList}}"> {{item.label}}元 </view> </view> <view class='giveMeal'> 赠送{{give}}块 </view> </view> <button class='recharge' bindtap="recharge">充 值</button>
/* pages/demo/recharge.wxss */ page { background: #fff; height: 100%; } .recharge_top { width: 100%; height: 131rpx; text-align: center; line-height: 131rpx; font-size: 39rpx; } .recharge_balance { width: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; height: 350rpx; font-size: 26rpx; box-shadow: 0 1px 10px 1px #b504cc; color: #979797; } .recharge_meal { margin: 0 20rpx; } .recharge_meal_title { font-size: 26rpx; height: 80rpx; line-height: 80rpx; } .mealList { display: flex; flex-wrap: wrap; justify-content: space-between; } .unactive { background: #eee; width: 215rpx; height: 93rpx; line-height: 93rpx; text-align: center; font-size: 34rpx; margin-bottom: 5px; } .active { color: #fff; background: #b504cc; width: 215rpx; height: 93rpx; line-height: 93rpx; text-align: center; font-size: 34rpx; margin-bottom: 5px; } .giveMeal { color: #b504cc; font-size: 22rpx; height: 80rpx; width: 100%; line-height: 80rpx; border-bottom: 1rpx solid #dbdbdb; } .giveMealImg { width: 25rpx; height: 25rpx; margin-right: 15rpx; } .giveExplain { margin-top: 15rpx; color: #acacac; font-size: 22rpx; display: flex; align-items: center; } .giveExplainImg { width: 28rpx; height: 28rpx; margin-right: 15rpx; } .recharge { width: 278rpx; height: 90rpx; display: block; position: absolute; background: #b504cc; bottom: 35rpx; left: calc(50% - 139rpx); border-radius: 45rpx; box-shadow: 1rpx 2rpx 1rpx 1rpx #b504cc; font-size: 36rpx; color: #fff; }
API文档:小程序登录 https://developers.weixin.qq.com/miniprogram/dev/api/wx.login.html
wx.login({
success(res) {
if (res.code) {
console.log("临时登录凭证 code" + res.code)
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
小程序调起支付API:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
wx.requestPayment({ timeStamp: res.data.data.timeStamp, nonceStr: res.data.data.nonceStr, package: res.data.data.package, signType: res.data.data.signType, paySign: res.data.data.paySign, 'success': function(res) { console.log(res); }, 'fail': function(res) { console.log(res); }, 'complete': function(res) { console.log(res); } })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。