基于springboot实现小程序微信支付与退款
最近需要再写小程序商城,无可避免的需要用到微信支付与商品售后退款等功能。于是研究了一些大佬的代码之后整合出了这个比较简单的微信支付与退款。
相关内容引用了以下两位大佬的。
https://blog.csdn.net/Majker/article/details/88379695
https://blog.csdn.net/wenqiwenqi123/article/details/78734405
先看看微信支付的流程,然后直接上代码测试。
先说明一些问题,安全证书仅仅是作为退款才有作用,如果仅仅是测试微信支付或者还未申请到安全证书的话,大可将构造器代码注释掉。
1.相关pom文件如下:
<!--wx 登录-->
<!-- http请求工具包依赖 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<!--wx支付-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
2.配置类如下
1.HttpKit
package cn.gdpu.config.pay;
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.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Iterator;
import java.util.Map;
/**
* @ClassName HttpKit
* @Author ttaurus
* @Date Create in 2020/1/21 18:18
*/
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;
}
}
}
2.PayConfig 这个类尤其重要 里面的参数必须都要具备 且不能错误 需要手动填写
```java
package cn.gdpu.config.pay;
import com.github.wxpay.sdk.WXPayConfig;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
/**
* @ClassName WXPayConfig
* @Author ttaurus
* @Date Create in 2020/1/21 11:22
*/
public class PayConfig implements WXPayConfig{
private byte[] certData;
/**
* 微信退款所需要的配置! 退款只需要证书即可。
* @throws Exception
*/
public PayConfig() throws Exception {
//部署服务器用到的路径 绝对路径。
//String certPath = "/usr/local/app/tomcat-dev/webapps/shop/WEB-INF/classes/apiclient_cert.p12";//从微信商户平台下载的安全证书存放的目录
//本地用到的路径 相对路径
String certPath = "src/main/resources/apiclient_cert.p12";
File file = new File(certPath);
InputStream certStream = new FileInputStream(file);
this.certData = new byte[(int) file.length()];
certStream.read(this.certData);
certStream.close();
}
@Override
public String getAppID() {
return ""; //appid
}
public String getAPPSECRET(){
return ""; //appSecret
}
@Override
public String getMchID() {
return ""; //商户号id
}
@Override
public String getKey() {
return ""; //支付API密钥
}
public String getNOTIFY_URL(){
/*
支付回调URL 必须在https下访问。这一步无法在本地实现 必须设置在服务器上
此url只是作为一个样例。
*/
return "https://www.testtest.design21/dev/user/pay/notify";
}
@Override
public InputStream getCertStream() {
ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
return certBis;
}
@Override
public int getHttpConnectTimeoutMs() {
return 8000;
}
@Override
public int getHttpReadTimeoutMs() {
return 10000;
}
}
3.PaymentApi 工具类 直接复制即可
package cn.gdpu.config.pay;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName PaymentApi
* @Author ttaurus
* @Date Create in 2020/1/21 17:34
*/
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";
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.PaymentKit 工具类 可不管直接复制即可
package cn.gdpu.config.pay;
import org.apache.commons.codec.Charsets;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.TreeMap;
/**
* @ClassName PaymentKit
* @Author ttaurus
* @Date Create in 2020/1/21 18:12
*/
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 (Map.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 (Map.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();
}
}
5.Tools 工具类 直接复制即可
package cn.gdpu.config.pay;
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;
/**
* @ClassName Tools
* @Author ttaurus
* @Date Create in 2020/1/21 18:13
*/
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;
}
}
6.XmlHelper 工具类 直接复制即可
package cn.gdpu.config.pay;
import org.apache.tomcat.util.http.fileupload.IOUtils;
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;
/**
* @ClassName XmlHelper
* @Author ttaurus
* @Date Create in 2020/1/21 18:23
*/
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();
}
}
7.Msg工具类 可直接复制
package cn.gdpu.util;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Msg {
public static Object empty(){
Map<String, Object> map = new TreeMap<>();
map.put("code", 0);
map.put("count", 0);
map.put("data", new ArrayList());
map.put("msg", "搜索结果为空");
return JSON.toJSON(map);
}
public static Object ok(){
JSONObject json = new JSONObject();
json.put("msg", "ok");
return JSON.toJSON(json);
}
public static Object fail(){
JSONObject json = new JSONObject();
json.put("msg", "fail");
return JSON.toJSON(json);
}
public static Object msg(String type, String msg){
Map<String, String> result = new HashMap<>();
result.put(type, msg);
return JSON.toJSON(result);
}
//未登录时返回code -1
public static void unlogin(HttpServletResponse response){
try {
Map<String, Integer> result = new HashMap<>();
result.put("code", Integer.valueOf(-1));
String jsonStr = JSON.toJSONString(result);
response.setContentType("application/json; charset=utf-8");
response.getWriter().print(jsonStr);
} catch (IOException e) {
e.printStackTrace();
}
}
public static Object msg(Object msg){
JSONObject json = new JSONObject();
json.put("msg", msg);
return JSON.toJSON(json);
}
}
8.MD5Util 可直接复制
package cn.gdpu.util;
import cn.gdpu.config.pay.PayConfig;
import com.github.wxpay.sdk.WXPayConstants;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
/**
* @ClassName MD5Util
* @Author ttaurus
* @Date Create in 2020/1/21 13:56
*/
public class MD5Util{
public static String getSign(Map<String,String> data) throws Exception{
PayConfig config = new PayConfig();
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(config.getKey());
MessageDigest md = null;
try{
md = MessageDigest.getInstance("MD5");
}catch(NoSuchAlgorithmException e){
e.printStackTrace();
}
byte[] array = new byte[0];
array = md.digest(sb.toString().getBytes(StandardCharsets.UTF_8));
StringBuilder sb2 = new StringBuilder();
for(byte item : array){
sb2.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1 , 3));
}
System.out.println("签名为:"+sb2);
return sb2.toString().toUpperCase();
}
}
3.Controller配置
- UserWXPayController
package cn.gdpu.userController;
import cn.gdpu.bean.GoodSpecificationDetail;
import cn.gdpu.bean.Order;
import cn.gdpu.bean.OrderDetail;
import cn.gdpu.bean.OrderVO;
import cn.gdpu.config.pay.PayConfig;
import cn.gdpu.config.pay.PaymentApi;
import cn.gdpu.config.pay.PaymentKit;
import cn.gdpu.mapper.GoodDao;
import cn.gdpu.mapper.OrderDao;
import cn.gdpu.mapper.OrderDetailDao;
import cn.gdpu.mapper.UserDao;
import cn.gdpu.util.MD5Util;
import cn.gdpu.util.Msg;
import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName WXPayController
* @Author ttaurus
* @Date Create in 2020/1/21 11:24
*/
@RestController
@RequestMapping("/user/pay")
public class UserWXPayController {
private static Logger logger = LoggerFactory.getLogger(UserWXPayController.class);//可注释
@PostMapping(value = "/notify") //回调函数
public String payNotify(HttpServletRequest request) {
InputStream is = null;
String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";
try {
is = request.getInputStream();
// 将InputStream转换成String
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
xmlBack = notify(request,sb.toString()); //调用通知方法
//System.out.println("返回的信息如下:\n"+sb.toString());
//System.out.println("已成功支付且回复到微信服务器!");
} catch (Exception e) {
logger.error("微信手机支付回调通知失败:", e);
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
//System.out.println("已成功支付且回复到微信服务器!");
return xmlBack;
}
/**
* 功能描述: <微信小程序 支付>
*
* @param orderNumber 商户订单号
* @param money 订单总金额(分)
* @param openid 微信用户的openId
* @return
* @throws Exception
*/
@PostMapping("/prepay")
public Object genWxProgramPayChannel(HttpServletRequest request,String orderNumber, BigDecimal money, String openid) throws Exception {
PayConfig config = new PayConfig();
Map<String, String> reqParams = new HashMap<>();
//微信分配的小程序ID
reqParams.put("appid", config.getAppID());
//微信支付分配的商户号
reqParams.put("mch_id", config.getMchID());
//随机字符串
reqParams.put("nonce_str", System.currentTimeMillis() / 1000 + "");
//签名类型
reqParams.put("sign_type", "MD5");
//充值订单 商品描述
reqParams.put("body", "TestTest-充值订单-微信小程序");
//商户订单号
reqParams.put("out_trade_no", orderNumber);
//订单总金额,单位为分
reqParams.put("total_fee",money.multiply(BigDecimal.valueOf(100)).intValue()+"");
//终端IP
reqParams.put("spbill_create_ip", "49.233.83.97");
//通知地址
reqParams.put("notify_url", config.getNOTIFY_URL());
//交易类型
reqParams.put("trade_type", "JSAPI");
//用户标识
reqParams.put("openid" , openid);
//签名
String sign = WXPayUtil.generateSignature(reqParams, config.getKey());
//request.getSession().setAttribute(orderNumber,sign);
reqParams.put("sign", sign);
/*
调用支付定义下单API,返回预付单信息 prepay_id
*/
String xmlResult = PaymentApi.pushOrder(reqParams);
logger.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", config.getAppID());
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, config.getKey());
packageParams.put("paySign", packageSign);
return packageParams;
}
public String notify(HttpServletRequest request,String notifyStr) {
String xmlBack = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[报文为空]]></return_msg></xml> ";
try {
// 转换成map
Map<String, String> resultMap = WXPayUtil.xmlToMap(notifyStr);
WXPay wxpayApp = new WXPay(new PayConfig());
OrderVO orderVO = new OrderVO();
if (wxpayApp.isPayResultNotifySignatureValid(resultMap)) {
String returnCode = resultMap.get("return_code"); //状态
String transactionId = resultMap.get("transaction_id");
System.out.println(transactionId);
if (returnCode.equals("SUCCESS")) {
//if (StringUtils.isNotBlank(orderNumber)) {
/**
* 业务代码在此处!
* 更新订单状态等等
*/
xmlBack = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
//}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return xmlBack;
}
}
2. 退款Controller 可本地调用
package cn.gdpu.controller;
import cn.gdpu.bean.*;
import cn.gdpu.config.pay.PayConfig;
import cn.gdpu.mapper.*;
import cn.gdpu.util.MD5Util;
import cn.gdpu.util.Msg;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;
/**
*
*/
@RestController
@RequestMapping("/admin")
public class AdminController {
private static Logger logger = LoggerFactory.getLogger(AdminController.class); //可注释
/**
* 功能描述: <微信小程序 退款>
* 退款操作
* @return
* @throws Exception
*/
@PostMapping("refund")
public Object refundByAdmin(HttpServletRequest request,
@RequestParam("transaction_id")String transaction_id,
@RequestParam("total_fee")String total_fee) throws Exception{
if(refund(transaction_id,total_fee)){
/**
* 业务代码
*/
return Msg.ok(); //消息通知工具类
}else{
return Msg.msg("出现问题,退款失败!");
}
}
public boolean refund(String transaction_id,
String total_fee) throws Exception{
Map<String,String> data = new HashMap<String,String>();
System.err.println("进入微信退款申请");
Date now = new Date();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//可以方便地修改日期格式
String hehe = dateFormat.format(now);
int pay = (int)(Double.parseDouble(total_fee) * 100); //需要*100
String out_refund_no = hehe + "wxrefund";
//String transaction_id = "微信支付时生成的流水号";
//String total_fee = "微信支付时支付的钱(单位为分)";
data.put("out_refund_no" , out_refund_no);
data.put("transaction_id" , transaction_id);
data.put("total_fee" , String.valueOf(pay));
data.put("refund_fee" , String.valueOf(pay));
PayConfig config = new PayConfig();
WXPay wxpay = new WXPay(config);
data.put("appid" , config.getAppID());
data.put("mch_id" , config.getMchID());
data.put("nonce_str" , WXPayUtil.generateNonceStr());
data.put("sign" , MD5Util.getSign(data));
Map<String,String> resp = null;
try{
resp = wxpay.refund(data);
}catch(Exception e){
e.printStackTrace();
}
System.err.println("返回信息:\n" + resp);
String return_code = resp.get("return_code"); //返回状态码
String return_msg = resp.get("return_msg"); //返回信息
String resultReturn = null;
if("SUCCESS".equals(return_code)){
String result_code = resp.get("result_code"); //业务结果
String err_code_des = resp.get("err_code_des"); //错误代码描述
if("SUCCESS".equals(result_code)){
//表示退款申请接受成功,结果通过退款查询接口查询
//修改用户订单状态为退款申请中(暂时未写)
resultReturn = "退款申请成功";
System.out.println(resultReturn);
return true;
}else{
logger.info("订单号:{}错误信息:{}" , err_code_des);
resultReturn = err_code_des;
return false;
}
}else{
logger.info("订单号:{}错误信息:{}" , return_msg);
resultReturn = return_msg;
return false;
}
}
}
最后一步就是微信小程序里调用接口即可。