小程序支付完整过程。足够详细!

2023-11-17

1、注册小程序
拿到App_idAppSecret 小程序密钥
取得商户的微信支付商户号 MCHID 和 微信支付密钥 APIKEY

2、流程 微信用户在微信端选商品下单-------》后台响应下单生成单号,产生第一次签名数据,提交微信统一支付接口,得到返回数据,支付单号,二次签名-----返参微信端----》微信端发起支付--------支付成功失败,微信支付平台发送消息给提供的地址返回数据。
2.1.1 小程序中用户选择商品下单。
【小程序】中,用户选中商品,数量,下单------提交-----》【后台】接收下单数据。验证数据,成功入库后,
**后台对微信支付接口发起支付请求。**后台进行第一次签名验算。
必须参数如下:

	SortedMap<String,String> resultmap  = new TreeMap<String,String>();//sortedMap 是有序排列 接口是TreeMap不是HashMap

    	resultmap.put("appid" , app_id);								//APP应用ID  		必须
    	resultmap.put("attach", "test");								//订单附加信息  
    	resultmap.put("mch_id" , mch_id);								//直连商户号		必须
    	resultmap.put("body", order.getOrder_no());						//产品信息。		必须
    	resultmap.put("nonce_str" , nonce_str); 						// 随机字符串不大于32位   必须
    	resultmap.put("notify_url", "https://www......./notify");	//通知地址 接收2.1.4步骤微信端支付后,微信支付平台给来的消息,需要提供的后台处理地址,   必须
    	resultmap.put("openid", open_id);   //发起支付用户的open_id 。必须
    	resultmap.put("out_trade_no" , order.getOrder_no());  //商户订单号  必须
    	resultmap.put("spbill_create_ip", requestIp(request));   //客户ip地址记录
    	resultmap.put("total_fee",String.valueOf(order.getPay_price()*100).substring(0, String.valueOf(order.getPay_price()*100).lastIndexOf(".")));   //支付金额按分计算。必须
    	resultmap.put("trade_type" , "JSAPI");   //支付接口选择,这里选择统一支付接口。必须

这个map文件。需排序。所以用SortedMap格式存储,因为转url字符串 后。再加密验算时候 key需要按ask码排序,再进行MD5加密验算。
String url = mapToUrl(resultmap); //这里用了一个map转str的工具类。

private static String  makeSign(String str){
		//生成签名
		return DigestUtils.md5DigestAsHex(str.getBytes());
		
	}	
	//map转url。
	private static String  mapToUrl(SortedMap<String,String> map){
		//生成url map.entrySet()
		String buffer ="";
		Set<Map.Entry<String,String>> map2 = map.entrySet();
		for(Map.Entry<String, String> s: map2){
			buffer+=s.getKey()+"="+s.getValue()+"&";
		}
		return buffer;
	}
	     	String signstr = url + "key="+  wxapp.getApikey();   //这个就比较关键了。这个字符串是从上面一系列参数转换成字符串后,本地加密验算的字符串。转化为字符串   “appid=*********&attach=*******............&“  + 最尾后再加上      **key=微信支付密钥APIKEY**
	     	String sign = makeSign(signstr).toUpperCase() ;   //MD5加密后,再转换成大写。这个sign。可以用微信开发平台提供的工具进行核算,验证。sign和上面signstr在平台算出来的结果是否一致。

