springboot-微信小程序-对接微信支付功能完整版
目录
springboot 微信小程序 对接微信支付功能(完整版)
微信小程序对接微信支付功能
业务流程时序图
地址:
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;
}
/**
* 过滤字段:指定需要序列化的字段
*/
@JsonIgnore
private transient Map<Class<?>, Set<String>> includes;
/**
* 过滤字段:指定不需要序列化的字段
*/
@JsonIgnore
private transient Map<Class<?>, Set<String>> excludes;
public Render() {
}
public Render<T> include(Class<?> type, String... fields) {
return include(type, Arrays.asList(fields));
}
public Render<T> include(Class<?> type, Collection<String> fields) {
if (includes == null)
includes = new HashMap<>();
if (fields == null || fields.isEmpty()) return this;
fields.forEach(field -> {
if (field.contains(".")) {
String tmp[] = field.split("[.]", 2);
try {
Field field1 = type.getDeclaredField(tmp[0]);
if (field1 != null) {
include(field1.getType(), tmp[1]);
}
} catch (Throwable e) {
}
} else {
getStringListFromMap(includes, type).add(field);
}
});
return this;
}
public Render<T> exclude(Class type, Collection<String> fields) {
if (excludes == null)
excludes = new HashMap<>();
if (fields == null || fields.isEmpty()) return this;
fields.forEach(field -> {
if (field.contains(".")) {
String tmp[] = field.split("[.]", 2);
try {
Field field1 = type.getDeclaredField(tmp[0]);
if (field1 != null) {
exclude(field1.getType(), tmp[1]);
}
} catch (Throwable e) {
}
} else {
getStringListFromMap(excludes, type).add(field);
}
});
return this;
}
public Render<T> exclude(Collection<String> fields) {
if (excludes == null)
excludes = new HashMap<>();
if (fields == null || fields.isEmpty()) return this;
Class type;
if (getData() != null) type = getData().getClass();
else return this;
exclude(type, fields);
return this;
}
public Render<T> include(Collection<String> fields) {
if (includes == null)
includes = new HashMap<>();
if (fields == null || fields.isEmpty()) return this;
Class type;
if (getData() != null) type = getData().getClass();
else return this;
include(type, fields);
return this;
}
public Render<T> exclude(Class type, String... fields) {
return exclude(type, Arrays.asList(fields));
}
public Render<T> exclude(String... fields) {
return exclude(Arrays.asList(fields));
}
public Render<T> include(String... fields) {
return include(Arrays.asList(fields));
}
protected Set<String> getStringListFromMap(Map<Class<?>, Set<String>> map, Class type) {
return map.computeIfAbsent(type, k -> new HashSet<>());
}
@Override
public String toString() {
return JSON.toJSONStringWithDateFormat(this, "yyyy-MM-dd HH:mm:ss");
}
public Map<Class<?>, Set<String>> getExcludes() {
return excludes;
}
public Map<Class<?>, Set<String>> getIncludes() {
return includes;
}
}
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 */
@Deprecated
public static String readIncommingRequestData(HttpServletRequest request) {
return readData(request);
}
private class TrustAnyTrustManager implements X509TrustManager {
private TrustAnyTrustManager() {
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
}
private class TrustAnyHostnameVerifier implements HostnameVerifier {
private TrustAnyHostnameVerifier() {
}
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
}
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_1
private static String unifiedOrderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 交易类型枚举
* WAP的文档:https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_1
* @author L.cm
* <pre>
* email: 596392912@qq.com
* site: http://www.dreamlu.net
* date: 2015年10月27日 下午9:46:27
* </pre>
*/
public enum TradeType {
JSAPI, NATIVE, APP, WAP, MWEB
}
/**
* 统一下单
* @param params 参数map
* @return String
*/
public static String pushOrder(Map<String, String> params) {
return HttpKit.post(unifiedOrderUrl, PaymentKit.toXml(params));
}
private static Map<String, String> request(String url, Map<String, String> params, String paternerKey) {
params.put("nonce_str", System.currentTimeMillis() + "");
String sign = PaymentKit.createSign(params, paternerKey);
params.put("sign", sign);
String xmlStr = HttpKit.post(url, PaymentKit.toXml(params));
return PaymentKit.xmlToMap(xmlStr);
}
/**
* 文档说明:https://pay.weixin.qq.com/wiki/doc/api/wap.php?chapter=15_4
* <pre>
* @param appId 公众账号ID 是 String(32) wx8888888888888888 微信分配的公众账号ID
* 随机字符串 noncestr 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法
* 订单详情扩展字符串 package 是 String(32) WAP 扩展字段,固定填写WAP
* @param prepayId 预支付交易会话标识 是 String(64) wx201410272009395522657a690389285100 微信统一下单接口返回的预支付回话标识,用于后续接口调用中使用,该值有效期为2小时
* 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法
* 时间戳 timestamp 是 String(32) 1414561699 当前的时间,其他详见时间戳规则
* @param paternerKey 签名密匙
* </pre>
* @return {String}
*/
public static String getDeepLink(String appId, String prepayId, String paternerKey) {
Map<String, String> params = new HashMap<String, String>();
params.put("appid", appId);
params.put("noncestr", System.currentTimeMillis() + "");
params.put("package", "WAP");
params.put("prepayid", prepayId);
params.put("timestamp", System.currentTimeMillis() / 1000 + "");
String sign = PaymentKit.createSign(params, paternerKey);
params.put("sign", sign);
String string1 = PaymentKit.packageSign(params, true);
String string2 = "";
try { string2 = PaymentKit.urlEncode(string1); } catch (UnsupportedEncodingException e) {}
return "weixin://wap/pay?" + string2;
}
// 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2
private static String orderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
/**
* 根据商户订单号查询信息
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 商户密钥
* @param transaction_id 微信订单号
* @return 回调信息
*/
public static Map<String, String> queryByTransactionId(String appid, String mch_id, String paternerKey, String transaction_id) {
Map<String, String> params = new HashMap<String, String>();
params.put("appid", appid);
params.put("mch_id", mch_id);
params.put("transaction_id", transaction_id);
return request(orderQueryUrl, params, paternerKey);
}
/**
* 根据商户订单号查询信息
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 商户密钥
* @param out_trade_no 商户订单号
* @return 回调信息
*/
public static Map<String, String> queryByOutTradeNo(String appid, String mch_id, String paternerKey, String out_trade_no) {
Map<String, String> params = new HashMap<String, String>();
params.put("appid", appid);
params.put("mch_id", mch_id);
params.put("out_trade_no", out_trade_no);
return request(orderQueryUrl, params, paternerKey);
}
// 文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_3
private static String closeOrderUrl = "https://api.mch.weixin.qq.com/pay/closeorder";
/**
* 关闭订单
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 商户密钥
* @param out_trade_no 商户订单号
* @return 回调信息
*/
public static Map<String, String> closeOrder(String appid, String mch_id, String paternerKey, String out_trade_no) {
Map<String, String> params = new HashMap<String, String>();
params.put("appid", appid);
params.put("mch_id", mch_id);
params.put("out_trade_no", out_trade_no);
return request(closeOrderUrl, params, paternerKey);
}
// 申请退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
public static String refundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";
// 查询退款文档地址:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
private static String refundQueryUrl = "https://api.mch.weixin.qq.com/pay/refundquery";
private static Map<String, String> baseRefundQuery(Map<String, String> params, String appid, String mch_id, String paternerKey) {
params.put("appid", appid);
params.put("mch_id", mch_id);
return request(refundQueryUrl, params, paternerKey);
}
/**
* 根据微信订单号查询退款
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 商户密钥
* @param transaction_id 微信订单号
* @return map
*/
public static Map<String, String> refundQueryByTransactionId(String appid, String mch_id, String paternerKey, String transaction_id) {
Map<String, String> params = new HashMap<String, String>();
params.put("transaction_id", transaction_id);
return baseRefundQuery(params, appid, mch_id, paternerKey);
}
/**
* 根据微信订单号查询退款
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 商户密钥
* @param out_trade_no 商户订单号
* @return map
*/
public static Map<String, String> refundQueryByOutTradeNo(String appid, String mch_id, String paternerKey, String out_trade_no) {
Map<String, String> params = new HashMap<String, String>();
params.put("out_trade_no", out_trade_no);
return baseRefundQuery(params, appid, mch_id, paternerKey);
}
/**
* 根据微信订单号查询退款
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 商户密钥
* @param out_refund_no 商户退款单号
* @return map
*/
public static Map<String, String> refundQueryByOutRefundNo(String appid, String mch_id, String paternerKey, String out_refund_no) {
Map<String, String> params = new HashMap<String, String>();
params.put("out_refund_no", out_refund_no);
return baseRefundQuery(params, appid, mch_id, paternerKey);
}
/**
* 根据微信订单号查询退款
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 商户密钥
* @param refund_id 微信退款单号
* @return map
*/
public static Map<String, String> refundQueryByRefundId(String appid, String mch_id, String paternerKey, String refund_id) {
Map<String, String> params = new HashMap<String, String>();
params.put("refund_id", refund_id);
return baseRefundQuery(params, appid, mch_id, paternerKey);
}
private static String downloadBillUrl = "https://api.mch.weixin.qq.com/pay/downloadbill";
/**
* <pre>
* ALL,返回当日所有订单信息,默认值
* SUCCESS,返回当日成功支付的订单
* REFUND,返回当日退款订单
* REVOKED,已撤销的订单
* </pre>
*/
public static enum BillType {
ALL, SUCCESS, REFUND, REVOKED
}
/**
* 下载对账单
* <pre>
* 公众账号ID appid 是 String(32) wx8888888888888888 微信分配的公众账号ID(企业号corpid即为此appId)
* 商户号 mch_id 是 String(32) 1900000109 微信支付分配的商户号
* 设备号 device_info 否 String(32) 013467007045764 微信支付分配的终端设备号
* 随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法
* 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法
* 对账单日期 bill_date 是 String(8) 20140603 下载对账单的日期,格式:20140603
* 账单类型 bill_type 否 String(8)
* </pre>
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 签名密匙
* @param billDate 对账单日期
* @return String
*/
public static String downloadBill(String appid, String mch_id, String paternerKey, String billDate) {
return downloadBill(appid, mch_id, paternerKey, billDate, null);
}
/**
* 下载对账单
* <pre>
* 公众账号ID appid 是 String(32) wx8888888888888888 微信分配的公众账号ID(企业号corpid即为此appId)
* 商户号 mch_id 是 String(32) 1900000109 微信支付分配的商户号
* 设备号 device_info 否 String(32) 013467007045764 微信支付分配的终端设备号
* 随机字符串 nonce_str 是 String(32) 5K8264ILTKCH16CQ2502SI8ZNMTM67VS 随机字符串,不长于32位。推荐随机数生成算法
* 签名 sign 是 String(32) C380BEC2BFD727A4B6845133519F3AD6 签名,详见签名生成算法
* 对账单日期 bill_date 是 String(8) 20140603 下载对账单的日期,格式:20140603
* 账单类型 bill_type 否 String(8)
* </pre>
* @param appid 公众账号ID
* @param mch_id 商户号
* @param paternerKey 签名密匙
* @param billDate 对账单日期
* @param billType 账单类型
* @return String
*/
public static String downloadBill(String appid, String mch_id, String paternerKey, String billDate, BillType billType) {
Map<String, String> params = new HashMap<String, String>();
params.put("appid", appid);
params.put("mch_id", mch_id);
params.put("nonce_str", System.currentTimeMillis() + "");
params.put("bill_date", billDate);
if (null != billType) {
params.put("bill_type", billType.name());
} else {
params.put("bill_type", BillType.ALL.name());
}
String sign = PaymentKit.createSign(params, paternerKey);
params.put("sign", sign);
return HttpKit.post(downloadBillUrl, PaymentKit.toXml(params));
}
}
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) {
// 生成签名前先去除sign
params.remove("sign");
String stringA = packageSign(params, false);
String stringSignTemp = stringA + "&key=" + paternerKey;
return Tools.md5(stringSignTemp).toUpperCase();
}
/**
* 支付异步通知时校验sign
* @param params 参数
* @param paternerKey 支付密钥
* @return {boolean}
*/
public static boolean verifyNotify(Map<String, String> params, String paternerKey){
String sign = params.get("sign");
String localSign = PaymentKit.createSign(params, paternerKey);
return sign.equals(localSign);
}
/**
* 微信下单,map to xml
* @param params 参数
* @return String
*/
public static String toXml(Map<String, String> params) {
StringBuilder xml = new StringBuilder();
xml.append("<xml>");
for (Entry<String, String> entry : params.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
// 略过空值
if (Tools.isEmpty(value)) continue;
xml.append("<").append(key).append(">");
xml.append(entry.getValue());
xml.append("</").append(key).append(">");
}
xml.append("</xml>");
return xml.toString();
}
/**
* 针对支付的xml,没有嵌套节点的简单处理
* @param xmlStr xml字符串
* @return map集合
*/
public static Map<String, String> xmlToMap(String xmlStr) {
XmlHelper xmlHelper = XmlHelper.of(xmlStr);
return xmlHelper.toMap();
}
}
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";
// sandbox
public static final String SANDBOX_MICROPAY_URL_SUFFIX = "/sandboxnew/pay/micropay";
public static final String SANDBOX_UNIFIEDORDER_URL_SUFFIX = "/sandboxnew/pay/unifiedorder";
public static final String SANDBOX_ORDERQUERY_URL_SUFFIX = "/sandboxnew/pay/orderquery";
public static final String SANDBOX_REVERSE_URL_SUFFIX = "/sandboxnew/secapi/pay/reverse";
public static final String SANDBOX_CLOSEORDER_URL_SUFFIX = "/sandboxnew/pay/closeorder";
public static final String SANDBOX_REFUND_URL_SUFFIX = "/sandboxnew/secapi/pay/refund";
public static final String SANDBOX_REFUNDQUERY_URL_SUFFIX = "/sandboxnew/pay/refundquery";
public static final String SANDBOX_DOWNLOADBILL_URL_SUFFIX = "/sandboxnew/pay/downloadbill";
public static final String SANDBOX_REPORT_URL_SUFFIX = "/sandboxnew/payitil/report";
public static final String SANDBOX_SHORTURL_URL_SUFFIX = "/sandboxnew/tools/shorturl";
public static final String SANDBOX_AUTHCODETOOPENID_URL_SUFFIX = "/sandboxnew/tools/authcodetoopenid";
}
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 {
@Autowired
private PayService payService;
@ApiOperation(value = "创建充值订单", notes = "创建充值订单")
@PostMapping
public Object order(@RequestBody RechargeDto rechargeDto) throws Exception {
/**
微信小程序支付
*/
//获取code
String code = rechargeDto.getCode();
//调用接口获取openId
String openId = AppletPayUtil.getOpenIdByCode(code);
/*
生成订单....,这里只创建了订单号
*/
//订单号 uuid
String outTradeNo= IdGen.uuid();
return Render.ok(payService.unifiedOrder(outTradeNo,rechargeDto.getRechargeMoney(),openId));
}
}
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<>();
//微信分配的小程序ID
reqParams.put("appid", WxProgramPayConfig.APPID);
//微信支付分配的商户号
reqParams.put("mch_id", WxProgramPayConfig.MCH_ID);
//随机字符串
reqParams.put("nonce_str", System.currentTimeMillis() / 1000 + "");
//签名类型
reqParams.put("sign_type", "MD5");
//充值订单 商品描述
reqParams.put("body", projectName + "-充值订单-微信小程序");
//商户订单号
reqParams.put("out_trade_no", outTradeNo);
//订单总金额,单位为分
reqParams.put("total_fee", money.multiply(BigDecimal.valueOf(100)).intValue() + "");
//终端IP
reqParams.put("spbill_create_ip", "127.0.0.1");
//通知地址
reqParams.put("notify_url", WxProgramPayConfig.NOTIFY_URL);
//交易类型
reqParams.put("trade_type", "JSAPI");
//用户标识
reqParams.put("openid", openid);
//签名
String sign = WXPayUtil.generateSignature(reqParams, WxProgramPayConfig.KEY);
reqParams.put("sign", sign);
/*
调用支付定义下单API,返回预付单信息 prepay_id
*/
String xmlResult = PaymentApi.pushOrder(reqParams);
log.info(xmlResult);
Map<String, String> result = PaymentKit.xmlToMap(xmlResult);
//预付单信息
String prepay_id = result.get("prepay_id");
/*
小程序调起支付数据签名
*/
Map<String, String> packageParams = new HashMap<String, String>();
packageParams.put("appId", WxProgramPayConfig.APPID);
packageParams.put("timeStamp", System.currentTimeMillis() / 1000 + "");
packageParams.put("nonceStr", System.currentTimeMillis() + "");
packageParams.put("package", "prepay_id=" + prepay_id);
packageParams.put("signType", "MD5");
String packageSign = WXPayUtil.generateSignature(packageParams, WxProgramPayConfig.KEY);
packageParams.put("paySign", packageSign);
return packageParams;
}
}
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);
//********************************************* 小测试************************************//
//小程序调用登录接口 获取code
wx.login({
success(res) {
if (res.code) {
var paramsData = {
"payType": "3",
"rechargeMoney": that.data.mealList[that.data.num].label,
//用户id 请调用相应接口 本地只作测试处理
"userId": "c39993421bca559c99736a67bb29c526",
"code": res.code
}
//小程序请求服务器接口 start
wx.request({
url: con.user_recharge_re,
method: "post",
data: paramsData,
header: {
'content-type': 'application/json'
},
success: function(res) {
//小程序调用 支付API start
wx.requestPayment({
timeStamp: res.data.data.timeStamp,
nonceStr: res.data.data.nonceStr,
package: res.data.data.package,
signType: res.data.data.signType,
paySign: res.data.data.paySign,
success: function(res) {
console.log(res);
},
fail: function(res) {
console.log(res);
},
complete: function(res) {
console.log(res);
}
})
//小程序调用 支付API end
}
})
//小程序请求服务器接口 end
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
//********************************************* 小测试************************************//
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function() {
}
})
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文档:小程序登录
wx.login({
success(res) {
if (res.code) {
console.log("临时登录凭证 code" + res.code)
} else {
console.log('登录失败!' + res.errMsg)
}
}
})
6.2小程序调用支付API
小程序调起支付API:
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);
}
})
参考链接
名称 | 链接 |
---|---|
微信小程序下订单 | |
小程序调取支付API | |
HTTPS服务器配置 | |
小程序API文档 | |