小程序微信支付实现---微服务之支付服务

mac2026-05-06  12

目录

开发前准备

小程序支付开发帮助文档

JSAPI支付

小程序支付业务流程图

统一下单API接口帮助文档

微信支付配置文件:Configure

maven配置SAXBuilder和Gson 

微信支付工具类:PayUtils  

获取客户端IP地址工具类:IPAddr

请求支付订单基础参数

调用微信支付接口

后端统一下单和支付回调接口

返回参数到前端起调微信支付


开发前准备

1、登录微信公众后台:小程序开通微信支付并绑定商户号(在小程序后台微信支付模块按提示操作即可)

2、登录商户平台:商户号对小程序绑定授权(产品中心-APPID授权管理,另外,如果是公众号还需在开发配置中配置支付授权目录)

注意:如果以上2条没有配置好则后面接口返回会出现 appid 和 mch_id 不匹配 (Lz第一次入坑)

3、准备好小程序的appid,微信支付的商户号,支付秘钥

4、小程序内调用登录接口,获取到用户的openid(小程序登录开发指南) 

小程序支付开发帮助文档

JSAPI支付

小程序支付业务流程图

统一下单API接口帮助文档

接下来给大家分享实现代码

微信支付配置文件:Configure

注意 : Configure 中 key不是商户秘钥,而是手动生成的key,LZ第二次入坑,key写的是商户密钥,出现签名错误,为此专门记录一篇博客,希望大家别再掉坑里了,看key如何生成戳这里 > 微信小程序支付统一下单接口返回签名错误已解决

