可以先看一下下面的IndexController 先理解一下需要用到哪些参数然后再从头开始准备就不会一头蒙了
完整代码:
链接: https://pan.quark.cn/s/518e02b22e4f
提取码:J6f2
可能遇到的坑:
微信支付这个的文档可以说跟没有是一样的 只写入参回参 加密解密搞得人一头雾水 网上找的大多数的都是用的别人封装好的接口 我是觉得一个是怕不安全 另一个是直接调别人写好的出了啥bug也不好改
最大的坑其实就是签名失败我的一次报签名失败的时候搞了三天 网上翻了个遍 代码试了个遍 最后发现是密钥用的不对 我的代码需要用的是V2的密钥 因为在我来公司之前公司做过微信支付的项目 当时主要怕重置了密钥会影响其他项目 所以就没有去重置 直接在之前的项目里面找了一下密钥拿上就去用了,结果就是 一个BUG卡了两三天
好 BUG把我心态搞崩了之后我抱着试试的心理我说我改一下密钥看看吧 结果改完密钥之后依然报错,报签名错误。好好好 又去网上翻代码 翻翻翻 改改改 又是两三天 然后突然在一篇博客里看到博主跟我一样 报签名错误 结果是因为密钥不能纯大写 也不能纯小写 因为微信官方文档写的是 包含 大写!小写!数字! 好家伙 咱以为人家是建议呢 没想到是硬性规定! 看完之后我赶紧又去换密钥 换完之后果然不报签名错误了 不过也仅仅是第一次签名的时候不报错了
紧接着就来到了第三次报错签名失败 这次报签名失败不是后端报错 后端签名后请求微信 请求参数和返回参数都没问题 已经拿到了timeStamp nonceStr package signType这些需要返回给前端的参数了 到了微信开发工具里都生成出了付款二维码 竟然是在扫码之后报签名失败的错!!!! 好好好 又开始换代码 换换换 换到最后 发现我在第二次签名的时候 在map里多加了一个在第一次签名时候生成的当前订单号 去掉之后立马不报错签名失败了!!
签名失败报完之后 到了回调 回调又开始报错 支付成功之后发现微信一直在调回调 搞了半天才知道 解析之后还要返回给微信String格式的xml 不然微信就会一直调用回调方法
后端的坑走完了之后还有前端的坑 最严重的时候我自己写了个uniapp的小程序才找到问题!!!!
第一次因为前端报错是因为 他拿code来我这获取openid 这能有什么错呢 这有错! 微信规定一个code只能用一次 然后哥们每次进来的时候都报错code有问题 最后最后还了各种代码之后发现url拼的有问题!!! 一定要用这个地址!! 后面我写uniapp的时候还发现了前端直接获取openid的方法
String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + WxPayConfig.APPID + "&secret=" + WxPayConfig.APPSECRET + "&js_code=" + code + "&grant_type=authorization_code";
uni.login({
provider: 'weixin',
success: res => {
uni.request({
url: 'https://api.weixin.qq.com/sns/jscode2session', // 请求微信服务器
method: 'GET',
data: {
appid: 'XXXXXXXXX', //你的小程序的APPID
secret: 'XXXXXXXXX', //你的小程序秘钥secret,
js_code: res.code, //wx.login 登录成功后的code
grant_type: 'authorization_code'
},
success: (res) => {
console.log('获取信息', res.data); // 换取成功后 暂存这些数据 留作后续操作
res.data.openid //openid 用户唯一标识
res.data.session_key //session_key 会话密钥
}
});
}
});
第二次因为前端报错是获取用户信息 还是给我code让我去获取用户头像和名字还有手机号和openid我又是换了各种代码 最后发现 人家微信把获取用户信息给停用了 改成了按钮获取 头像和名字 直接在前端获取的! 具体我在我的uniapp小程序里都有写
第三次因为前端报错还是获取手机号 报这个错的时候觉得我被玩了 获取手机号的时候我代码没问题 但是就是怎么都获取不到 一直报错 最后我说我TM自己写个uniapp我试试 我就不信了 结果 结果 结果我写了之后发现一点问题没有 最后发现是前端拿着获取open的code给的我让我获取手机号 但是其实两个code是不一样的 不能互用的 好好好 我给前端反馈回去这个问题 然后! 重点来了 ! 哥们跟我说,我知道呀 是的呀 是这样的呀 无语呀!!!!!
最后一次因为前端报错是个小问题 第一个做支付的项目的时候我的接口是让前端直接传金额进来的 然后 微信传金额的时候需要以分为单位 但是他给我传的不对 然后一直报错
下面就是我的uniapp的源码 配合后端项目的话多看看debug 很快就能理解代码了
链接: https://pan.quark.cn/s/49059110c037
提取码:2Sqv
前期准备
登录已认证的小程序并开通微信支付
在微信支付-产品中心-我的产品中开通JSAPI支付并同时配置JSAPI支付支付授权目录 配置成自己服务器的域名 后面部署之后会用到 本地测试的话回调需要用内网穿透 我用的是Natapp 很简单
在微信支付中下载并配置商户证书 证书在退款的时候会用到
链接: https://pay.weixin.qq.com/docs/merchant/development/development-preparation/download-configure-merchant-certificates.html
还需要设置V2密钥!注意!注意!注意!去生成一个包含大写 小写 数字的32位随机数
参照:
Kc9f924v44bXOce8Ta32U90O43d620RN
heVvwN9g5HoTBubDRoldzUgeKMmyfaRI
也可以直接拿去用 无所谓的 V2 V3最好设置成一样的 省的后面忘了
如果是小程序的话基本不需要做什么配置 保存AppID和AppSecret 用于获取AccessToken 部署的时候需要配置一下服务器域名
pom.xml中
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.39</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.15</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
获取到所有需要的参数之后放在一个配置类里 WxPayConfig:
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.song.wxpay.controller.IndexController;
import com.song.wxpay.util.ip.AddressUtils;
import com.song.wxpay.util.ip.IpUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.SSLContext;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.*;
public class WxPayConfig {
public static final String APPID = "你的小程序APPID";
//小程序秘钥
public static final String APPSECRET = "你的APPSECRET";
//商户号
public static String weChatPayMchId = "微信支付的商户号";
//商户秘钥 商户号里你自己设置的32位密码
public static String weChatV2PayKey = "Kc9f924v44bXOce8Ta32U90O43d620RN";
//商户秘钥 商户号里你自己设置的32位密码
public static String weChatV3PayKey = "heVvwN9g5HoTBubDRoldzUgeKMmyfaRI";
public static String weChatPayKeyPath = "apiclient_key.pem";
public static String weChatPayP12Path = "apiclient_cert.p12";
//获取用户手机号url
public static String weChatGetPhoneUrl = "https://api.weixin.qq.com/wxa/business/getuserphonenumber";
//支付回调地址 可以使用内网穿透也可以部署到服务器后用服务器的真实域名加地址
public static String weChatPayNotifyUrl = "http://3dm924.natappfree.cc/weChatPayNotify";
//退款回调地址 可以使用内网穿透也可以部署到服务器后用服务器的真实域名加地址
public static String weChatRefundNotifyUrl = "http://3dm924.natappfree.cc/weChatRefundNotify";
public static String weChatAuthorizeUrl = "https://open.weixin.qq.com/connect/oauth2/authorize";
public static String weChatAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token";
public static String weChatUserinfoUrl = "https://api.weixin.qq.com/sns/userinfo";
public static String weChatPayUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
public static String weChatRefundUrl = "https://api.mch.weixin.qq.com/secapi/pay/refund";
public static String weChatPayOrderQueryUrl = "https://api.mch.weixin.qq.com/pay/orderquery";
/**
* 获取微信Jsapi的accessToken
*/
public static String getAccessToken() throws IOException {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
url = url.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
String result = HttpUtils.sendGet(url);
JSONObject jsonObject = JSON.parseObject(result);
String accessToken = jsonObject.getString("access_token");
return accessToken;
}
public static String getPhone(String code) throws IOException {
String accessToken = getAccessToken();
String url = weChatGetPhoneUrl + "?access_token=" + accessToken;
// 请求参数
Map<String, String> map = new HashMap<>(1);
map.put("code", code);
// 发起请求 注意请求参数map要转成json字符串
String res = HttpUtils.sendPost(url, JSON.toJSONString(map));
// 解析请求结果 拿到手机号
JSONObject jsonObject = JSONObject.parseObject(res);
String phone_info = jsonObject.get("phone_info").toString();
JSONObject jsonPhone_info = JSONObject.parseObject(phone_info);
String phoneNumber = jsonPhone_info.get("phoneNumber").toString();
System.out.println("用户手机号:" + phoneNumber);
return phoneNumber;
}
public static JSONObject getPhoneNumber(String code) {
try {
String accessToken = getAccessToken();
String url = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=ACCESSTOKEN";
url = url.replace("ACCESSTOKEN", accessToken);
HashMap<String, Object> requestParam = new HashMap<>();
// 手机号调用凭证
requestParam.put("code", code);
JSONObject jsonObject = JSONObject.parseObject(HttpUtils.sendPost(url, JSON.toJSONString(requestParam)));
return jsonObject;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static JSONObject login(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session" + "?appid=" + WxPayConfig.APPID + "&secret=" + WxPayConfig.APPSECRET + "&js_code=" + code + "&grant_type=authorization_code";
String result = HttpUtils.sendGet(url);
JSONObject resultObject = JSON.parseObject(result);
String ip = IpUtils.getIpAddr(); //当前用户登录Ip
String address = AddressUtils.getRealAddressByIP(ip); //当前用户登录地址
System.out.println("根据code去获取信息:" + resultObject);
resultObject.put("Ip", ip);
resultObject.put("address", address);
return resultObject;
}
public static String doRefund(String url, String data){
StringBuilder sb = new StringBuilder();
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream instream = IndexController.class.getClassLoader().getResourceAsStream("apiclient_cert.p12");
String mchid = WxPayConfig.weChatPayMchId;
try {
keyStore.load(instream, mchid.toCharArray());
} finally {
instream.close();
}
// 证书
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, mchid.toCharArray()).build();
// 只允许TLSv1协议
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
//创建基于证书的httpClient,后面要用到
CloseableHttpClient client = HttpClients.custom()
.setSSLSocketFactory(sslsf)
.build();
HttpPost httpPost = new HttpPost(url);//退款接口
StringEntity reqEntity = new StringEntity(data);
// 设置类型
reqEntity.setContentType("application/x-www-form-urlencoded");
httpPost.setEntity(reqEntity);
CloseableHttpResponse response = client.execute(httpPost);
try {
HttpEntity entity = response.getEntity();
if (entity != null) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(entity.getContent(), "UTF-8"));
String text = "";
while ((text = bufferedReader.readLine()) != null) {
sb.append(text);
}
}
EntityUtils.consume(entity);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
response.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
/**
* 随机字符串
* @return
*/
public static String generateNonceStr() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
}
public static String generateSignature(final Map<String, String> data, String key, String 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("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 ("MD5".equals(signType)) {
return MD5(sb.toString()).toUpperCase();
}else if ("HMACSHA256".equals(signType)) {
return HMACSHA256(sb.toString(), key);
}else {
throw new Exception(String.format("Invalid sign_type: %s", signType));
}
}
public static String MD5(String data) throws Exception {
java.security.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();
}
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();
}
public static String mapToXml(Map<String, String> map) throws Exception {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set<String> set = map.keySet();
Iterator<String> it = set.iterator();
while (it.hasNext()) {
String key = it.next();
sb.append("<" + key + ">").append(map.get(key)).append("</" + key + ">");
}
sb.append("</xml>");
return sb.toString();
}
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = 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) {
getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
public static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-general-entities", false);
documentBuilderFactory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
documentBuilderFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
documentBuilderFactory.setXIncludeAware(false);
documentBuilderFactory.setExpandEntityReferences(false);
return documentBuilderFactory.newDocumentBuilder();
}
public static Logger getLogger() {
Logger logger = LoggerFactory.getLogger("wxpay java sdk");
return logger;
}
public static String generateOrderNumber() {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); // 生成日期部分
String datePart = dateFormat.format(new Date());
Random random = new Random(); // 生成随机数部分
int randomPart = random.nextInt(999999);
String orderNumber = datePart + String.format("%06d", randomPart); // 拼接订单号
return orderNumber;
}
public static byte[] readInput(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int len = 0;
byte[] buffer = new byte[1024];
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.close();
in.close();
return out.toByteArray();
}
private static final String ALGORITHM = "AES";
private static final String ALGORITHM_MODE_PADDING = "AES/ECB/PKCS7Padding";
/**
* AES解密
*
* @param base64Data 64
* @return str
* @throws Exception e
*/
public static String decryptData(String base64Data,String mchKey) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
Cipher cipher = Cipher.getInstance(ALGORITHM_MODE_PADDING, "BC");
SecretKeySpec key = new SecretKeySpec(WxPayConfig.MD5(mchKey).toLowerCase().getBytes(), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
return new String(cipher.doFinal(base64Decode8859(base64Data).getBytes("ISO-8859-1")), "utf-8");
}
/**
* Base64解码
* @param source base64 str
* @return str
*/
public static String base64Decode8859(final String source) {
String result = "";
final Base64.Decoder decoder = Base64.getDecoder();
try {
// 此处的字符集是ISO-8859-1
result = new String(decoder.decode(source), "ISO-8859-1");
} catch (final UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
}
HttpUtils StringUtils.EMPTY 替换成 “” 空 Constants.UTF8替换成UTF-8 或者在完整项目代码中拿到这两个类
import com.song.wxpay.util.Result.StringUtils;
import com.song.wxpay.util.ip.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.*;
import java.net.ConnectException;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
/**
* 通用http发送方法
*
* @author ruoyi
*/
public class HttpUtils
{
private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url)
{
return sendGet(url, StringUtils.EMPTY);
}
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param)
{
return sendGet(url, param, Constants.UTF8);
}
/**
* 向指定 URL 发送GET方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @param contentType 编码类型
* @return 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param, String contentType)
{
StringBuilder result = new StringBuilder();
BufferedReader in = null;
try
{
String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
log.info("sendGet - {}", urlNameString);
URL realUrl = new URL(urlNameString);
URLConnection connection = realUrl.openConnection();
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect();
in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
String line;
while ((line = in.readLine()) != null)
{
result.append(line);
}
log.info("recv - {}", result);
}
catch (ConnectException e)
{
log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
}
catch (SocketTimeoutException e)
{
log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
}
catch (IOException e)
{
log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
}
catch (Exception e)
{
log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
}
finally
{
try
{
if (in != null)
{
in.close();
}
}
catch (Exception ex)
{
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url 发送请求的 URL
* @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendPost(String url, String param)
{
PrintWriter out = null;
BufferedReader in = null;
StringBuilder result = new StringBuilder();
try
{
log.info("sendPost - {}", url);
URL realUrl = new URL(url);
URLConnection conn = realUrl.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
out = new PrintWriter(conn.getOutputStream());
out.print(param);
out.flush();
in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
String line;
while ((line = in.readLine()) != null)
{
result.append(line);
}
log.info("recv - {}", result);
}
catch (ConnectException e)
{
log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
}
catch (SocketTimeoutException e)
{
log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
}
catch (IOException e)
{
log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
}
catch (Exception e)
{
log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
}
finally
{
try
{
if (out != null)
{
out.close();
}
if (in != null)
{
in.close();
}
}
catch (IOException ex)
{
log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
}
}
return result.toString();
}
public static String sendSSLPost(String url, String param)
{
StringBuilder result = new StringBuilder();
String urlNameString = url + "?" + param;
try
{
log.info("sendSSLPost - {}", urlNameString);
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
URL console = new URL(urlNameString);
HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
conn.setRequestProperty("Accept-Charset", "utf-8");
conn.setRequestProperty("contentType", "utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setSSLSocketFactory(sc.getSocketFactory());
conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
conn.connect();
InputStream is = conn.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String ret = "";
while ((ret = br.readLine()) != null)
{
if (ret != null && !"".equals(ret.trim()))
{
result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
}
}
log.info("recv - {}", result);
conn.disconnect();
br.close();
}
catch (ConnectException e)
{
log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
}
catch (SocketTimeoutException e)
{
log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
}
catch (IOException e)
{
log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
}
catch (Exception e)
{
log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
}
return result.toString();
}
private static class TrustAnyTrustManager implements X509TrustManager
{
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType)
{
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType)
{
}
@Override
public X509Certificate[] getAcceptedIssuers()
{
return new X509Certificate[] {};
}
}
private static class TrustAnyHostnameVerifier implements HostnameVerifier
{
@Override
public boolean verify(String hostname, SSLSession session)
{
return true;
}
}
}
接下来就是接口层IndexController
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.song.wxpay.util.HttpUtils;
import com.song.wxpay.util.WxPayConfig;
import com.song.wxpay.util.ip.Constants;
import com.song.wxpay.util.ip.IpUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController("/")
public class IndexController {
@RequestMapping("/index")
public String index() {
return "Wellcome";
}
//根据Code获取用户手机号!注意!登录的Code和获取手机号的Code是不同的 uniapp获取手机号需要button设置 @getphonenumber="getUserPhone" open-type="getPhoneNumber"
@PostMapping("/getPhone")
public JSONObject getPhone(String code) {
return WxPayConfig.getPhoneNumber(code);
}
//根据Code进入login接口获取当前用户openID 顺带获取了一下当前请求来源用户的登录Ip以及登录地址
@PostMapping("/login")
public JSONObject login(String code) {
return WxPayConfig.login(code);
}
@PostMapping("/v2pay")
public Map v2pay() throws Exception {
Map<String, String> map = new HashMap<>();
map.put("appid", WxPayConfig.APPID); //公众账号ID
map.put("mch_id", WxPayConfig.weChatPayMchId); //商户号
map.put("nonce_str", WxPayConfig.generateNonceStr()); //随机字符串
map.put("body", "商品描述"); //商品描述
map.put("out_trade_no", "88888888202311061200"); //商户订单号
map.put("total_fee", "1"); //标价金额 转换成分 微信发起付款请求要求是字符串
map.put("trade_type", "JSAPI"); //交易类型
map.put("spbill_create_ip", IpUtils.getIpAddr()); //终端IP
map.put("notify_url", WxPayConfig.weChatPayNotifyUrl); //通知地址
map.put("openid", "oIdOA4otaUBfXVxWlB2QtpL2oFEc"); //openid
map.put("sign_type", "MD5"); //签名类型
map.put("sign", WxPayConfig.generateSignature(map, WxPayConfig.weChatV2PayKey, "MD5"));
String resultXml = HttpUtils.sendPost(WxPayConfig.weChatPayUrl, WxPayConfig.mapToXml(map));
System.out.println("请求参数===========");
System.out.println("商品详情:" + map.get("body") + " 商户订单号:" + map.get("out_trade_no") + " OPENID:" + map.get("openid") + " JSON:" + JSON.toJSONString(map));
Map<String, String> wxResultMap = WxPayConfig.xmlToMap(resultXml);
System.out.println("返回参数===========");
System.out.println(JSON.toJSONString(wxResultMap));
Map<String, String> resultMap = new HashMap<>();
if (wxResultMap.get("return_code").equals("SUCCESS") && wxResultMap.get("result_code").equals("SUCCESS")) {
resultMap.put("appId", WxPayConfig.APPID);
resultMap.put("timeStamp", System.currentTimeMillis() / 1000 + "");
resultMap.put("nonceStr", WxPayConfig.generateNonceStr());
resultMap.put("package", "prepay_id=" + wxResultMap.get("prepay_id"));
resultMap.put("signType", "MD5");
//generateSignature在这里会去生成签名 所以resultMap中不能再去多加参数 否则会报生成签名的错误!!!!!
resultMap.put("paySign", WxPayConfig.generateSignature(resultMap, WxPayConfig.weChatV2PayKey, "MD5"));
} else {
resultMap.put("code", "500");
}
return resultMap;
}
@RequestMapping("/weChatPayNotify")
public String newweChatPayNotify(HttpServletRequest request) throws Exception {
System.out.println("支付回调");
String notifyXml = new String(WxPayConfig.readInput(request.getInputStream()), "utf-8");
Map<String, String> notifyMap = WxPayConfig.xmlToMap(notifyXml);
if ("SUCCESS".equals(notifyMap.get("result_code"))) { //return直接返回notifyXml 当result_code返回的结果是SUCCESS时 则不进行调用了 如果不返回固定格式微信就会反复调用
System.out.println("回调成功"); //支付成功时候,处理业务逻辑
System.out.println("微信交易订单号:" + notifyMap.get("transaction_id") + " 商户订单号:" + notifyMap.get("out_trade_no") + " OPENID:" + notifyMap.get("openid") + " JSON:" + JSON.toJSONString(notifyMap));
return notifyXml;
}
return notifyXml;
}
@RequestMapping("/weChatRefundNotify")
public String weChatRefundNotify(HttpServletRequest request) throws Exception {
System.out.println("退款回调");
Map<String, String> returnMap = new HashMap<>();
String xml = new String(WxPayConfig.readInput(request.getInputStream()), "utf-8");
Map<String, String> notifyMap = WxPayConfig.xmlToMap(xml);
// 验签返回的数据
if (notifyMap.get("return_code").equals("SUCCESS")) {
// 此时在解密 req_info 字段信息
Map<String, String> reqInfo = WxPayConfig.xmlToMap(WxPayConfig.decryptData(notifyMap.get("req_info"), WxPayConfig.weChatV2PayKey));
if (!reqInfo.get("refund_status").equals(Constants.SUCCESS)) {
System.out.println("回调成功");
returnMap.put("return_code", "SUCCESS");
returnMap.put("return_msg", "OK");
System.out.println(reqInfo); //transaction_id:交易成功后的微信交易单号 success_time:成功时间 refund_recv_accout:退款到哪个位置 out_refund_no、out_trade_no
return WxPayConfig.mapToXml(returnMap);
}
}
returnMap.put("return_code", "FAIL");
returnMap.put("return_msg", "");
String returnXml = WxPayConfig.mapToXml(returnMap);
return returnXml;
}
@PostMapping("/v2refund")
public Map v2refund() throws Exception {
Map<String, String> reqData = new HashMap<>();
reqData.put("appid", WxPayConfig.APPID);
reqData.put("mch_id", WxPayConfig.weChatPayMchId);
reqData.put("nonce_str", WxPayConfig.generateNonceStr());//随机字符串
reqData.put("sign_type", "MD5");
reqData.put("transaction_id", "4200001972202311061014940605");//支付成功后支付回调返回的transaction_id
reqData.put("out_refund_no", WxPayConfig.generateOrderNumber());
reqData.put("total_fee", "1");//订单总金额,单位为分
reqData.put("refund_fee", "1");//退款总金额,单位为分
reqData.put("notify_url", WxPayConfig.weChatRefundNotifyUrl);
reqData.put("sign", WxPayConfig.generateSignature(reqData, WxPayConfig.weChatV2PayKey, "MD5"));
String xmlParam = WxPayConfig.mapToXml(reqData);
System.out.println("退款请求参数===========");
System.out.println(xmlParam);
String xmlStr = WxPayConfig.doRefund("https://api.mch.weixin.qq.com/secapi/pay/refund", xmlParam);
Map<String, String> map = WxPayConfig.xmlToMap(xmlStr);
if (map == null || !"SUCCESS".equals(map.get("return_code"))) {
System.out.println("-----退款发起失败-----" + JSON.toJSONString(map));
return null;
} else {//返回退款微信订单号
System.out.println("退款返回参数===========");
System.out.println(JSON.toJSONString(map));
return map;
}
}
}
如果是公众号的话需要做一下配置 并保存AppID和AppSecret 用于获取AccessToken
配置IP白名单(配置本地的ip地址)
服务器配置 :(其实就是需要微信服务器调用自己项目中的接口去验证)
//微信回调的校验接口
@RequestMapping(value = "/check", produces = "text/plain;charset=UTF-8", method = {RequestMethod.GET, RequestMethod.POST})
//微信服务器根据小程序配置的token,结合时间戳timestamp和随机数nonce通过SHA1生成签名,发起get请求,检验token的正确性,
//检验正确原样返回随机字符串echostr,失败返回空字符串
public String check(HttpServletRequest request, HttpServletResponse response,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
String echostr) throws Exception {
//若是为get请求,则为开发者模式验证
if ("get".equals(request.getMethod().toLowerCase())) {
return wechatService.checkSignature(signature, timestamp, nonce, echostr);
}else {
return null;
}
}
public String checkSignature(String signature, String timestamp, String nonce, String echostr) {
try {
//这里的“token”是正确的token,由服务器定义,小程序只有使用正确的token,微信服务器才会验证通过
String checkSignature = SHA1.creatSHA1("token", timestamp, nonce);
if (checkSignature.equals(signature)) {
return echostr;
}
} catch (AesException e) {
e.printStackTrace();
}
return null;
}
public static String creatSHA1(String token, String timestamp, String nonce) throws AesException
{
try {
String[] array = new String[] { token, timestamp, nonce};
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < 3; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1签名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.ComputeSignatureError);
}
}