(签名校验工具)
2.1.2 后台算签名,及把签名加入到xml中去。对统一支付接口https://api.mch.weixin.qq.com/pay/unifiedorder发起提交。xml post提交得到返回数据xml格式的。requestmap.put("sign", sign );
//好了。上面第一次签名加密串有了。在这里加入到map中去。才组合成了完整的提交到微信支付接口的参数。这个格式肯定不接受。需要转换为xml格式。

	String xml = "";
		     		xml = MapToXml.mapToXml(resultmap);		//这里用了一个工具类,转换为了xml格式的串。后台这里就要对微信支付接口进行一个访问提交,同时把参数,用xml的格式进行提交。
					CloseableHttpClient **httpClient** = HttpClientBuilder.create().build();
		 			// 创建Post请求
		 			HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");  //微信小程序的开发文档坑爹,v3和统一支付,初始根本分布清楚。乱套了。一直提交给的的是v3的地址。总是验证错误。
		 			StringEntity entity = new StringEntity(xml, null, "UTF-8", false);
		 			// post请求是将参数放在请求体里面传过去的;这里将entity放入post请求体中
		 			httpPost.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36");
		 			httpPost.setHeader("Content-Type", "application/json;charset=utf8");
		 			httpPost.setHeader("Accept", "application/json;charset=utf8");
		 			httpPost.setEntity(**entity**);

这样后台提交就完成了。
2.1.3接收微信接口返回的xml,取值,本地时间戳,不大于32位的随机串。再次md5加密签名。
接收返回的参数。
CloseableHttpResponse response = null; Map<String, String> remap = new HashMap<String,String>(); // 由客户端执行(发送)Post请求 response = httpClient.execute(httpPost); // 从响应模型中获取响应实体 HttpEntity responseEntity = response.getEntity(); if (responseEntity != null) { System.out.println("响应内容长度为:" + responseEntity.getContentLength()); remap = XmlToMap.getXmlBodyContext(EntityUtils.toString(responseEntity)); } //这里就是接收到的微信返回数据。返回的格式一样是xml格式的。这里用了一个工具类。对数据进行整理到map中。这个map中有几个关键key-value。这个是需要后台返回给小程序中给用户的。
paysignMap.put(“appId”, app_id); //小程序的app_id
paysignMap.put(“nonceStr”, nonce_str); //随机字符串。第二次签名需要加入的。这个可以重新再生成一个。也可以拿之前的。
paysignMap.put(“package”, “prepay_id=” + prepay_id); //支付订单号
paysignMap.put(“signType”, “MD5”); //验算加密的方式
paysignMap.put(“timeStamp”, time2); //时间戳
把这个再次传换成url的 appid=&nonceStr=&package=… + 再加上 key=微信支付密钥APIKEY
String paysignurl = mapToUrl(paysignMap);
paysignurl += “key=”+ wxapp.getApikey();
String paysign = makeSign(paysignurl).toUpperCase();
再次md5验算。可以去微信平台工具那里验算。但有个巨坑。微信网站平台的验算。在package=parepay_id=wx
**订单号。他会过滤掉后面的订单号。验算不会相等自己平台后面的MD5加密串。 这里可以把自己的urlstr 转xml 。再把xml提供到网站验算平台,进行验算。看是不是能够一样。后台还是按url方式进行加密算。但去网站上检验时,用XML的格式去复制粘贴。就能能看到一样了。这个困扰了大天半。这样第二次签名验算的加密串就拿到了。
把下面四个必要参数返回给微信用户。以及下单成功,发起支付的消息一并发给微信端。
towxmap.put(“timeStamp”, time); //时间戳
towxmap.put(“nonceStr”, nonce_str); //随机串
towxmap.put(“prepay_id”, prepay_id); //在微信支付返回来的支付订单号
towxmap.put(“paySign”, paysign); //后台二次签名
2.1.4小程序接收到必要参数,调用wx.requestPayment.发起支付。提供下面正确的参数。即可正常支付。
小程序调用wx.requestPayment 发起支付 发送必须参数如下:

	timeStamp: result.data.payment.timeStamp,								//后台二次签名时,用的时间戳,返回过来的。
    nonceStr: result.data.payment.nonceStr,									//后台二次签名时候。用到的随机串
    package: 'prepay_id=' + result.data.payment.prepay_id,    		//注意这里和后台返回的不同。前面加了'prepay_id='支付订单号
    signType: 'MD5',																		   //加密方式
    paySign: result.data.payment.paySign,					