/** * @ClassName: Configure * @Author: liuyaolong * @Description: 微信端支付配置信息 * @Version: 1.0 */ public class Configure { //微信小程序appid public static String appId = ""; //微信小程序appsecret public static String appSecret = ""; //微信商户号 public static String mch_id=""; //手动生成的key public static final String key = ""; //获取微信Openid的请求地址 public static String WxGetOpenIdUrl = ""; //支付成功后的服务器回调url public static final String notify_url=""; //签名方式 public static final String SIGNTYPE = "MD5"; //交易类型 public static final String TRADETYPE = "JSAPI"; //微信统一下单接口地址 public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; public static String getAppId() { return appId; } public static void setAppId(String appId) { Configure.appId = appId; } public static String getAppSecret() { return appSecret; } public static void setAppSecret(String appSecret) { Configure.appSecret = appSecret; } public static String getMch_id() { return mch_id; } public static void setMch_id(String mch_id) { Configure.mch_id = mch_id; } public static String getKey() { return key; } public static String getWxGetOpenIdUrl() { return WxGetOpenIdUrl; } public static void setWxGetOpenIdUrl(String wxGetOpenIdUrl) { WxGetOpenIdUrl = wxGetOpenIdUrl; } public static String getNotify_url() { return notify_url; } public static String getSIGNTYPE() { return SIGNTYPE; } public static String getTRADETYPE() { return TRADETYPE; } public static String getPay_url() { return pay_url; } }

PayUtils在使用时需要在Maven中配置SAXBuilder和Gson

SAXBuilder解读分析: Java解析XML之SAXBuilder用法

maven配置SAXBuilder和Gson

<properties> <gson.version>2.2.4</gson.version> <saxbuilder.version>2.0.5</saxbuilder.version> </properties> <!-- SAXBuilder --> <dependency> <groupId>org.jdom</groupId> <artifactId>jdom2</artifactId> <version>${saxbuilder.version}</version> </dependency> <!-- gson --> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>${gson.version}</version> </dependency>

微信支付工具类:PayUtils  

/** * @ClassName: PayUtils * @Author: liuyaolong * @Description: 微信支付工具类 * @Version: 1.0 */ public class PayUtils { /** * Map值过滤,除去 Map中值为空值和签名参数 * @param params 签名参数 * @return */ public static Map<String,String> paramsFilter(Map<String,String> params){ Map<String,String> rsMap = new HashMap<String,String>(); if(params == null || params.size() <= 0){ return rsMap; } for (String key : params.keySet()){ String value = params.get(key); if(value == null || value.equals("") || key.equalsIgnoreCase("sign") || key.equalsIgnoreCase("sign_type")){ continue; } rsMap.put(key,value); } return rsMap; } /** * 把数组所有元素排序,并按照 “参数=参数值” 的模式用 “&” 字符拼接成字符串 * @param params 需要排序并参与字符拼接的参数组 * @return 拼接后字符串 */ public static String createLinkString(Map<String, String> params) { List<String> keys = new ArrayList<>(params.keySet()); Collections.sort(keys); String prestr = ""; for (int i = 0; i < keys.size(); i++) { String key = keys.get(i); String value = params.get(key); if (i == keys.size() - 1) {// 拼接时,不包括最后一个&字符 prestr = prestr + key + "=" + value; } else { prestr = prestr + key + "=" + value + "&"; } } return prestr; } /** * 签名字符串 * @param text 需要签名的字符串 * @param key 密钥 * @param input_charset 编码格式 * @return 签名结果 */ public static String sign(String text, String key, String input_charset) { text = text + "&key=" + key; return DigestUtils.md5Hex(getContentBytes(text, input_charset)); } /** * 签名字符串 * @param text 需要签名的字符串 * @param sign 签名结果 * @param key 密钥 * @param input_charset 编码格式 * @return 签名结果 */ public static boolean verify(String text, String sign, String key, String input_charset) { text = text +"&key="+ key; String mysign = DigestUtils.md5Hex(getContentBytes(text, input_charset)); if (mysign.equals(sign)) { return true; } else { return false; } } /** * @param content * @param charset * @return * @throws java.security.SignatureException * @throws UnsupportedEncodingException */ public static byte[] getContentBytes(String content, String charset) { if (charset == null || "".equals(charset)) { return content.getBytes(); } try { return content.getBytes(charset); } catch (UnsupportedEncodingException e) { throw new RuntimeException("MD5签名过程中出现错误,指定的编码集不对,您目前指定的编码集是:" + charset); } } /** HttpRequest 请求 * @param requestUrl 请求地址 * @param requestMethod 请求方法 * @param outputStr 参数 */ public static String httpRequest(String requestUrl, String requestMethod, String outputStr) { // 创建SSLContext StringBuffer buffer = null; try { URL url = new URL(requestUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(requestMethod); conn.setDoOutput(true); conn.setDoInput(true); conn.connect(); //往服务器端写内容 if (null != outputStr) { OutputStream os = conn.getOutputStream(); os.write(outputStr.getBytes("utf-8")); os.close(); } // 读取服务器端返回的内容 InputStream is = conn.getInputStream(); InputStreamReader isr = new InputStreamReader(is, "utf-8"); BufferedReader br = new BufferedReader(isr); buffer = new StringBuffer(); String line = null; while ((line = br.readLine()) != null) { buffer.append(line); } br.close(); } catch (Exception e) { e.printStackTrace(); } return buffer.toString(); } public static String urlEncodeUTF8(String source) { String result = source; try { result = java.net.URLEncoder.encode(source, "UTF-8"); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } /** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 * @param strxml * @return * @throws org.jdom2.JDOMException * @throws IOException */ public static Map doXMLParse(String strxml) throws Exception { if (null == strxml || "".equals(strxml)) { return null; } Map m = new HashMap(); InputStream in = StringInputstream(strxml); SAXBuilder builder = new SAXBuilder(); Document doc = builder.build(in); Element root = doc.getRootElement(); List list = root.getChildren(); Iterator it = list.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String k = e.getName(); String v = ""; List children = e.getChildren(); if (children.isEmpty()) { v = e.getTextNormalize(); } else { v = getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * 获取子结点的xml * * @param children * @return String */ public static String getChildrenText(List children) { StringBuffer sb = new StringBuffer(); if (!children.isEmpty()) { Iterator it = children.iterator(); while (it.hasNext()) { Element e = (Element) it.next(); String name = e.getName(); String value = e.getTextNormalize(); List list = e.getChildren(); sb.append("<" + name + ">"); if (!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("</" + name + ">"); } } return sb.toString(); } public static InputStream StringInputstream(String str) { return new ByteArrayInputStream(str.getBytes()); } /** * java对象转字符串 * @param <T> 泛型占位符 * @param obj T * @return String */ public static <T> String toJson(T obj) { Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").enableComplexMapKeySerialization().create(); return gson.toJson(obj); }

获取客户端IP地址工具类:IPAddr

/** * @ClassName: IPAddr * @Author: liuyaolong * @Description: IP地址工具类 * @Version: 1.0 */ public class IPAddr { /** * 获取Ip地址,多级反向代理 * @param request * @return */ public static String getIpaddr(HttpServletRequest request){ String ipAddress = request.getHeader("x-forwarded-for"); if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if(ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){ //根据网卡取本机配置的IP InetAddress inet=null; try { inet = InetAddress.getLocalHost(); } catch (UnknownHostException e) { e.printStackTrace(); } ipAddress= inet.getHostAddress(); } } //对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15 if(ipAddress.indexOf(",")>0){ ipAddress = ipAddress.substring(0,ipAddress.indexOf(",")); } } return ipAddress; } }

请求支付订单基础参数

/** * @ClassName: orderInfo * @Author: liuyaolong * @Description: 订单基础信息 * @Version: 1.0 */ @Data public class OrderInfo implements Serializable { private static final long serialVersionUID = 1L; @NotBlank(message = "orderId can not be null !") private String orderId;//订单编号 @NotBlank(message = "title can not be null !") private String title;//商品标题 @NotNull(message = "money can not be null !") private Integer money;//订单金额,单位:分 @NotBlank(message = "openId can not be null !") private String openId;//小程序用户唯一标识 private String intro;//商品介绍 }

调用微信支付接口Service

/** * @ClassName: WeChatPayService * @Author: liuyaolong * @Description: 微信支付服务类 * @Version: 1.0 */ @Service public class WeChatPayService { private static Logger logger = LoggerFactory.getLogger(WeChatPayService.class); /** * 微信支付 * @param ipAddr 用户IP地址 * @param orderInfo 订单信息 * @return */ public Map WeChatPay(String ipAddr, OrderInfo orderInfo){ Map<String,Object> map = new HashMap<String, Object>(); //生成的随机字符串32 String nonce_str = RandomStringGenerator.getRandomStringByLength(32); try{ String body = new String(orderInfo.getTitle().getBytes("ISO-8859-1"),"UTF-8"); //参数组装,生成统一下单接口签名 Map<String,String> paramsMap = new HashMap<String,String>(); paramsMap.put("appid",Configure.getAppId()); paramsMap.put("mch_id",Configure.getMch_id()); paramsMap.put("nonce_str",nonce_str); paramsMap.put("body",body); paramsMap.put("out_trade_no",orderInfo.getOrderId()); paramsMap.put("total_fee",String.valueOf(orderInfo.getMoney())); paramsMap.put("spbill_create_ip",ipAddr); paramsMap.put("notify_url",Configure.getNotify_url()); paramsMap.put("trade_type",Configure.getTRADETYPE()); paramsMap.put("openid",orderInfo.getOpenId()); //参数过滤 paramsMap = PayUtils.paramsFilter(paramsMap); //把所有数据拼接成字符串 String linkParams = PayUtils.createLinkString(paramsMap); //MD5生成签名,用于调用统一下单接口 String sign = PayUtils.sign(linkParams,Configure.getKey(),"utf-8").toUpperCase(); //拼接统一下单接口所需xml数据 String xml = "<xml>" + "<appid>" + Configure.getAppId() + "</appid>" + "<body><![CDATA[" + body + "]]></body>" + "<mch_id>" + Configure.mch_id + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>" + "<notify_url>" + Configure.notify_url + "</notify_url>" + "<openid>" + orderInfo.getOpenId() + "</openid>" + "<out_trade_no>" + orderInfo.getOrderId() + "</out_trade_no>" + "<spbill_create_ip>" + ipAddr + "</spbill_create_ip>" + "<total_fee>" + String.valueOf(orderInfo.getMoney()) + "</total_fee>"//支付的金额,单位:分 + "<trade_type>" + Configure.TRADETYPE + "</trade_type>" + "<sign>" + sign + "</sign>" + "</xml>"; //调用统一下单接口,返回响应数据 String result = PayUtils.httpRequest(Configure.pay_url,"POST",xml); //将解析结果存入Map中 Map rsMap = PayUtils.doXMLParse(result); //返回状态码 String return_code = (String) rsMap.get("return_code"); String result_msg = (String) rsMap.get("result_msg"); String err_code = (String) rsMap.get("err_code"); String err_code_des = (String) rsMap.get("err_code_des"); if (return_code.equals("SUCCESS") || return_code.equals(return_code) ) { //返回的预付单信息 String prepay_id = (String) rsMap .get("prepay_id"); map.put("nonceStr", nonce_str); map.put("package", "prepay_id=" + prepay_id); Long timeStamp = System.currentTimeMillis() / 1000; //这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误 map.put("timeStamp", timeStamp + ""); //拼接签名需要的参数 String stringSignTemp = "appId=" + Configure.getAppId() + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id + "&signType=MD5&timeStamp=" + timeStamp; //再次签名,这个签名用于小程序端调用wx.requesetPayment方法 String paySign = PayUtils.sign(stringSignTemp, Configure.key, "utf-8").toUpperCase(); map.put("paySign", paySign); } else { logger.info("统一下单失败"); return map; } map.put("appid", Configure.getAppId()); } catch (Exception e) { e.printStackTrace(); } return map; } }

后端统一下单和支付回调接口

/** * @ClassName: WechatPayController * @Author: liuyaolong * @Description: 微信支付api控制层 * @Version: 1.0 */ @RestController @RequestMapping("/wechat") public class WechatPayController { @Resource private WeChatPayService weChatPayService; private static Logger logger = LoggerFactory.getLogger(WechatPayController.class); /** * 微信支付 * @param in * @param request * @param result * @return */ @PostMapping("/pay") public ResponseMsg<String> pay(@Valid @RequestBody RequestMsg<OrderInfo> in, HttpServletRequest request, BindingResult result) { ResponseMsg<String> response = new ResponseMsg<>(); Object object; try{ //获取客户端ip地址 String ip = IPAddr.getIpaddr(request); object = weChatPayService.WeChatPay(ip,in.getData()); response.setData(PayUtils.toJson(object)); } catch (Exception e) { e.printStackTrace(); } logger.info("调用微信支付接口,入参信息为:{}",in.getData()); return response; } /** * 支付回调接口 * @param request * @param response * @throws Exception */ @PostMapping("/notify") public void notify(HttpServletRequest request, HttpServletResponse response) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream())); StringBuilder sb = new StringBuilder(); BufferedOutputStream out = null; String line = null; try{ while((line = br.readLine()) != null){ sb.append(line); } //响应xml String notifyXml = sb.toString(); String resXml = ""; Map map = PayUtils.doXMLParse(notifyXml); String returnCode = (String) map.get("return_code"); if ("SUCCESS".equals(returnCode)) { //验证签名是否正确,回调验签时需要去除sign和空值参数 Map<String, String> validParams = PayUtils.paramsFilter(map); //把所有数据拼接成字符串 String prestr = PayUtils.createLinkString(validParams); //根据微信官网的介绍,此处不仅对回调的参数进行验签,还需要对返回的金额与系统订单的金额进行比对等 if (PayUtils.verify(prestr, (String) map.get("sign"), Configure.getKey(), "utf-8")) { /**此处添加自己的业务逻辑代码start**/ //注意要判断微信支付重复回调,支付成功后微信会重复的进行回调 /**此处添加自己的业务逻辑代码end**/ //通知微信服务器已经支付成功 resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> "; } } else { resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; } out = new BufferedOutputStream(response.getOutputStream()); out.write(resXml.getBytes()); out.flush(); logger.info("微信支付接口回调 ! "); } catch (Exception e) { e.printStackTrace(); } finally { br.close(); out.close(); } } }

 返回参数到前端起调微信支付

以上就是本文的全部内容,希望对大家的学习有所帮助,欢迎评论交流。能get到知识点不要忘了关注点赞~~~

最新回复(0)