最近打算把微信相关的东西整理一下,本来想发布成jar形式但是经过考虑jar包形式其实是一个框架形式或工具形式。在使用上存在一些使用成本比如要是根据编写者的一些规则来使用,还有就是如果封装太死导致不够灵活所以经过考虑我把微信相关操作整合成了一个springBoot工程发布在了码云上地址是[码云地址]
(https://gitee.com/yankangkk/watchmen),jspi的支付要在微信号里面配置项目的url,ip之类的这个可以百度一下,有兴趣的可以debug走读代码,再结合微信官方文档基本上就能了解微信的整个接口交互过程。微信官方文档地址
目前整合和了扫微信扫码支付和JSAPI支付日后还会添加一些其他东西,下边是一些代码贴出来
package com.watchmen.wechat.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
import lombok.Data;
/**
* @author kk
* 微信配置类
*/
@Component
@PropertySource({"classpath:/config/config.properties"})
@ConfigurationProperties
@Data
public class WeChatConfig {
@Value("${appID}")
String appID;
// 微信支付的API密钥
@Value("${weChatApiKey}")
String weChatApiKey;
// 微信支付的商户号
@Value("${weChatMerchant}")
String weChatMerchant;
// 微信统一下单API地址
@Value("https://api.mch.weixin.qq.com/pay/unifiedorder")
String weChatOrderUrl;
@Value("${weChatCallbackUrl}")
String weChatCallbackUrl;
@Value("${weChatPay}")
String weChatPay;
@Value("MD5")
String weChatMD5Sign;
@Value("watchmen")
String body;
@Value("NATIVE")
String tradeTypeNative;
@Value("JSAPI")
String tradeTypeJSAPI;
}
下面类组装微信支付需要的参数,并请求微信统一下单接口
package com.watchmen.wechat.pay;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentMap;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import com.watchmen.wechat.config.WeChatConfig;
import com.watchmen.wechat.util.HttpRequest;
import com.watchmen.wechat.util.MD5Util;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NetUtil;
import cn.hutool.core.util.XmlUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
/**
* @author kk
* 微信相关
*/
@Component
public class WeChatPay {
@Autowired
WeChatConfig weChatConfig;
@Autowired
MD5Util mD5Util;
@Autowired
HttpRequest httpRequest;
/**
* 微信扫码支付
* @param amount 金额
* @param body 描述
* @param openid 微信openid
* @return
*/
@SuppressWarnings("unused")
public JSONObject scanningPay(String amount,String body,String openid){
JSONObject parse = null;
try {
TreeMap<String, Object> params = new TreeMap<>();
// 支付的商品名称
params.put("body", body);
// 商户订单号【备注:每次发起请求都需要随机的字符串,否则失败。】
params.put("out_trade_no", "F"+IdUtil.createSnowflake(1, 1).nextId());
// 支付金额
params.put("total_fee", amount);
// 商户订单号【备注:每次发起请求都需要随机的字符串,否则失败。】
params.put("out_trade_no", IdUtil.simpleUUID());
// openid
params.put("openid", openid);
// 交易类型
params.put("trade_type", weChatConfig.getTradeTypeNative());
String returnJson = weChatInitialization(params);
parse = (JSONObject) JSONUtil.parse(returnJson);
return parse;
} catch (Exception e) {
e.printStackTrace();
}
return parse;
}
/**
* 微信JSAPI支付
* @param amount 金额
* @param body 描述
* @param openid 微信openid
* @return
*/
public TreeMap<String, Object> JSAPIPay(String amount,String body,String openid){
// 返回给前端
TreeMap<String, Object> map = MapUtil.newTreeMap(null);
JSONObject parse = null;
try {
TreeMap<String, Object> params = new TreeMap<>();
// 支付的商品名称
params.put("body", body);
// 商户订单号【备注:每次发起请求都需要随机的字符串,否则失败。】
params.put("out_trade_no", "F"+IdUtil.createSnowflake(1, 1).nextId());
// 支付金额
params.put("total_fee", amount);
// 商户订单号【备注:每次发起请求都需要随机的字符串,否则失败。】
params.put("out_trade_no", IdUtil.simpleUUID());
// openid
params.put("openid", openid);
// 交易类型
params.put("trade_type", weChatConfig.getTradeTypeJSAPI());
String returnJson = weChatInitialization(params);
parse = (JSONObject) JSONUtil.parse(returnJson);
String xml = parse.getStr("xml");
JSONObject parseObj = JSONUtil.parseObj(xml);
// 预付款id
String prepayId = parseObj.getStr("prepay_id");
// appId
map.put("appId", weChatConfig.getAppID());
// 时间戳
map.put("timeStamp", System.currentTimeMillis());
// 随机字符串
map.put("nonceStr", IdUtil.simpleUUID());
// 预支付Id
map.put("package", prepayId);
// 加密类型
map.put("signType", weChatConfig.getWeChatMD5Sign());
// 签名
map.put("paySign", mD5Util.createSign("UTF-8", map, weChatConfig.getWeChatApiKey()));
return map;
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* 微信支付参数初始化
* @param params 输入参数
* @param payType 支付类型
* @return
*/
private String weChatInitialization(TreeMap<String, Object> params){
// 获取ip地址
LinkedHashSet<String> localIpv4s = NetUtil.localIpv4s();
String localIpv4 = "127.0.0.1";
if(localIpv4s.size() > 0){
Integer count = 0;
for (String ip : localIpv4s) {
count++;
if(count == 2){
localIpv4 = ip;
}
}
}
try {
// 公众账号ID
params.put("appid", weChatConfig.getAppID());
// 商户号
params.put("mch_id", weChatConfig.getWeChatMerchant());
// 随机字符串
params.put("nonce_str", IdUtil.createSnowflake(1, 1).nextId());
// 通知地址(回调地址)
params.put("notify_url", weChatConfig.getWeChatCallbackUrl());
// 客户端主机 ip
params.put("spbill_create_ip", localIpv4);
// 生成签名
String createSign = mD5Util.createSign("UTF-8", params, weChatConfig.getWeChatApiKey());
params.put("sign", createSign);
// 转换成xml格式,在请求微信接口
String createXml = getRequestXml(params);
// 调用微信统一下单接口
String doHttpsJson_HttpClient = httpRequest.doHttpsJson_HttpClient(weChatConfig.getWeChatOrderUrl(),
"POST", createXml.toString());
// 把微信返回的xml格式转化为json串
JSONObject parseFromXml = JSONUtil.parseFromXml(doHttpsJson_HttpClient);
return parseFromXml.toString();
} catch (Exception e) {
throw new RuntimeException("初始化参数失败!");
}
}
/**
* 解析微信回调数据
* @param request
* @return
*/
public Map<String, Object> payResult(HttpServletRequest request){
Map<String, Object> map = new HashMap<String, Object>();
StringBuffer sb = new StringBuffer();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line = "";
while((line = br.readLine()) != null){
sb.append(line);
}
JSONObject parseFromXml = JSONUtil.parseFromXml(sb.toString());
String xml = parseFromXml.getStr("xml");
String resultCode = JSONUtil.parseObj(xml).getStr("result_code");
String returnCode = JSONUtil.parseObj(xml).getStr("return_code");
if("SUCCESS".equals(resultCode) && "SUCCESS".equals(returnCode)){
map = (Map<String, Object>) parseFromXml.getObj("xml");
}
} catch (IOException e) {
e.printStackTrace();
}
return map;
}
/**
* @author chenp
* @Description:将请求参数转换为xml格式的string
* @param parameters 请求参数
* @return
*/
public String getRequestXml(SortedMap<String, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
parameters.forEach((k,v) -> {
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
});
sb.append("</xml>");
return sb.toString();
}
}
package com.watchmen.wechat.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import org.springframework.stereotype.Component;
import com.sun.net.ssl.HttpsURLConnection;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
/**
* @author kk
* http请求工具
*/
@Component
public class HttpRequest {
/**
* @param requestUrl 要请求的url
* @param requestMethod 请求方式
* @param outputStr 发送求的提交数据
* @return 返回请求结果
*/
public String doHttpsJson_HttpClient(String requestUrl, String requestMethod, String outputStr){
try {
URL url = new URL(null,requestUrl,new com.sun.net.ssl.internal.www.protocol.https.Handler());
//URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 设置请求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
// 当outputStr不为null时向输出流写数据
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意编码格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 释放资源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
conn.disconnect();
return buffer.toString();
} catch (MalformedURLException e) {
System.out.println("连接超时:{}");
e.printStackTrace();
} catch (IOException e) {
System.out.println("https请求异常:{}");
e.printStackTrace();
}
return "";
}
}
package com.watchmen.wechat.util;
import java.security.MessageDigest;
import java.util.Map;
import java.util.SortedMap;
import org.springframework.stereotype.Component;
/**
* @author kk
* MD5 加密工具
*/
@Component
public class MD5Util {
private static final String hexDigits[] = {"0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
/**
* MD5加密
* @param origin 要加密的字符串
* @param charsetname 字符编码
* @return
*/
public String MD5Encode (String origin, String charsetname){
String result = "";
try {
result = origin;
MessageDigest md = MessageDigest.getInstance("MD5");
if(null == charsetname || "".equals(charsetname)){
result = this.byteArrayToHexString(md.digest(result.getBytes()));
}else{
result = byteArrayToHexString(md.digest(result.getBytes(charsetname)));
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* @param byte数组转换字符串
* @return
*/
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
/**
* @param characterEncoding 字符编码
* @param params 加密参数
* @param key 加密所用的秘钥
* @return
*/
public String createSign(String characterEncoding,SortedMap<String, Object> params,String key){
StringBuffer sb = new StringBuffer();
params.forEach((k,v)->{
if(null != v &&
!"".equals(v) &&
!"sign".equals(k) &&
!"key".equals(k)){
sb.append(k + "=" + v + "&");
}
});
sb.append("key=" + key);
String sign = this.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
}
controller直接调用就可以看到结果了
package com.watchmen.controller;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.watchmen.wechat.pay.WeChatPay;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
/**
* @author kk
* 微信测试
*/
@RestController
@RequestMapping("/wechat")
public class WeChatPayTest {
@Autowired
WeChatPay weChatPay;
/**
* 扫码支付测试
* @return
*/
@GetMapping("/scanning")
public Object scanningPayment(){
JSONObject parse = weChatPay.scanningPay("1", "测试支付", "oIbwD1txKe4cnhEOr3SYEo1Tllyk");
// 解析json
String xml = parse.getStr("xml");
// 生成二维码
String codeUrl = JSONUtil.parseObj(xml).getStr("code_url");
// 手机微信扫码支付后微信就会payCall方法了
QrCodeUtil.generate(codeUrl, 300, 300, FileUtil.file("D:/logs/test.jpg"));
return null;
}
/**
* jsApi支付测试
* @return
*/
@GetMapping("/jsApi")
public Object jsApiPayment(){
TreeMap<String, Object> parse = weChatPay.JSAPIPay("1", "测试支付", "oIbwD1txKe4cnhEOr3SYEo1Tllyk");
System.out.println(parse);
return parse;
}
/**
* 微信回调地址
* @param request
* @param response
* @throws Exception
*/
@RequestMapping("/payCall")
public void payCall(HttpServletRequest request,HttpServletResponse response) throws Exception{
System.out.println("微信回调用!");
Map<String, Object> payResult = weChatPay.payResult(request);
if(payResult.size() > 0){
// 输出订单号
System.out.println("out_trade_no:"+payResult.get("out_trade_no").toString());
}
}
}
下面是配置文件,填入自己的微信相关参数就行了
#-----------------------------------------------微信相关-------------------------------------------
#获取access_token,url
accessTokenUrl=https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
#使用access_token 获取到jsapi_ticket
ticketUrl=https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS&type=jsapi
codeUrl=https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=APPSECRET&code=WECHATCODE&grant_type=authorization_code
infoUrl=https://api.weixin.qq.com/sns/userinfo?access_token=TOKEN&openid=OPENID&lang=zh_CN0
#微信
appID=
appSecret=
#微信支付的API密钥
weChatApiKey=
#微信支付的商户号
weChatMerchant=
#微信支付成功之后的回调地址【注意:当前回调地址必须是公网能够访问的地址】
weChatCallbackUrl=
#true为使用真实金额支付,false为使用测试金额支付(1分)
weChatPay=false