java开发小程序-微信支付

废话不多说。

预下单文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=9_1

小程序文档:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5

前端部分要两步:

1、调起预支付,获得一些数据

2、然后将预支付数据用于调起微信支付

首先是小程序发起预支付:

start_pay: function (e) {
    var that = this;
  //获取session3rd var state = wx.getStorageSync("state") wx.getUserInfo({ success: res => { wx.request({ url: app.globalData.reqfristUrl + app.globalData.xmName + 'wx_pay/startPay', method: 'post', data: { payUser: wx.getStorageSync("users"),//支付对象,业务需要 payType:2,//支付类型,业务需要 session3rd: state,//必传,文档要求,session_key encryptedData: res.encryptedData,//必传,文档要求 iv: res.iv,//必传,文档要求 fee: that.data.moneyOnline//支付费用,一般业务也是要传的 }, success: function (res) { console.log(res.data) if(res.data.isCheck == "true"){ that.do_pay(res.data) }else{ utils.alertMsg("充值失败") } } }) } }) }

  

后端java预支付处理:

@ResponseBody
	@RequestMapping(value = "/startPay", method = RequestMethod.POST, produces = "application/json;charset=utf-8")
	public Object startPay(HttpServletRequest request, @RequestBody Map obj) {
		HashMap<String, Object> map = new HashMap<String, Object>();
		try {
		    //支付类型 1:其它 2:微信 3:微信代充 4:支付宝 5:支付宝代充
		    String payType = String.valueOf(obj.get("payType"));
		    //充值对象 自己或者别人的手机号码
		    String payUser = String.valueOf(obj.get("payUser"));
		    UserBeforeMoney userBeforeMoney = userService.findByMobile(payUser);//查找对应账户
		    if(userBeforeMoney == null) {
		        map.put("isCheck", "false");
                map.put("errorMsg", "账户不存在");
                return map;
		    }
		    //充值对象的userId
		    Long payUserId = userBeforeMoney.getUserId();
		    String fee = String.valueOf(obj.get("fee"));
		    //数字校验
		    String pattern = "([1-9]\\d*\\.?\\d*)|(0\\.\\d*[1-9])";
		    boolean check = Pattern.matches(pattern, fee);
		    if(!check) {
		        map.put("isCheck", "false");
		        map.put("errorMsg", "请输入正确的数字");
		        return map;
		    }
			String encryptedData = String.valueOf(obj.get("encryptedData"));
			String iv = String.valueOf(obj.get("iv"));
			//根据支付类型判断是去哪个redis库取session3rd和appId
			String session3rd = "";String appid = "";// 小程序ID
			if("2".equals(payType)) {
			    appid = AppID;//AppID是properties里面定义的常量
			    session3rd = jedisClientSingle.get(String.valueOf(obj.get("session3rd")));
			}
			if("3".equals(payType)) {
			    appid = Property_AppID;
                session3rd = jedisClientSingle_wy.get(String.valueOf(obj.get("session3rd")));
            }
			String[] session_key = session3rd.split(";");
			//解析用户信息用于获取openid和unionId
			JSONObject userInfo = EncryptedDataUtils.getUserInfo(encryptedData, session_key[1], iv);
			if (userInfo != null) {
				String unionId = userInfo.getString("userInfo");// 用户标识
				String openId = userInfo.getString("openId");// 用户标识
				
				//数据准备
				String timeStamp = new Date().getTime() / 1000 + "";
				String nonce_str = RandomUtil.getUUID();// 随机字符串
				String pack_age = "prepay_id=";
				String signType = "MD5";
				
				String mch_id = Mch_Id;// 商户号
				String key = Mch_Key;//商户key
				String paySign = "";
				String orderSn = RandomUtil.getOrderIdByUUId();//uuid当订单号

				// 生成预支付订单
				Map<String, Object> paramMap = new HashMap<String, Object>();
				paramMap.put("appid", appid);
				paramMap.put("mch_id", mch_id);
				paramMap.put("nonce_str", nonce_str);
				paramMap.put("sign_type", signType);
				paramMap.put("body", "微信支付");//这边windows server会出现中文乱码问题,结尾会给出解决方案
				paramMap.put("out_trade_no", orderSn);// 编号
				paramMap.put("total_fee", String.valueOf(new BigDecimal(fee).multiply(new BigDecimal(100)).intValue()));// 价格
				//paramMap.put("total_fee", "10");// 价格
				paramMap.put("openid", openId);// openId
				String ip = HttpUtil.getIpAddr(request);
                // String ip2 = HttpUtil.getIpAddrAdvanced(request);
				paramMap.put("spbill_create_ip", ip);// 用户端ip
				paramMap.put("trade_type", "JSAPI");// 这个api有,固定的
				paramMap.put("notify_url", "https://www.xxx.com/yac_web/wx_pay_notify/notify");//回调地址
				//字典序排序,最后附上key
				String sign2param = new String((MapUtil.sortMap(paramMap)+ "key=" + key).getBytes(),"utf-8");
				String pay_sign2 = MD5.md5(sign2param).toUpperCase();
				paramMap.put("sign", pay_sign2);
				//变成xml字符串
				String xmlParam=XmlUtil.getRequestXml(paramMap);
				String result = HttpUtil.httpsRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", "POST",
						xmlParam);
				log.info("预支付返回结果xml:" + result);
				
				//预支付订单xml,用来获取prepay_id
				Map<String,String> prepayResult = XmlUtil.xml2Map(result);
				// 小程序调起支付,所需参数
				map.put("appId", appid);
				map.put("nonceStr", nonce_str);
				map.put("package", pack_age+prepayResult.get("prepay_id"));
				map.put("signType", signType);
				map.put("timeStamp", timeStamp);
				String param = MapUtil.sortMap(map)+ "key=" + key;
				paySign=MD5.md5(param).toUpperCase();
				map.put("paySign", paySign);
				
				map.put("isCheck", "true");
			}
		} catch (Exception e) {
		    map.put("isCheck", "false");
		    map.put("errorMsg", "充值失败");
			log.error(e.getMessage(), e);
		}
		return map;
	}

 

