一、申请微信公众平台、微信商户号

1、首先需要去微信公众平台注册企业级小程序,个人账号是无法进行微信认证,没有认证的账号无法申请小程序支付功能(微信认证一次需要300元和一系列给腾讯账号打钱环节,太坑了,每次打钱几毛钱,手续费好几块),注册成功后会分配小程序的appID,后续对接支付时要用到。之后则可以申请微信支付,需要自己去申请商户号然后绑定小程序的appID。

java uniapp java uniapp微信小程序支付_java

2、申请商户号流程网上可以搜到,或者按照腾讯的步骤一步一步也能申请成功,这个不是很难。申请成功后,还需要填写一些基本设置都是程序中要用到的数据,比如商户平台的密钥key,之后可以绑定好小程序的ID,然后就可以设置支付了。在产品中心---》AppID账号管理里面关联小程序。

java uniapp java uniapp微信小程序支付_java uniapp_02

在我的产品中开通JSAPI支付权限

java uniapp java uniapp微信小程序支付_System_03

开发配置里面的JSAPI支付中的授权目录则是用来设置支付成功后的回调,比如微信支付成功后,微信会调用这个地址来反馈支付的结果,后端接收到后做一系列的处理。该地址必须是公网地址,域名可以是带端口号,亲测可以。http协议可以使用,建议是使用https。

java uniapp java uniapp微信小程序支付_System_04

以上设置完成即可。

二、uni-app+SSM框架对接微信支付

1、首先需要在支付页面的onLoad方法中调用uni.login来获取当前微信用户的code值,然后调用获取openID的方法。

onLoad() {
			const that=this
			uni.login({
			   success: res => {
			     //code值(5分钟失效)
			     console.info(res.code);
				 that.getOpenId(res.code);
			   }
			});
}

2、通过用户的code值调用Java后端接口,来获得openID。

async getOpenId(data){
				let jss = this.$store.state.user.JSESSIONID;
				let param = {
					mobileLogin: true,
					JSESSIONID: jss,
					code:data,
					};
				let type =  RS.RequestType.getOpenid;
				try {
//调用接口得到返回的openID
					var res = await RS.RequestServer.fetch(type, param);
				} catch (e) {
					console.log(e)
					uni.showToast({
						title: e.msg || e.errMsg,
						duration: 500,
					})
				}
				this.openid=res.data.body.openid
			}

3、Java后端获取到code值,调用微信提供的接口来获取openID

@ResponseBody
	@RequestMapping(value = "getOpenid")
    public AjaxJson getOpenid(String code) throws IOException {
		AjaxJson j = new AjaxJson();
        //wx接口路径
        String url = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code&" +"appid=" + APPID + "&secret=" + SECRET + "&js_code=" + code;
        //使用HttpClient发送请求
        CloseableHttpClient httpclient = HttpClients.createDefault();
        //发送Get请求
        HttpGet request = new HttpGet(url);
        request.addHeader("Content-Type", "application/json");
        //获得响应
        CloseableHttpResponse response = httpclient.execute(request);
        //拿到响应体
        HttpEntity httpEntity = response.getEntity();
        //使用工具转换
        String result = EntityUtils.toString(httpEntity, "UTF-8");// 转成string
        JSONObject jsonObject = JSONObject.parseObject(result);
        System.out.println(jsonObject);//拿到的所有内容
        String openid = jsonObject.get("openid").toString();
        System.out.println(openid);//拿到的openid
        j.setSuccess(true);
        j.put("openid", openid);
        return j;
    }

4、当用户输入金额,点击提交按钮,则需要把金额、openID先发送到后端进行处理,后端接收后生成订单然后调用微信接口把这些信息发送过去,发送之后把生成的订单信息反馈到支付页面,然后把这些信息放到uni.requestPayment里面。

async commit() {
				let jss = this.$store.state.user.JSESSIONID;
				let username = this.$store.state.user.username;
				var that=this
				uni.showLoading()
				let param = {
					mobileLogin: true,
					JSESSIONID: jss,
					money:this.money,
					openid:this.openid,
					};
    //调用接口把金额和openid发送到后端进行处理
				let type =  RS.RequestType.orderResult;
				try {
					var res = await RS.RequestServer.fetch(type, param);
				} catch (e) {
					console.log(e)
					uni.showToast({
						title: e.msg || e.errMsg,
						duration: 500,
					})
				}
				console.log(res)
    //把得到的结果取出来,然后调用uni.requestPayment,则会弹出填写支付密码的页面,支付成功则调用success,支付失败则调用fail。
				const configdata=res.data.body.orderResult
				this.orderNo=res.data.body.orderNo
				this.mchid=res.data.body.mchid
				uni.requestPayment({
					provider: 'wxpay',
					appId:configdata.appId,
					timeStamp: configdata.timeStamp,
					nonceStr: configdata.nonceStr,
					package: configdata.package,
					signType: "MD5",
					paySign: configdata.paySign,
					success: function(res) {
						console.log('success:' + JSON.stringify(res));
					},
					fail: function(err) {
						console.log('fail:' + JSON.stringify(err));
					}
				});
				 uni.hideLoading()
			}

