springboot 微信小程序 对接微信支付功能(完整版)
微信小程序对接微信支付功能
- 业务流程时序图
- 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. 创建相关的目录文件 如下
- 创建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 微信小程序 对接微信支付功能(完整版)相关推荐
- 微信小程序怎么开通支付功能?
对于一些想通过小程序卖货的企业商家来说,在申请小程序后需要同时开通微信支付功能,才能在小程序上进行交易.那么关于微信小程序怎么开通支付功能,下面给大家说一说. 一.注册非个人主体的小程序账号 不管你是 ...
- 微信小程序如何开通支付功能?
微信小程序商城支付方式有哪些,如何开通小程序商城的支付功能,商家开通小程序支付功能的条件有哪些,小程序支付和微商城支付有冲突吗,小程序商城支付方式下商家如何提现,手续费多少? 微信小程序商城支付方式 ...
- 让你的微信小程序具有在线支付功能
最近需要在微信小程序中用到在线支付功能,于是看了一下官方的文档,发现要在小程序里实现微信支付还是很方便的,如果你以前开发过服务号下的微信支付,那么你会发现其实小程序里的微信支付和服务号里的开发过程如出 ...
- 微信小程序如何实现支付功能?看官方文档头疼(使用云函数的方式操作)
先来个效果图 ^_^ 微信支付功能,个人公众号是没有办法进行开发支付功能的,需要是使用非个人公众号进行注册(如:营业执照等,可以去淘宝购买一个也行 大概500左右) 公众平台的配置可以参考文档,这里 ...
- 微信小程序获取用户绑定手机号码完整版(转载)
一.准备阶段 创建小程序项目(测试号即可) 创建Java后台项目(此处为SpringBoot 普通项目) 二.前端代码 WXML 代码 <!--index.wxml--> <view ...
- 微信小程序之快递查询(完整版)
一.简介: 点击按钮查看快递信息.我也在网上找了很多快递查询的例子,但是它们都不是很详细.在代码上他们都没错,但是在配置上却缺少了一些东西,导致我们这些白嫖党并没有什么用,对于程序中出现的一些错误无法 ...
- 最新Zblog博客微信小程序源码全开源完整版+带教程
正文: zbolg系统可用,使用教程请打开压缩包内的 使用教程.docx 查看 1.网站必须为zblog程序 2.网址必须备案 3.必须为https协议 4.zblog必须为伪静态 源码功能: 1.首 ...
- 微信小程序码的生成(JAVA完整版) 亲测可用
JAVA生成小程序码(太阳码) 首先准备工具类,这里我使用的是QrUtil;废话不多说,上工具类; 工具类是获取token使用; appid = 小程序appID secret = 小程序秘钥 /** ...
- 微信小程序:小程序申请开通支付功能的步骤
随着微信小程序越来越受欢迎,微信小程序的开发越来越火,尤其是初创型公司需要开发自己的小程序,就需要知道微信小程序开发的一套流程,而且商家在运营小程序的时候开通支付功能是必然选项,本章博文就来讲一下微信 ...
最新文章
- 青龙羊毛——果园合集(快手+抖音)(教程)
- Ogre 学习笔记 (二) 环境:阴影 、雾效、灯光
- mysql ereg_php中正则表达式匹配函数ereg是不是被弃用了?
- linux创建特殊文件rules,RHEL5 Oracle Linux 5上生成正确的udev rule 规则文件
- python实现逐步回归分析_Python实现逐步回归(stepwise regression)
- 【2016年第1期】专题导读:农业大数据
- ncurses初始化函数:raw(),cbreak(),echo(),noecho(),keypad(),halfdelay()
- 媲美Siri语音 英朗自然语音识别系统体验
- 谷歌正式推出在线云储存服务Google Drive
- Microsoft SQL Server 2008详细安装步骤
- Java中的getBytes()方法详解
- java找不到或无法加载主类
- like to do 和like doing的区别
- RAD Studio 10.4 for delphi XE Assigned和Nil的联系与区别
- OpenStack物理资源虚拟化比率设置
- IDEA实现单元测试
- discuz7.2帖子管理
- Adobe Photoshop CS5如何调出标尺和网格
- 阿里云盘 PC端 Aliyun-Drive-Desktop-Client
- 【面试题】大数据方向