2.1.5 前面提到notify。后台接收微信支付成功失败的网址。接收的数据一样是xml格式。必须要给微信接口返回消息。不然会一直发。
再根据微信发过来的支付结果消息,进行订单更新处理,改变订单状态,和给用户发送消息。因为各方面数据通讯不知道正常否,不方便调试的话,可以自己模拟一个发送接收页面。xml传递数据。数据处理完全正常了。再转微信支付接口。这样就能很快的测试好。

@RequestMapping(value="wxclient/notify")
	@ResponseBody
	public void notify(HttpServletRequest request,HttpServletResponse response) throws Exception{
		//request.setCharacterEncoding("utf-8"); // 接收格式指定
		//String path = request.getServletContext().getRealPath("/WEB-INF/temple/default/");
		String result = "";
		BufferedReader in = null;
		request.setCharacterEncoding("UTF-8");
		//InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
		in = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
		String line;
		while ((line = in.readLine()) != null) {
			result += line;
		}

	//ObjectMapper MAPPER = new ObjectMapper();
	//Map<String, Object> jsonmap = MAPPER.readValue(result, HashMap.class);
		SortedMap<String,String> map2 = new TreeMap<String,String>();
		map2 = XmlToMap.getXmlBodyContext(result);  //获得的result来的格式为xml,这里xml转map
		
		String signurl = mapToUrlQuSign(map2);			//map再转url后,加key。再md5算sign验证码。
		WxApp wxapp = *****.SelectWxApp(10001);	//暂时直接用的wxapp_id没有查询。
		signurl += "key="+  wxapp.getApikey();
		map2.put("signurl", signurl);
		System.out.println(signurl);
		String sign2 = makeSign(signurl).toUpperCase();   //对本站内的数据验算。
    	map2.put("sign2", sign2);
    	System.out.println(map2);
    	
    	Constants.map = map2;    //得到返回值,给到一个全局变量供查看。
    	
    	SortedMap<String,String> map = new TreeMap<String,String>();

    	if(sign2!=null && sign2.equals(map2.get("sign"))){  //比较本站内的数据加密 和 返回的加密串  是否一致。一致则证明是一个订单发起的消息回应。再根据消息内容对订单做出正确的处理。
    		Order order = new Order();
			int update_time = Integer.parseInt(String.valueOf(System.currentTimeMillis()/1000));
			//order.setUser_id(Integer.parseInt((String)redisUtil.get(token)));
			order.setWxapp_id(10001);
			order.setOrder_no(map2.get("out_trade_no"));
			order.setUpdate_time(update_time);		
			order.setPay_time(StampToData.dateToStamp(map2.get("time_end")));
			order.setTransaction_id(map2.get("transaction_id"));
			Service.OrderPayUpdateByOrderId(order);
			System.out.println(order);
			map.put("result_code","SUCCESS");		
    	}else{
    		map.put("result_code", "FAIL");
    	}
    	String xml = "";
    	xml = MapToXml.mapToXml(map);				//返回给支付平台成功消息。map转xml。
    	
    	response.setCharacterEncoding("UTF-8");
		response.getWriter().println(xml);

	}

XmlToMap

public class XmlToMap{
	
	 public static Document parseXmlString(String xmlStr){
	 try{
         InputSource is = new InputSource(new StringReader(xmlStr));
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         DocumentBuilder builder=factory.newDocumentBuilder();
         Document doc = builder.parse(is);
         return doc;
     }catch(Exception e){
         e.printStackTrace();
     }
     return null;
 }

 public static SortedMap<String, String> getXmlBodyContext(String bodyXml){

     SortedMap<String, String> dataMap = new TreeMap<String,String>();

     Document doc = parseXmlString(bodyXml);
     if(null != doc){
         NodeList rootNode = doc.getElementsByTagName("xml");
         if(rootNode != null){

             Node root = rootNode.item(0);
             NodeList nodes = root.getChildNodes();
             for(int i = 0;i < nodes.getLength(); i++){
                 Node node = nodes.item(i);
                 dataMap.put(node.getNodeName(), node.getTextContent());
             }
         }
     }
     return dataMap;
 }


}

