之前有做了小程序的微信支付以为app的小程序的流程应该差不多,就直接把代码c过来然后发现不行,踩了一堆坑,硬生生耗了俩周(中间还有其他事情耽搁了)
小程序调起微信支付

准备工作

微信商户平台注册
微信开放平台注册
注册会需要营业执照,申请支付接口的话会需要300元的审核费用,具体就不多说了,这个不难而且网上教程一堆。

先上代码吧,最后再说踩的坑

后台代码

package com.ruoyi.web.controller.defalt;

import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.domain.COrder;
import com.ruoyi.system.domain.CUser;
import com.ruoyi.system.service.ICOrderService;
import com.ruoyi.system.service.ICUserService;
import com.ruoyi.util.RedisUtil;
import com.ruoyi.util.wx.PayForUtil;
import com.ruoyi.util.wx.WXPayUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.*;
import java.util.*;

/**
 * 登录注册Controller
 * 
 * @author 浮生
 * @date 2019-09-16
 */
@Controller
@RequestMapping("/front")
public class FrontBuyController extends BaseController {
	@Autowired
	RedisUtil redis;
	
    @Autowired
    private ICOrderService orderService;
    
    @Autowired
    private ICUserService cUserService;
    
    
	String appId = "微信开放平台申请的移动端的appid";
	// 商户号
	String partnerId = "微信商户平台的商户号";
	//商户号
	String mch_id = partnerId;
	//扩展字段,暂填写固定值Sign=WXPay,java无法使用package关键字作为变量名所以用packageValue替代
	String packageValue = "Sign=WXPay";
	// 第一次发起支付请求的接口地址
	String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
	// key为商户平台设置的密钥key
	String WXKeHukey = "商户平台设置的密钥";
	// 本机的ip地址
	String spbill_create_ip = "本机的ip地址";
	// 支付回调地址
	String notify_url = "支付回调地址";
	// 调起支付方式
	String trade_type = "APP";
	// 签名类型
	String SIGNTYPE = "MD5";