5、Java后端的orderResult接口和回调方法,里面包含了一些处理数据的方法,所有处理微信支付的方法都在里面。

@Controller
@RequestMapping(value = "${adminPath}/miniprogram/miniprogram")
public class MiniprogramConfig extends BaseController {
	//小程序appid ,需要改为真实的
    private final static String APPID = "";

    //小程序secret ,需要改为真实的
    private final static String SECRET = "";
   
    @ResponseBody
	@RequestMapping(value = "getOpenid")
    public AjaxJson getOpenid(String code) throws IOException {
		AjaxJson j = new AjaxJson();
        //wx接口路径
        String url = "https://api.weixin.qq.com/sns/jscode2session?grant_type=authorization_code&" +
                    "appid=" + APPID + "&secret=" + SECRET + "&js_code=" + code;
        //使用HttpClient发送请求
        CloseableHttpClient httpclient = HttpClients.createDefault();
        //发送Get请求
        HttpGet request = new HttpGet(url);
        request.addHeader("Content-Type", "application/json");
        //获得响应
        CloseableHttpResponse response = httpclient.execute(request);
        //拿到响应体
        HttpEntity httpEntity = response.getEntity();
        //使用工具转换
        String result = EntityUtils.toString(httpEntity, "UTF-8");// 转成string
        JSONObject jsonObject = JSONObject.parseObject(result);
        System.out.println(jsonObject);//拿到的所有内容
        String openid = jsonObject.get("openid").toString();
        System.out.println(openid);//拿到的openid
        j.setSuccess(true);
        j.put("openid", openid);
        return j;
    }
    
    @ResponseBody
	@RequestMapping(value = "orderResult")
    public AjaxJson orderResult(Miniprogram miniprogram) throws IOException{
		AjaxJson j = new AjaxJson();
		
		//商户号
		String mch_id = "";
		//key为商户平台设置的密钥key
		String WXKeHukey = "";
		String appId = APPID;
		String notify_url = "https://www.hnskxxjsyxgs.cn/zzys/a/miniprogram/miniprogram/xcxNotify";//微信回调地址
		String trade_type = "JSAPI";
		//签名类型
		String SIGNTYPE = "MD5";
		//第一次发起支付请求的接口地址
		String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
		//生成的随机字符串
		String nonce_str = getRandomString(32);
		//本机的ip地址
		String spbill_create_ip = "";
		//商户订单号(先用时间代替),可以自己定义订单号格式
		Date date = new Date();
		String randomString = OrderUtils.getRandomString(5);
		String orderNo = "R"+randomString+date.getTime();
		Map<String, String> packageParams = new HashMap<String, String>();
		//小程序ID,微信分配的小程序ID
		packageParams.put("appid", appId);
		//商户号,微信支付分配的商户号
		packageParams.put("mch_id", mch_id);
		//随机字符串,长度要求在32位以内。
		packageParams.put("nonce_str", nonce_str);
		//商品简单描述
		packageParams.put("body", "用户充值");
		//商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
		packageParams.put("out_trade_no", orderNo);//商户订单号
		String money = miniprogram.getMoney().toString();
		String changeBranch = changeBranch(money);
		//订单总金额,单位为分
		packageParams.put("total_fee", changeBranch);
		//终端IP,支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IP
		packageParams.put("spbill_create_ip", spbill_create_ip);
		//异步接收微信支付结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。
		packageParams.put("notify_url", notify_url);
		//交易类型(JSAPI--JSAPI支付(或小程序支付)、NATIVE--Native支付、APP--app支付,MWEB--H5支付,不同trade_type决定了调起支付的方式,请根据支付产品正确上传)
		packageParams.put("trade_type", trade_type);
		//用户标识,trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。
		packageParams.put("openid", miniprogram.getOpenid());
		
		  // 除去数组中的空值和签名参数
		packageParams = paraFilter(packageParams);
		String prestr = createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
		
		//MD5运算生成签名,这里是第一次签名,用于调用统一下单接口,key为商户平台设置的密钥key
		String mysign = sign(prestr, WXKeHukey, "utf-8").toUpperCase();
		System.out.println("=======================第一次签名:" + mysign + "=====================");

		//拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
		String xml = "<xml version='1.0' encoding='gbk'>" + "<appid>" + appId + "</appid>"
		 + "<body><![CDATA[" + "用户充值" + "]]></body>"
		 + "<mch_id>" + mch_id + "</mch_id>"
		 + "<nonce_str>" + nonce_str + "</nonce_str>"
		 + "<notify_url>" + notify_url + "</notify_url>"
		 + "<openid>" + miniprogram.getOpenid() + "</openid>"
		 + "<out_trade_no>" + orderNo + "</out_trade_no>"
		 + "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>"
		 + "<total_fee>" + changeBranch + "</total_fee>"
		 + "<trade_type>" + trade_type + "</trade_type>"
		 + "<sign>" + mysign + "</sign>"
		 + "</xml>";
		 
		System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);
		
