微信小程序对接微信支付功能

  • 业务流程时序图
  • JAVA版
    • 1. 项目架构
    • 2. pom.xml配置文件
    • 3. 小程序账号参数配置类
    • 4.JAVA 通用代码
      • 4.1 工具类
        • 4.1.1 IdGen (id生成类)
        • 4.1.2 Render(响应结果类)
        • 4.1.3 ResultCode 响应码
      • 4.2 SDK类
        • 4.2.1 HttpKit
        • 4.2.2 IOUtils
        • 4.2.3 PaymentApi
        • 4.2.4 PaymentKit
        • 4.2.5 tool
        • 4.2.6 WXPayConstants
        • 4.2.7 WXPayUtil (官方SDK)
        • 4.2.8 xml解析类
    • 5.JAVA业务代码
      • 5.1 控制器
        • 5.1.1 下充值订单
        • 5.1.2 小程序回调控制器
      • 5.2 工具类
      • 5.3 业务层,调用下订单的API
      • 5.4 实体 RechargeDto (接口参数的实体Bean)
  • 小程序版
    • 1.创建小程序项目
    • 2. 创建相关的目录文件 如下
    • 3. util目录
      • 3.1 data.js
      • 3.2 network.js
    • 4. demo下的文件内容
      • 4.1 recharge.js
      • 4.2 recharge.wxml
      • 4.3 recharge.wxss
    • 5.页面效果
    • 6.相关API:
      • 6.1 小程序内调用登录接口,获取临时登录凭证code
      • 6.2小程序调用支付API
        • 6.2.1 参数说明
        • 6.2.2 小程序代码
  • 参考链接

业务流程时序图

地址:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_4&index=3

JAVA版

1. 项目架构

2. pom.xml配置文件

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>

3. 小程序账号参数配置类

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 = "";
}

参数详情请看下图,(来源:小程序账号申请微信支付过程中获取的)

4.JAVA 通用代码

4.1 工具类

4.1.1 IdGen (id生成类)
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());}}
4.1.2 Render(响应结果类)
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;}/*** 过滤字段:指定需要序列化的字段*/@JsonIgnoreprivate transient Map<Class<?>, Set<String>> includes;/*** 过滤字段:指定不需要序列化的字段*/@JsonIgnoreprivate 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<>());}@Overridepublic 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;}}
4.1.3 ResultCode 响应码
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;
}

4.2 SDK类

使用的官方的sdk和自己编写的代码,相关类如下,下载下方给的demo

4.2.1 HttpKit
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 */@Deprecatedpublic 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;}}}
4.2.2 IOUtils
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);}
}
4.2.3 PaymentApi
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_1private 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_2private 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_3private 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_4public static String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";// 查询退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5private 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));}}
4.2.4 PaymentKit
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) {// 生成签名前先去除signparams.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();}}
4.2.5 tool

常用工具

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;}}
4.2.6 WXPayConstants

微信支付的一些常量配置

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";// sandboxpublic 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";}
4.2.7 WXPayUtil (官方SDK)
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);}}
4.2.8 xml解析类
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();}}

5.JAVA业务代码

5.1 控制器

5.1.1 下充值订单

开放给小程序端的 充值接口
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 {@Autowiredprivate PayService payService;@ApiOperation(value = "创建充值订单", notes = "创建充值订单")@PostMappingpublic Object order(@RequestBody RechargeDto rechargeDto) throws Exception {/**微信小程序支付*///获取codeString code = rechargeDto.getCode();//调用接口获取openIdString openId = AppletPayUtil.getOpenIdByCode(code);/*生成订单....,这里只创建了订单号*///订单号  uuidString outTradeNo= IdGen.uuid();return Render.ok(payService.unifiedOrder(outTradeNo,rechargeDto.getRechargeMoney(),openId));}
}
5.1.2 小程序回调控制器

微信支付成功后,微信要调用接口,微信自动调用

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();}}}

5.2 工具类

该类中只有个获取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;}
}

5.3 业务层,调用下订单的API

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<>();//微信分配的小程序IDreqParams.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() + "");//终端IPreqParams.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;}}

5.4 实体 RechargeDto (接口参数的实体Bean)

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;}

小程序版

1.创建小程序项目

创建成功后的目录如下

2. 创建相关的目录文件 如下

  1. 创建demo文件夹,在该目录下创建.js ,json,wxml.wxss 文件
    其中js文件里面写js代码,json可以存放数据,wxml 可以构建页面视图(类似html),wxss可以进行页面渲染(类似css)

3. util目录

3.1 data.js
//域名+端口号
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"
}
3.2 network.js

该文件中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,
}

4. demo下的文件内容