	/**
	 * a调起微信预支付接口
	 * 
	 * @param money:支付金额
	 * @return
	 * @throws UnsupportedEncodingException 
	 */
	@GetMapping("/buyStepOne")
	@ResponseBody
	@CrossOrigin(allowCredentials = "true")
	public AjaxResult buyStepOne(Long money,String redisKey) throws UnsupportedEncodingException {
		if(redis.hasKey(redisKey)) {
			//获取当前用户id
			Long userId = Long.parseLong(redis.get(redisKey).toString());
			// 通过course_id查询购买的课程信息
			System.out.println(money);
			
			// 生成的随机字符串
			String nonce_str = StringUtils.getRandomString(32);
			// 商品名称
			String body = new String("shuangxi-huiyuan".getBytes("ISO-8859-1"), "UTF-8");

			// 商户订单号(先用时间加上3位随机数代替)
			Date date = new Date();
			String orderNo = date.getTime() + StringUtils.getRandomString(3) + "";

			Map<String, String> packageParams = new HashMap<String, String>();
			// 小程序ID,微信分配的小程序ID
			packageParams.put("appid", appId);
			// 小程序ID,微信分配的小程序ID
			packageParams.put("attach", userId.toString());
			
			// 商户号,微信支付分配的商户号
			packageParams.put("mch_id", mch_id);
			// 随机字符串,长度要求在32位以内。
			packageParams.put("nonce_str", nonce_str);
			// 商品简单描述
			packageParams.put("body", body);
			// 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
			packageParams.put("out_trade_no", orderNo);// 商户订单号
			// 订单总金额,单位为分
			packageParams.put("total_fee", money.toString());
			// 终端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);

			// 除去数组中的空值和签名参数
			packageParams = WXPayUtils.paraFilter(packageParams);
			String prestr = WXPayUtils.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
			System.out.println("签名数据"+prestr);
			// MD5运算生成签名,这里是第一次签名,用于调用统一下单接口,key为商户平台设置的密钥key
			String mysign = WXPayUtils.sign(prestr, WXKeHukey, "utf-8").toUpperCase();
			System.out.println("=======================第一次签名:" + mysign + "=====================");

			// 拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
			String xml = "<xml version='1.0' encoding='gbk'>" 
					+ "<appid>" + appId + "</appid>" 
					+ "<attach>" + userId + "</attach>" 
					+ "<body><![CDATA["+body+"]]></body>" 
					+ "<mch_id>" + mch_id + "</mch_id>" 
					+ "<nonce_str>" + nonce_str + "</nonce_str>"
					+ "<notify_url>" + notify_url + "</notify_url>" 
					+ "<out_trade_no>" + orderNo + "</out_trade_no>"
					+ "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" 
					+ "<total_fee>" + money + "</total_fee>" 
					+ "<trade_type>" + trade_type + "</trade_type>" 
					+ "<sign>" + mysign + "</sign>"
					+ "</xml>";

			System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);

			// 调用统一下单接口,并接受返回的结果
			String result = WXPayUtils.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 = WXPayUtils.doXMLToMap(result);
			System.out.println(map);
			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", (String) map.get("nonceStr"));
				response.put("package", "prepay_id=" + prepay_id);
				Long timeStamp = System.currentTimeMillis() / 1000;
				response.put("timeStamp", timeStamp + "");// 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误

				String stringSignTemp = "appId=" + appId + "&nonceStr=" +  (String) map.get("nonceStr") + "&package=prepay_id=" + prepay_id
						+ "&signType=" + SIGNTYPE + "&timeStamp=" + timeStamp;
				// 再次签名,这个签名用于小程序端调用wx.requesetPayment方法
//				String paySign = WXPayUtils.sign(stringSignTemp, WXKeHukey, "utf-8").toUpperCase();
//				System.out.println("=======================第二次签名:" + paySign + "=====================");

//				response.put("paySign", paySign);
				response.put("appid", appId);
				response.put("sign", (String) map.get("sign"));
				response.put("mysign",mysign);
				// 将订单和报名信息添加到数据库
				COrder c_order = new COrder();
				c_order.setCreateTime(new Date());
				c_order.setOrderMoney(money);
				c_order.setOrderFlag(0);
				c_order.setOrderNumber(orderNo);
				c_order.setUserId(userId);
				orderService.insertCOrder(c_order);
				
			}
			System.out.println(response);
			AjaxResult resultall = new AjaxResult();
			return resultall.success(response);
		}else {
			return error("未登陆");
		}
		
	}

	/**
	 * 微信支付回调方法
	 */
	@GetMapping("/buySteptwo")
	@ResponseBody
	@CrossOrigin(allowCredentials = "true")
	public AjaxResult buySteptwo() {
		try {
			//读取参数  
			InputStream inputStream ;  
			StringBuffer sb = new StringBuffer();  
			inputStream = getRequest().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, String> m = new HashMap<String, String>();  
			m = doXMLParse(sb.toString());
	        //过滤空 设置 TreeMap  
	        SortedMap<Object,Object> packageParams = new TreeMap<Object,Object>();        
	        Iterator<String> it = m.keySet().iterator();  
	        while (it.hasNext()) {  
	            String parameter = it.next();  
	            String parameterValue = m.get(parameter);  
	            String v = "";  
	            if(null != parameterValue) {  
	                v = parameterValue.trim();  
	            }  
	            packageParams.put(parameter, v);  
	        }  
	        // 微信支付的API密钥  
	        String key = WXKeHukey; // key  
	        //判断签名是否正确  
        	if(PayForUtil.isTenpaySign("UTF-8", packageParams,key)) {  
	            //------------------------------  
	            //处理业务开始  
	            //------------------------------  
	            String resXml = "";  
	            if("SUCCESS".equals((String)packageParams.get("result_code"))){ 
	            	System.out.println("+++++++++++++++++++回调参数+++++++++++++++++++");
	            	System.out.println(packageParams);
	                // 这里是支付成功  
		            //执行自己的业务逻辑开始
		            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");//是否关注公众号
	                //公用回传参数 我设置的是 "优惠券ID;课程Type:用户ID"
	                String attach = (String)packageParams.get("attach");
	                //商户订单号
	                String out_trade_no = ((String)packageParams.get("out_trade_no"));  
	                out_trade_no = out_trade_no.replace(" ", "");
	                //付款金额【以分为单位】
	                String total_fee = (String)packageParams.get("total_fee"); 
	                //微信生成的交易订单号
	                String transaction_id = (String)packageParams.get("transaction_id");//微信支付订单号
	                //支付完成时间
	                String time_end=(String)packageParams.get("time_end");
//			                //回调一次后进行判断,查询是否以及添加记录到数据库
//			                Record huidiao = Db.findById("c_order" , "order_number", transaction_id);
//			                if(huidiao != null) {
//			                	redirect("/");
//			                	return;
//			                }
	                //通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.  
	                resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"  
	                        + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";  
	                /**
	                 * 	支付成功后的处理
	                 */
	                // 更新订单数据
					Long userId = Long.parseLong(attach);
					COrder c_order = new COrder();
					c_order.setOrderNumber(out_trade_no);
					List<COrder> orderList = orderService.selectCOrderList(c_order);
					if(orderList.size()>=1) {
						c_order = orderList.get(0);
						c_order.setCreateTime(new Date());
						c_order.setOrderMoney(Long.parseLong(total_fee));
						c_order.setOrderFlag(1);
						c_order.setUserId(userId);
						orderService.updateCOrder(c_order);
						//查询会员月费
						Long monthMoney = orderService.selectMonthMoney();
						//计算充值多少个月的会员
						long month = Long.parseLong(total_fee)/monthMoney;
						//查询用户信息
						CUser cUser = new CUser();
						cUser.setUserId(userId);
						List<CUser> userList = cUserService.selectUserInfoList(cUser);
						//根据用户vip剩余时间处理业务
						if(userList.size()<=0) {
							cUser = userList.get(0);
							Calendar userVip = Calendar.getInstance();
							userVip.setTime(cUser.getUserVip());
							Calendar now = Calendar.getInstance();
							userVip.setTime(new Date());
							if(userVip.after(now)) {
								//会员未过期
							}else {
								//会员已过期,或者未充值过会员
								userVip = now;
							}
							userVip.add(Calendar.MONTH, (int)month);
							cUser.setUserVip(userVip.getTime());
							cUserService.updateCUser(cUser);
							return success();
						}else {
							System.out.println("查询不到用户");
							return error("查询不到用户");
						}
		                
					}else {
						System.out.println("获取订单失败请与管理员联系");
						return error("获取订单失败请与管理员联系");
					}
					
	                
	            } else {  
	                System.out.println("支付失败,错误信息:" + packageParams.get("err_code"));  
	                resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"  
	                        + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> "; 
	                //TODO 支付失败要做什么 我这里先不写
		            //------------------------------  
		            //处理业务完毕  
		            //------------------------------  
		            BufferedOutputStream out = new BufferedOutputStream(  
		                    getResponse().getOutputStream());  
		            out.write(resXml.getBytes());  
		            out.flush(); 
		            out.close();
	            }  
	        } else{  
		        	System.out.println("通知签名验证失败");
		            return error("通知签名验证失败");
		        }  
	        }catch(IOException e) {
	        	e.printStackTrace();
	        	return error("IO处理失败,请联系管理员!");
	        } catch (JDOMException e) {
				e.printStackTrace();
	        	return error("JDOM处理失败,请联系管理员!");
			}
		return error();   
			
		
	}
	
	/** 
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。 
     * @param strxml 
     * @return 
     * @throws JDOMException 
     * @throws IOException 
     */  
    @SuppressWarnings({ "rawtypes", "unchecked" })
	public static Map doXMLParse(String strxml) throws JDOMException, IOException {  
        strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");  
  
        if(null == strxml || "".equals(strxml)) {  
            return null;  
        }  
          
        Map m = new HashMap();  
          
        InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));  
        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 
     */  
    @SuppressWarnings("rawtypes")
	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();  
    }  

}