MapToXml 两个方式,都可以用。

public class MapToXml {

	public static boolean isNumeric(String str){
	    for(int i=str.length();--i>=0;){
	        int chr=str.charAt(i);
	        if(chr<48 || chr>57)
	            return false;
	    }
	   return true;
	}
	
	public static String mapToXml(SortedMap<String, String> map) throws Exception {
	  /*  if (map.size()==0) {
	            return "fail";
	        }
	    
	        String xml = "<xml>";
	        for(Entry<String,String> s : map.entrySet()) {
	           if(isNumeric(s.getValue())){ 
	        	   xml += "<" + s.getKey() + "><![CDATA[" + s.getValue() + "]]></" + s.getKey() + ">"; 
	           }else{        	   
	        	   xml += "<" + s.getKey() + ">" + s.getValue() + "</" + s.getKey() + ">";
	           }
	        	   
	           
	        }
	        xml += "</xml>";
	        return xml;
	}*/
		 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
        //防止XXE攻击
        documentBuilderFactory.setXIncludeAware(false);
        documentBuilderFactory.setExpandEntityReferences(false);
        DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
        org.w3c.dom.Document document = documentBuilder.newDocument();
        org.w3c.dom.Element root = document.createElement("xml");
        document.appendChild(root);
        for (String key: map.keySet()) {
            String value = map.get(key);
            if (value == null) {
                value = "";
            }
            value = value.trim();
            org.w3c.dom.Element filed = document.createElement(key);
            filed.appendChild(document.createTextNode(value));
            root.appendChild(filed);
        }
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        DOMSource source = new DOMSource(document);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        StringWriter writer = new StringWriter();
        StreamResult result = new StreamResult(writer);
        transformer.transform(source, result);
        String output = writer.getBuffer().toString();
        try {
            writer.close();
        }
        catch (Exception ex) {
        }
        return output;
    }

}

HttpClient 需要 httpcomponents-client-5.1-bin 支持。https://dlcdn.apache.org//httpcomponents/httpclient/binary/httpcomponents-client-5.1-bin.zip

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

小程序支付完整过程。足够详细! 的相关文章