		//调用统一下单接口,并接受返回的结果
		String result = httpRequest(pay_url, "POST", xml);
 
		System.out.println("调试模式_统一下单接口 返回XML数据:" + result);
 
		// 将解析结果存储在HashMap中
//		Map map = doXMLParse(result);
		
 
//		String return_code = (String) map.get("return_code");//返回状态码
		
		//解析结果,将结果存在map中
		Map map = doXMLToMap(result);
		String return_code = (String) map.get("return_code");
		
        //返回给移动端需要的参数
        Map<String, Object> response = new HashMap<String, Object>();
        if(return_code == "SUCCESS" || return_code.equals(return_code)){
            // 业务结果
            String prepay_id = (String) map.get("prepay_id");//返回的预付单信息
            response.put("nonceStr", nonce_str);
            response.put("package", "prepay_id=" + prepay_id);
            Long timeStamp = System.currentTimeMillis() / 1000;
            response.put("timeStamp", timeStamp + "");//这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误


            String stringSignTemp = "appId=" + appId + "&nonceStr=" + nonce_str + "&package=prepay_id=" + prepay_id+ "&signType=" + SIGNTYPE + "&timeStamp=" + timeStamp;
            //再次签名,这个签名用于小程序端调用wx.requesetPayment方法
            String paySign = sign(stringSignTemp, WXKeHukey, "utf-8").toUpperCase();
            System.out.println("=======================第二次签名:" + paySign + "=====================");
            
            response.put("paySign", paySign);
            response.put("appid", appId);
        }
    		j.setSuccess(true);
    		j.put("orderResult", response);
    		j.put("orderNo", orderNo);
    		j.put("mchid", mch_id);
		    return j;
    }
    
    /**
	 * 解析xml,取出其中的result_code,和prepay_id
	 */
	public HashMap<String ,String> doXMLToMap(String results) {
		HashMap<String ,String> result = new HashMap<String ,String>();
		String return_code = results.substring(results.indexOf("<return_code><![CDATA[")+22, results.indexOf("]]></return_code>"));
		result.put("return_code", return_code);
		if(return_code.equals("SUCCESS")) {
			String prepay_id = results.substring(results.indexOf("<prepay_id><![CDATA[")+20, results.indexOf("]]></prepay_id>"));
			result.put("prepay_id", prepay_id);
		}
		return result;
		
	}
	/**
	 *
	 * @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();
	}

	/**
	 * 除去数组中的空值和签名参数
	 * 
	 * @param sArray 签名参数组
	 * @return 去掉空值与签名参数后的新签名参数组
	 */
	public static Map<String, String> paraFilter(Map<String, String> sArray) {
		Map<String, String> result = new HashMap<String, String>();
		if (sArray == null || sArray.size() <= 0) {
			return result;
		}
		for (String key : sArray.keySet()) {
			String value = sArray.get(key);
			if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
					|| key.equalsIgnoreCase("sign_type")) {
				continue;
			}
			result.put(key, value);
		}
		return result;
	}

	/**
	 * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
	 * 
	 * @param params 需要排序并参与字符拼接的参数组
	 * @return 拼接后字符串
	 */
	public static String createLinkString(Map<String, String> params) {
		List<String> keys = new ArrayList<String>(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.md5DigestAsHex(getContentBytes(text, input_charset)); 
  }
	
	/**
	 * @param content
	 * @param charset
	 * @return
	 * @throws 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);
		}
	} 
	 public static String getRandomString(int length){
	     String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	     Random random=new Random();
	     StringBuffer sb=new StringBuffer();
	     for(int i=0;i<length;i++){
	       int number=random.nextInt(62);
	       sb.append(str.charAt(number));
	     }
	     return sb.toString();
	 }
	 
	 /**
     *
     * 将元为单位的转换为分 (乘100)
     *
     * @param amount
     * @return
     */
    public static String changeBranch(Long amount) {
        return BigDecimal.valueOf(amount).multiply(new BigDecimal(100)).toString();
    }
 
    /**
     * 将元为单位的转换为分 替换小数点,支持以逗号区分的金额
     *
     * @param amount
     * @return
     */
    public static String changeBranch(String amount) {
        String currency = amount.replaceAll("\\$|\\¥|\\,", ""); // 处理包含, ¥
        // 或者$的金额
        int index = currency.indexOf(".");
        int length = currency.length();
        Long amLong;
        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();
    }
    @ResponseBody
    @RequestMapping(value="xcxNotify")
    public String xcxNotify(HttpServletRequest request,HttpServletResponse response) throws Exception {
	    	InputStream inputStream = request.getInputStream();
		    //获取请求输入流
		    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
		    byte[] buffer = new byte[1024];
		    int len = 0;
		    while ((len=inputStream.read(buffer))!=-1){
		    outputStream.write(buffer,0,len);
		    }
		    outputStream.close();
		    inputStream.close();
		    Map<String,Object> map = getMapFromXML(new String(outputStream.toByteArray(),"utf-8"));
		    logger.info("【小程序支付回调】 回调数据: \n"+map);
		    String resXml = "";
		    String returnCode = (String) map.get("return_code");
		    if ("SUCCESS".equalsIgnoreCase(returnCode)) {
		    String returnmsg = (String) map.get("result_code");
		    if("SUCCESS".equals(returnmsg)){
//支付成功则会收到微信回调反馈的信息,可以在下面做一些逻辑处理
		    String totalFee = (String) map.get("total_fee");//充值金额
		    String mchid = (String) map.get("mch_id");//商户号
		    String orderNumber = (String) map.get("out_trade_no");//订单号

		   //如果不返回resXml,则微信回调会一直调用,直到你返回支付成功的resXml才停止

		    //支付成功
		    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>";
		    logger.info("支付失败:"+resXml);
		    }
		    }else{
		    resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
		    + "<return_msg><![CDATA[报文为空]></return_msg>" + "</xml>";
		    logger.info("【订单支付失败】");
		    }
		    logger.info("【小程序支付回调响应】 响应内容:\n"+resXml);
		    Thread.sleep(1000);
		    return resXml;
    }
    public static Map<String, Object> getMapFromXML(String responseString) {
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            SAXReader reader = new SAXReader();
            Document doc;
            doc = reader.read(new ByteArrayInputStream(responseString.getBytes("UTF-8")));
//            doc = reader.read(responseString);
            Element root = doc.getRootElement();
            List<Element> list = root.elements();
            for (Element element : list) {
                map.put(element.getName(), element.getText());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return map;
    }
    
    /**

     * 金额为分的格式

     */

     public static final String CURRENCY_FEN_REGEX = "\\-?[0-9]+";

     /**

     * 将分为单位的转换为元 (除100)

     *

     * @param amount

     * @return

     * @throws Exception

     */

     public static String fen2YuanStr(String amount) {
     if (!amount.matches(CURRENCY_FEN_REGEX)) {
     throw new RuntimeException("金额格式错误|"+amount);

     }

     return formatFen(BigDecimal.valueOf(Long.valueOf(amount)).divide(new BigDecimal(100)));

     }

     /**

     * 格式化数字

     * @param fen

     * @return

     */

     private static String formatFen(BigDecimal fen){
     DecimalFormat df1 = new DecimalFormat("0.00");

     return df1.format(fen);

     }

     /**

     * 将元为单位的参数转换为分 , 只对小数点前2位支持

     *

     * @param yuan

     * @return

     * @throws Exception

     */

     public static String yuan2FenInt(String yuan){
     BigDecimal fenBd = new BigDecimal(yuan).multiply(new BigDecimal(100));

     fenBd = fenBd.setScale(0, BigDecimal.ROUND_HALF_UP);

     return String.valueOf(fenBd.intValue());

     }