pom文件
可以参考下我之前的小程序的代码的pom文件,小程序调起微信支付,基本没做更改,只是用的spring-boot,本地打包加入外部jar包太麻烦就,直接走pom引入了外部的包,就多了下面一个包

        <dependency>
			<groupId>org.jdom</groupId>
			<artifactId>jdom</artifactId>
			<version>1.1</version>
		</dependency>

用到的外部jar包和工具类,jar包好像都没用到就导了pom文件就可以了,添加pom文件后还是有问题,可以把jar包导下,maven项目导外部jar包,打jar包会有问题,需要自己去解决下,我就是解决不了,所以选择了直接找网上的pom文件直接导
https://download.csdn.net/download/qq_40488121/11704169

前端代码

(前端不是我写的,我找前端要了份代码,全不全就不晓得了)

   uni.request({
	              	url:baseUrl+'/front/NOyStepOne',
	              	// method: 'GET',
	              	data: { 
						redisKey:this.rediskey,
						money:30
					 },
	              	success: res => {
				    console.log(res.data.data)
					var str=res.data.data.package
					var arr=str.split("=")[1]
					//胜利ar as1	=arr.toUpperCase()
					//console.log(as1)
					var obj={
						appid:res.data.data.appid,      //id 应用id
						partnerid:'1557620991',              //商户号 
						prepayid:arr,                         //预支付
						package:'Sign=WXPay',
						noncestr:res.data.data.nonceStr,
						timestamp:res.data.data.timeStamp,   //时间戳
						sign:res.data.data.sign
					}
					
					var orderInfo=JSON.stringify(obj)
					console.log("222")
					console.log(obj)
					var test=true;
					uni.requestPayment({
					    provider: 'wxpay',
					    orderInfo:orderInfo, //微信、支付宝订单数据
					    success: function (res) {
					        console.log('success:' + JSON.stringify(res));
					    },
					    fail: function (err) {
					        console.log('fail:' + JSON.stringify(err));
					    }
					});
					},
	              	fail: () => {},
	              	complete: () => {}
	              });