随机推荐

  • TreeMap 的特点

    TreeMap基于红黑树实现 增删改查的平均和最差时间复杂度均为O 最大特点时Key有序 key必须实现Comparable接口或者提供Comparator比较器 所以key不允许为null HashMap 依靠hashCode和equal
  • web移动端适配方案以及不同单位之间的区别

    web移动端适配方案 第一种 rem实现原理 rem是一个倍数单位 它是基于html的font size的倍数 只要我们在不同的设备上设置一个合适的初始值 当设备发生变化font size就会自动等比适配大小 从而在不同的设备上表现统一 如
  • 注意力模型CBAM

    论文 CBAM Convolutional Block Attention Module Convolutional Block Attention Module CBAM 表示卷积模块的注意力机制模块 是一种结合了空间 spatial 和
  • 【Vue2】之简单自定义插件开发,含demo

    一个vue2 x的简单插件开发实例 首先创建文件夹和文件 个人习惯把插件都放在src plugins文件夹里 创建本次demo插件目录src plugins demo 以及目录文件 demo index js demo src main j
  • python,求解字符串的所有子串

    网上的一种解法 def cut s str results num 0 x 1 表示子字符串长度 for x in range len s i 表示偏移量 for i in range len s x results append s i
  • 部署 - 前后端发布策略

    前端发布策略 前端发布的本质是静态资源的发布 主要关心缓存和资源同步问题 HTTP缓存 合理的使用缓存让未修改的文件复用可以有效的减轻服务器负担和提高前端页面渲染效率 1 协商缓存 2 本地缓存 本地缓存无需跟服务器再次确认 直接根据文件名
  • CORS policy: header is present on the requested

    CORS policy header is present on the requested 欢迎使用Markdown编辑器 解决方法 请求结果 欢迎使用Markdown编辑器 Access to XMLHttpRequest at htt
  • spring boot读取pom.xml变量

    1 application yml配置 version project version 项目版本号 为什么不使用 project version 呢 避免与避免与Spring语法冲突 项目pom继承了spring boot starter
  • DCDC相关

    1 同步和异步 同步 MOSFET管 效率高 价格贵 零件数多 电路复杂 异步 二极管 效率低 价格低 电路简单 同步整流上管S1和下管S2需要一相同频率信号以互补方式进行驱动 保证S1导通时S2截止 S1截止S2导通 异步二极管损耗 在电
  • plsql 修改sql窗口字体

    工具 首选项 用户界面 字体 编辑器 选择
  • Android自定义一个广播接收器BroadcastReceiver监听系统wifi连接

    概述 注册一个广播用来接收系统发送的广播 比如 发送或接收到一个短信 用Toast或Notification通知提醒 或者是打开或者断开网络连接 用Toast做出提示 注册文件
  • uniapp-计算属性、watch 侦听器、props验证

    一 计算属性 计算属性本质上就是一个 function 函数 它可以实时监听 data 中数据的变化 并 return 一个计算后的新值 1 声明与使用计算属性 计算属性需要以 function 函数的形式声明到组件的 computed 选
  • win11不兼容很多游戏?win11不兼容哪些游戏

    很多用户升级win11系统之后 最担心的就是win11兼容性不强 很多游戏都玩不了 那到底win11不兼容哪些游戏 下面小编就来给大家讲讲 win11很多游戏不兼容 1 其实win11系统并没有那么多无法兼容的游戏 基本上win10可以兼容
  • java插入gif_Java swing(纯代码和含部分个人解析)插入png或gif图片的方法,切换界面功能的实现...

    package swing public class mains public static void main String args new swing package swing import java awt Color impor
  • 听说,你想做大模型时代的应用层创业!

    亲爱的科技探险家们和代码魔法师们 未来的钟声已经敲响 预示着一场极度炫酷的虚拟现实游戏即将展开 从初期简单的智能识别 到设计师级别的图纸设计 生成式AI技术 Generative AI 以其独特理念和创新模式重塑了传统内容生产效率和交互模式
  • Unity手机端3档震动

    using System Collections using System Collections Generic using UnityEngine public class VibrateHelper MonoBehaviour sta
  • elementui dialog组件固定高度

    弹窗高度过大 想设置个自适应的高度 固定头尾 deep el dialog margin 5vh auto important deep el dialog body height 70vh overflow auto margin hei
  • ChatGPT救命!4岁男孩3年求医17位专家无果,大模型精准揪出病因

    克雷西 萧箫 发自 凹非寺量子位 公众号 QbitAI 怪病 缠身3年求医无果 最终竟然被ChatGPT成功诊断 这是发生在一名4岁男孩身上的真实经历 某次运动后 他身体开始剧痛 母亲前后带她看了17名医生 从儿科 骨科到各种专家 先后进行
  • DeferredResult的使用场景及用法

    场景 假设我们现在要实现这样一个功能 浏览器要实时展示服务端计算出来的数据 一种可能的实现是 浏览器频繁 例如定时1秒 向服务端发起请求以获得服务端数据 但定时请求并不能 实时 反应服务端的数据变化情况 若定时周期为S 则数据延迟周期最大即
  • 小程序支付完整过程。足够详细!

    1 注册小程序 拿到App id 和 AppSecret 小程序密钥 取得商户的微信支付商户号 MCHID 和 微信支付密钥 APIKEY 2 流程 微信用户在微信端选商品下单 后台响应下单生成单号 产生第一次签名数据 提交微信统一支付接口