4.1 recharge.js
// 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);//********************************************* 小测试************************************////小程序调用登录接口 获取codewx.login({success(res) {if (res.code) {var paramsData = {"payType": "3","rechargeMoney": that.data.mealList[that.data.num].label,//用户id 请调用相应接口 本地只作测试处理"userId": "c39993421bca559c99736a67bb29c526","code": res.code}//小程序请求服务器接口 startwx.request({url: con.user_recharge_re,method: "post",data: paramsData,header: {'content-type': 'application/json'},success: function(res) {//小程序调用 支付API startwx.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() {}
})
4.2 recharge.wxml
<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>
4.3 recharge.wxss
/* 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;
}

5.页面效果

6.相关API:

6.1 小程序内调用登录接口,获取临时登录凭证code

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)}}
})

6.2小程序调用支付API

小程序调起支付API:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5

6.2.1 参数说明

6.2.2 小程序代码
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);}
})

参考链接

名称 链接
微信小程序下订单 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1
小程序调取支付API https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5
HTTPS服务器配置 https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=10_4
小程序API文档 https://developers.weixin.qq.com/miniprogram/dev/

springboot 微信小程序 对接微信支付功能(完整版)相关推荐

  1. 微信小程序怎么开通支付功能?

    对于一些想通过小程序卖货的企业商家来说,在申请小程序后需要同时开通微信支付功能,才能在小程序上进行交易.那么关于微信小程序怎么开通支付功能,下面给大家说一说. 一.注册非个人主体的小程序账号 不管你是 ...

  2. 微信小程序如何开通支付功能?

    微信小程序商城支付方式有哪些,如何开通小程序商城的支付功能,商家开通小程序支付功能的条件有哪些,小程序支付和微商城支付有冲突吗,小程序商城支付方式下商家如何提现,手续费多少? 微信小程序商城支付方式 ...

  3. 让你的微信小程序具有在线支付功能

    最近需要在微信小程序中用到在线支付功能,于是看了一下官方的文档,发现要在小程序里实现微信支付还是很方便的,如果你以前开发过服务号下的微信支付,那么你会发现其实小程序里的微信支付和服务号里的开发过程如出 ...

  4. 微信小程序如何实现支付功能?看官方文档头疼(使用云函数的方式操作)

    先来个效果图  ^_^ 微信支付功能,个人公众号是没有办法进行开发支付功能的,需要是使用非个人公众号进行注册(如:营业执照等,可以去淘宝购买一个也行 大概500左右) 公众平台的配置可以参考文档,这里 ...

  5. 微信小程序获取用户绑定手机号码完整版(转载)

    一.准备阶段 创建小程序项目(测试号即可) 创建Java后台项目(此处为SpringBoot 普通项目) 二.前端代码 WXML 代码 <!--index.wxml--> <view ...

  6. 微信小程序之快递查询(完整版)

    一.简介: 点击按钮查看快递信息.我也在网上找了很多快递查询的例子,但是它们都不是很详细.在代码上他们都没错,但是在配置上却缺少了一些东西,导致我们这些白嫖党并没有什么用,对于程序中出现的一些错误无法 ...

  7. 最新Zblog博客微信小程序源码全开源完整版+带教程

    正文: zbolg系统可用,使用教程请打开压缩包内的 使用教程.docx 查看 1.网站必须为zblog程序 2.网址必须备案 3.必须为https协议 4.zblog必须为伪静态 源码功能: 1.首 ...

  8. 微信小程序码的生成(JAVA完整版) 亲测可用

    JAVA生成小程序码(太阳码) 首先准备工具类,这里我使用的是QrUtil;废话不多说,上工具类; 工具类是获取token使用; appid = 小程序appID secret = 小程序秘钥 /** ...

  9. 微信小程序:小程序申请开通支付功能的步骤

    随着微信小程序越来越受欢迎,微信小程序的开发越来越火,尤其是初创型公司需要开发自己的小程序,就需要知道微信小程序开发的一套流程,而且商家在运营小程序的时候开通支付功能是必然选项,本章博文就来讲一下微信 ...

最新文章

  1. 青龙羊毛——果园合集(快手+抖音)(教程)
  2. Ogre 学习笔记 (二) 环境:阴影 、雾效、灯光
  3. mysql ereg_php中正则表达式匹配函数ereg是不是被弃用了?
  4. linux创建特殊文件rules,RHEL5 Oracle Linux 5上生成正确的udev rule 规则文件
  5. python实现逐步回归分析_Python实现逐步回归(stepwise regression)
  6. 【2016年第1期】专题导读:农业大数据
  7. ncurses初始化函数:raw(),cbreak(),echo(),noecho(),keypad(),halfdelay()
  8. 媲美Siri语音 英朗自然语音识别系统体验
  9. 谷歌正式推出在线云储存服务Google Drive
  10. Microsoft SQL Server 2008详细安装步骤
  11. Java中的getBytes()方法详解
  12. java找不到或无法加载主类
  13. like to do 和like doing的区别
  14. RAD Studio 10.4 for delphi XE Assigned和Nil的联系与区别
  15. OpenStack物理资源虚拟化比率设置
  16. IDEA实现单元测试
  17. discuz7.2帖子管理
  18. Adobe Photoshop CS5如何调出标尺和网格
  19. 阿里云盘 PC端 Aliyun-Drive-Desktop-Client
  20. 【面试题】大数据方向

热门文章

  1. 服务器信息列表下架关闭,龙王传说服务器关闭下架公告
  2. 一个移动互联网自媒体的运营手记
  3. w3 html table,HTML5 table 标签
  4. Apache网页优化 ---配置防盗链
  5. 融合正余弦和柯西变异的麻雀搜索算法
  6. 私有化DNS服务器搭建
  7. Linux系统内核介绍及Linux系统运行级别,uname,vmstat,top命令参数详解 和一些排查案例
  8. 【NI Multisim 14.0原理图的设计——简单电路设计】
  9. 哈尔滨理工大学第七届程序设计竞赛初赛 题集
  10. 笔记本电脑,充电器一拔立马关机,突然无法用电池怎么办