踩的坑

说下我踩的坑吧

  1. 生成第一次签名的sign和xml里的参数要一模一样,我最开始就是不一致导致统一下单接口一直没调通,具体代码位置如下俩处
			Map<String, String> packageParams = new HashMap<String, String>();
			// 小程序ID,微信分配的小程序ID
			packageParams.put("appid", appId);
			// 小程序ID,微信分配的小程序ID
			packageParams.put("attach", userId.toString());
			
			// 商户号,微信支付分配的商户号
			packageParams.put("mch_id", mch_id);
			// 随机字符串,长度要求在32位以内。
			packageParams.put("nonce_str", nonce_str);
			// 商品简单描述
			packageParams.put("body", body);
			// 商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*且在同一个商户号下唯一
			packageParams.put("out_trade_no", orderNo);// 商户订单号
			// 订单总金额,单位为分
			packageParams.put("total_fee", money.toString());
			// 终端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);

			// 除去数组中的空值和签名参数
			packageParams = WXPayUtils.paraFilter(packageParams);
			String prestr = WXPayUtils.createLinkString(packageParams); // 把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
// 拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
			String xml = "<xml version='1.0' encoding='gbk'>" 
					+ "<appid>" + appId + "</appid>" 
					+ "<attach>" + userId + "</attach>" 
					+ "<body><![CDATA["+body+"]]></body>" 
					+ "<mch_id>" + mch_id + "</mch_id>" 
					+ "<nonce_str>" + nonce_str + "</nonce_str>"
					+ "<notify_url>" + notify_url + "</notify_url>" 
					+ "<out_trade_no>" + orderNo + "</out_trade_no>"
					+ "<spbill_create_ip>" + spbill_create_ip + "</spbill_create_ip>" 
					+ "<total_fee>" + money + "</total_fee>" 
					+ "<trade_type>" + trade_type + "</trade_type>" 
					+ "<sign>" + mysign + "</sign>"
					+ "</xml>";

			System.out.println("调试模式_统一下单接口 请求XML数据:" + xml);

			// 调用统一下单接口,并接受返回的结果
			String result = WXPayUtils.httpRequest(pay_url, "POST", xml);

这俩处的参数要一模一样,我就是因为多了个参数加在xml里,签名那里漏了,导致一直不行
2. app是不需要进行第二次签名的,前台需要的sign参数直接是在调用统一下单接口后放回的
3. 随机字符串也是在调用统一下单接口后放回的,不是用的我传给微信的,而是要用微信那传回来的

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