Java实现微信扫码支付V2(NATIVE方式)[全网最简单]
基本业务逻辑就是用户访问过来,我们去调微信支付的接口人家返给我们一个二维码我们丢给前端让用户扫码支付就行,等他支付完了微信会回调通知我们支付完了,这个回调的地址需要外网可以访问到,这里用到Ngrok的内网穿透,先看我操作。
导入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <!-- hutool --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version> 5.7 . 2 </version> </dependency> <!-- 微信支付 SDK --> <dependency> <groupId>com.github.wxpay</groupId> <artifactId>wxpay-sdk</artifactId> <version> 0.0 . 3 </version> </dependency> <!-- 二维码 --> <dependency> <groupId>com.google.zxing</groupId> <artifactId>core</artifactId> <version> 3.5 . 0 </version> </dependency> <!-- wechatpay httpclient --> <dependency> <groupId>com.github.wechatpay-apiv3</groupId> <artifactId>wechatpay-apache-httpclient</artifactId> <version> 0.4 . 8 </version> </dependency> |
hutool的工具包,好多工具方法免得自己写了,包含了大部分我们想用的工具类,发起HTTP请求、生成二维码,解析xml转map等,有兴趣可以自己研究。
第一步:参数配置类
基本的参数需要去微信网站申请,不多赘述,直接上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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分)的一些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 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层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | @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即二维码,和回调的业务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 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( "通知签名验证失败" ); } } } |
运行项目,访问支付接口就可以看到生成的二维码了...
请求成功,控制台打印如下:
至此微信扫码支付基本完成,包括反二维码的接口和微信回调的接口,目前回调接口是访问不到的,需要内网穿透,可以看我另一篇文章
now ,fight for future
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
2020-10-21 Excel导入导出功能