获取ip:

/**
	 * 获取ip
	 * @param request
	 * @return
	 */
	public static String getIpAddr(HttpServletRequest request) {
		String ip = request.getHeader("x-forwarded-for");
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getHeader("WL-Proxy-Client-IP");
		}
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			ip = request.getRemoteAddr();
		}
		return ip;
	}

  

解密获取用户信息json对象:

/**
     * 获取信息
     */
    public static JSONObject getUserInfo(String encryptedData,String sessionkey,String iv){
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionkey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
               // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, "UTF-8");
                return JSONObject.parseObject(result);
            }
        } catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException | InvalidParameterSpecException | UnsupportedEncodingException | IllegalStateException | IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();
        }
        return null;
    }

  

字典序排序:

/**
	 * 字典序排序,返回参数拼接字符串
	 * @param paramMap
	 * @return 例如a="1"&b="2"&...
	 */
	public static String sortMap(Map<String, Object> paramMap){
		Collection<String> keyset2 = paramMap.keySet();
		List list2 = new ArrayList<String>(keyset2);
		Collections.sort(list2);
		// 这种打印出的字符串顺序和微信官网提供的字典序顺序是一致的
		String paramStr = "";
		for (int i = 0; i < list2.size(); i++) {
			paramStr = paramStr + list2.get(i) + "=" + paramMap.get(list2.get(i)) + "&";
		}
		return paramStr;
	}

 

map转xml:

/**
	 * map转xml
	 * @param parameters
	 * @return
	 */
    public static String getRequestXml(Map<String,Object> parameters){    
          StringBuffer sb = new StringBuffer();    
          sb.append("<xml>");    
          Set es = parameters.entrySet();
          Iterator it = es.iterator();    
          while(it.hasNext()) {    
              Map.Entry entry = (Map.Entry)it.next();    
              String key = (String)entry.getKey();    
              String value = (String)entry.getValue();    
              if ("attach".equalsIgnoreCase(key)||"body".equalsIgnoreCase(key)||"sign".equalsIgnoreCase(key)) {    
                  sb.append("<"+key+">"+"<![CDATA["+value+"]]></"+key+">");    
              }else {    
                  sb.append("<"+key+">"+value+"</"+key+">");    
              }    
          }    
          sb.append("</xml>");    
          return sb.toString();    
    }

  

xml2Map:

/**
	 * xml字符串转map
	 * @param xml
	 * @return
	 * @throws IOException
	 */
	public static Map<String, String> xml2Map(String xml) throws Exception {
		Map<String, String> map = new HashMap<String, String>();
		SAXReader reader = new SAXReader();
		setFeature(reader);
		InputStream is = new ByteArrayInputStream(xml.getBytes("utf-8"));
		Document doc = null;
		try {
			doc = reader.read(is);
			Element root = doc.getRootElement();
			List<Element> list = root.elements();
			for (Element e : list) {
				map.put(e.getName(), e.getText());
			}
		} catch (DocumentException e) {
			e.printStackTrace();
		} finally {
			is.close();
		}
		return map;
	}

  

/**
     * 禁用外部实体
     * @return
     * @throws SAXException 
     */
    private static void setFeature(SAXReader reader) throws SAXException {
        
        reader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        
        reader.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
        
        // 是否包含外部生成的实体。当正在解析文档时为只读属性,未解析文档的状态下为读写。
        reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
        
        // 是否包含外部的参数,包括外部DTD子集。当正在解析文档时为只读属性,未解析文档的状态下为读写。
        reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        
        reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        
        reader.setIncludeExternalDTDDeclarations(false);
        
    }

  

http请求:

//请求方法    
    public static String httpsRequest(String requestUrl, String requestMethod, String outputStr) {    
          try {    
                 
              URL url = new URL(requestUrl);    
              HttpURLConnection conn = (HttpURLConnection) 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();    
              inputStream = null;    
              conn.disconnect();    
              return buffer.toString();    
          } catch (ConnectException ce) {    
              log.error("连接超时:{}"+ ce,ce);    
          } catch (Exception e) {    
              log.error("https请求异常:{}"+ e,e);    
          }    
          return null;    
    }  

  

小程序调起支付:

do_pay: function (param) {
    var that = this;
    
    wx.requestPayment({
      'timeStamp': param.timeStamp,
      'nonceStr': param.nonceStr,
      'package': param.package,
      'signType': param.signType,
      'paySign': param.paySign,
      'success': function (res) {
          utils.alertMsg("充值成功")
      },
      'fail': function (res) {
        utils.alertMsg("充值失败")
        console.log(res)
      }
    })
  }

  

支付的回调:

@RequestMapping(value = "/notify", method = { RequestMethod.GET, RequestMethod.POST })
	public void startPay(HttpServletRequest request, HttpServletResponse response) {
		
		try {
			Map<String, Object> reqMap = XmlUtil.xml2Map(request);
			
			String openId = String.valueOf(reqMap.get("openid"));
			String outtradeno = String.valueOf(reqMap.get("out_trade_no"));
			String total_fee = String.valueOf(reqMap.get("total_fee"));
			BigDecimal fee = new BigDecimal(total_fee).divide(new BigDecimal(100));
			String time_end = String.valueOf(reqMap.get("time_end"));
			//TODO 自己的业务逻辑
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		
	}

  

 

注意:微信预支付过程中,商品描述因为使用中文而导致乱码使结果为前面不正确,此问题在windows server下才会发生,因为windows server编码是根据地区决定的,反正在中国肯定不是utf-8,所以要修改tomcat bin文件夹下面的catalina.bat文件

 

posted on 2018-08-12 20:15  康纳酱  阅读(474)  评论(0)    收藏  举报

导航