前言:最近公司想实现微信的扫码支付,网上找了很多资料也看了官方文档,看的一脸懵逼,最终总结了最简单的实现方式。
基本业务逻辑就是用户访问过来,我们去掉微信支付的接口人家给我们反一个二维码我们丢给前端让用户扫码支付就行,等他支付完了微信会回调通知我们支付完了,这个回调的地址需要外网可以访问到,这里用到Ngrok的内网穿透,先看我操作。
导入依赖:
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.0.6</version>
</dependency>
<--生成二维码必须-->
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
直接导入hutool的工具包,好多工具方法免得自己写了,包含了大部分我们想用的工具类,发起HTTP请求、生成二维码,解析xml转map等,有兴趣可以自己研究
第一步:参数配置类
基本的参数需要去微信网站申请,不多赘述,直接上代码
public class WeChatConfig {
/**
* 微信服务号APPID
*/
public static String APPID="wx6b4656gd30cb1c8c";
/**
* 微信支付的商户号
*/
public static String MCHID="16132344586";
/**
* 微信支付的API密钥
*/
public static String APIKEY="Dafawangluokejiyouxiaewqeongsi1234";
/**
* 微信支付成功之后的回调地址【注意:当前回调地址必须是公网能够访问的地址】
内网穿透去实现
*/
public static String WECHAT_NOTIFY_URL_PC="http://inxus.free.idcfengye.com/wxnotify";
/**
* 微信统一下单API地址
*/
public static String UFDODER_URL="https://api.mch.weixin.qq.com/pay/unifiedorder";
/**
* 应用对应的凭证
*/
public static String APP_SECRET="10a893a9d0964a8e76a6042d0650f08a";
}
第二步:基本的工具类
验证签名、获取本地IP,生成订单号以及元转分(微信支付是以分为单位,0.1元即为10分)的一些方法
public class PayForUtil {
/**
* 是否签名正确,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
* @return boolean
*/
public static boolean isTenpaySign( SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
String v = (String)entry.getValue();
if(!"sign".equals(k) && null != v && !"".equals(v)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
//算出摘要
String mysign = DigestUtil.md5Hex(sb.toString()).toLowerCase();
String tenpaySign = ((String)packageParams.get("sign")).toLowerCase();
return tenpaySign.equals(mysign);
}
/**
* sign签名
* @return String
*/
public static String createSign( SortedMap<Object, Object> packageParams, String API_KEY) {
StringBuffer sb = new StringBuffer();
Set es = packageParams.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = entry.getValue().toString();
if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = DigestUtil.md5Hex(sb.toString());
return sign;
}
/**
* 获取本机IP地址
* @return String
*/
public static String localIp(){
String ip = null;
Enumeration allNetInterfaces;
try {
allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
List<InterfaceAddress> InterfaceAddress = netInterface.getInterfaceAddresses();
for (InterfaceAddress add : InterfaceAddress) {
InetAddress Ip = add.getAddress();
if (Ip != null && Ip instanceof Inet4Address) {
ip = Ip.getHostAddress();
}
}
}
} catch (SocketException e) {
e.printStackTrace();
}
return ip;
}
/**
* 生成订单号
* @return
*/
public static String getOrderNo() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String newDate = sdf.format(new Date());
String result = "";
Random random = new Random();
for (int i = 0; i < 3; i++) {
result += random.nextInt(10);
}
return newDate + result;
}
/**
* 元转换成分
* @param amount
* @return
*/
public static String getMoney(String amount) {
if(amount==null){
return "";
}
// 金额转化为分为单位
// 处理包含, ¥ 或者$的金额
String currency = amount.replaceAll("\\$|\\¥|\\,", "");
int index = currency.indexOf(".");
int length = currency.length();
Long amLong = 0l;
if(index == -1){
amLong = Long.valueOf(currency+"00");
}else if(length - index >= 3){
amLong = Long.valueOf((currency.substring(0, index+3)).replace(".", ""));
}else if(length - index == 2){
amLong = Long.valueOf((currency.substring(0, index+2)).replace(".", "")+0);
}else{
amLong = Long.valueOf((currency.substring(0, index+1)).replace(".", "")+"00");
}
return amLong.toString();
}
}
第三步: 编写controller层
编写两个接口,预支付的接口即生成二维码,和回调的接口即用户扫码成功后微信会调你的回调接口告诉你人家支付成功了,该你下一步操作了。
@Controller
public class PayController {
//预支付接口(生成二维码并输出到浏览器)
@GetMapping("codePay" )
@ResponseBody
private void codePay(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 支付的商品名称
String body="可口可乐";
//支付金额(单位:分)
String totalFee="1";
//获取二维码内容urlCode
String resXml = WeChatPay.getNative( body, totalFee);
Map<String, Object> data= XmlUtil.xmlToMap(resXml);
String urlCode = data.get("code_url").toString();
//生成二维码到输出流
response.setContentType("image/jpeg");
ServletOutputStream out = response.getOutputStream();
QrCodeUtil.generate(urlCode, 300, 300,"jpg" ,out);
out.close();
}
//回调接口
@RequestMapping("wxnotify" )
public void wxnotify(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.err.println("开始回调。。。");
WeChatPay.notify(request, response);
}
}
第四步:编写业务层
具体的封装参数向微信发起请求返回code_url即二维码,和回调的业务
public class WeChatPay {
/**
* @Description NATIVE支付模式(二维码网页扫码支付)
*/
public static String getNative(String body,String totalFee) throws Exception {
//账号信息
String appid = WeChatConfig.APPID;
String mch_id = WeChatConfig.MCHID;
String key = WeChatConfig.APIKEY;
//微信支付成功之后的回调地址【注意:当前回调地址必须是公网能够访问的地址】
String notify_url = WeChatConfig.WECHAT_NOTIFY_URL_PC;
String ufdoder_url = WeChatConfig.UFDODER_URL;
String trade_type = "NATIVE";
String out_trade_no = PayForUtil.getOrderNo();
//请求参数封装
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
// 随机字符串
packageParams.put("nonce_str", "nonce"+out_trade_no);
// 支付的商品名称
packageParams.put("body", body);
// 商户订单号【备注:每次发起请求都需要随机的字符串,否则失败。】
packageParams.put("out_trade_no",out_trade_no);
// 支付金额
packageParams.put("total_fee", totalFee);
// 客户端主机
packageParams.put("spbill_create_ip", PayForUtil.localIp());
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
// 获取签名
String sign = PayForUtil.createSign(packageParams, key);
packageParams.put("sign", sign);
// 将请求参数转换成String类型
String requestXML = XmlUtil.mapToXmlStr(packageParams, "xml");
System.err.println("请求报文:-----------------------------------");
System.err.println(requestXML);
// 解析请求之后的xml参数并且转换成String类型
String resXml= HttpUtil.post(ufdoder_url, requestXML);
System.err.println("应答报文:-----------------------------------");
System.err.println(resXml);
return resXml;
}
/**
* @Description 支付回调
*/
public static void notify(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 读取回调数据
InputStream inputStream;
StringBuffer sb = new StringBuffer();
inputStream = request.getInputStream();
String s;
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while ((s = in.readLine()) != null) {
sb.append(s);
}
in.close();
inputStream.close();
// 解析xml成map
Map<String, Object> m=XmlUtil.xmlToMap(sb.toString());
// 过滤空 设置 TreeMap
SortedMap<Object, Object> packageParams = new TreeMap<Object, Object>();
Iterator<String> it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = it.next();
Object parameterValue = m.get(parameter);
String v = "";
if (null != parameterValue) {
v = parameterValue.toString().trim();
}
packageParams.put(parameter, v);
}
// 微信支付的API密钥
String key = WeChatConfig.APIKEY;
// 判断签名是否正确
if (PayForUtil.isTenpaySign(packageParams, key)) {
String resXml = "";
if ("SUCCESS".equals((String) packageParams.get("result_code"))) {
System.err.println("--------------------------------------------");
System.err.println("支付回调成功。。。可在此处执行业务逻辑。。。");
System.err.println("--------------------------------------------");
// 支付成功,执行自己的业务逻辑开始
String app_id = (String) packageParams.get("appid");
String mch_id = (String) packageParams.get("mch_id");
String openid = (String) packageParams.get("openid");
// 是否关注公众号
String is_subscribe = (String) packageParams.get("is_subscribe");
// 附加参数【商标申请_0bda32824db44d6f9611f1047829fa3b_15460】--【业务类型_会员ID_订单号】
String attach = (String) packageParams.get("attach");
String out_trade_no = (String) packageParams.get("out_trade_no");
String total_fee = (String) packageParams.get("total_fee");
// 微信支付订单号
String transaction_id = (String) packageParams.get("transaction_id");
// 支付完成时间
String time_end = (String) packageParams.get("time_end");
// 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
System.err.println("支付失败,错误信息:" + packageParams.get("err_code"));
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
}
response.getWriter().write(resXml);
} else {
System.err.println("通知签名验证失败");
}
}
}
运行项目,访问支付接口就可以看到生成的二维码了
请求成功,控制台打印如下:
至此微信扫码支付基本完成,包括反二维码的接口和微信回调的接口,目前回调接口是访问不到的,需要内网穿透