作者:odile微笑头 | 来源:互联网 | 2023-09-17 13:21
之前有做了小程序的微信支付以为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 {@AutowiredRedisUtil redis;@Autowiredprivate ICOrderService orderService;@Autowiredprivate 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为商户平台设置的密钥keyString 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)) {//获取当前用户idLong 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 packageParams = new HashMap();// 小程序ID,微信分配的小程序IDpackageParams.put("appid", appId);// 小程序ID,微信分配的小程序IDpackageParams.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的机器IPpackageParams.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为商户平台设置的密钥keyString mysign = WXPayUtils.sign(prestr, WXKeHukey, "utf-8").toUpperCase();System.out.println("=======================第一次签名:" + mysign + "=====================");// 拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去String xml = "" + "" + appId + "" + "" + userId + "" + "" + "" + mch_id + "" + "" + nonce_str + ""+ "" + notify_url + "" + "" + orderNo + ""+ "" + spbill_create_ip + "" + "" + money + "" + "" + trade_type + "" + "" + mysign + ""+ "";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 response = new HashMap();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 m = new HashMap(); m = doXMLParse(sb.toString());//过滤空 设置 TreeMap SortedMap packageParams = new TreeMap(); Iterator 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 &#61; "" &#43; "" &#43; "" &#43; " "; /*** 支付成功后的处理*/// 更新订单数据Long userId &#61; Long.parseLong(attach);COrder c_order &#61; new COrder();c_order.setOrderNumber(out_trade_no);List orderList &#61; orderService.selectCOrderList(c_order);if(orderList.size()>&#61;1) {c_order &#61; 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 &#61; orderService.selectMonthMoney();//计算充值多少个月的会员long month &#61; Long.parseLong(total_fee)/monthMoney;//查询用户信息CUser cUser &#61; new CUser();cUser.setUserId(userId);List userList &#61; cUserService.selectUserInfoList(cUser);//根据用户vip剩余时间处理业务if(userList.size()<&#61;0) {cUser &#61; userList.get(0);Calendar userVip &#61; Calendar.getInstance();userVip.setTime(cUser.getUserVip());Calendar now &#61; Calendar.getInstance();userVip.setTime(new Date());if(userVip.after(now)) {//会员未过期}else {//会员已过期&#xff0c;或者未充值过会员userVip &#61; 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("支付失败,错误信息&#xff1a;" &#43; packageParams.get("err_code")); resXml &#61; "" &#43; "" &#43; "" &#43; " "; //TODO 支付失败要做什么 我这里先不写//------------------------------ //处理业务完毕 //------------------------------ BufferedOutputStream out &#61; 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处理失败&#xff0c;请联系管理员&#xff01;");} catch (JDOMException e) {e.printStackTrace();return error("JDOM处理失败&#xff0c;请联系管理员&#xff01;");}return error(); }/** * 解析xml,返回第一级元素键值对。如果第一级元素有子节点&#xff0c;则此节点的值是子节点的xml数据。 * &#64;param strxml * &#64;return * &#64;throws JDOMException * &#64;throws IOException */ &#64;SuppressWarnings({ "rawtypes", "unchecked" })public static Map doXMLParse(String strxml) throws JDOMException, IOException { strxml &#61; strxml.replaceFirst("encoding&#61;\".*\"", "encoding&#61;\"UTF-8\""); if(null &#61;&#61; strxml || "".equals(strxml)) { return null; } Map m &#61; new HashMap(); InputStream in &#61; new ByteArrayInputStream(strxml.getBytes("UTF-8")); SAXBuilder builder &#61; new SAXBuilder(); Document doc &#61; builder.build(in); Element root &#61; doc.getRootElement(); List list &#61; root.getChildren(); Iterator it &#61; list.iterator(); while(it.hasNext()) { Element e &#61; (Element) it.next(); String k &#61; e.getName(); String v &#61; ""; List children &#61; e.getChildren(); if(children.isEmpty()) { v &#61; e.getTextNormalize(); } else { v &#61; getChildrenText(children); } m.put(k, v); } //关闭流 in.close(); return m; } /** * 获取子结点的xml * &#64;param children * &#64;return String */ &#64;SuppressWarnings("rawtypes")public static String getChildrenText(List children) { StringBuffer sb &#61; new StringBuffer(); if(!children.isEmpty()) { Iterator it &#61; children.iterator(); while(it.hasNext()) { Element e &#61; (Element) it.next(); String name &#61; e.getName(); String value &#61; e.getTextNormalize(); List list &#61; e.getChildren(); sb.append("<" &#43; name &#43; ">"); if(!list.isEmpty()) { sb.append(getChildrenText(list)); } sb.append(value); sb.append("" &#43; name &#43; ">"); } } return sb.toString(); } }
pom文件
可以参考下我之前的小程序的代码的pom文件&#xff0c;小程序调起微信支付&#xff0c;基本没做更改&#xff0c;只是用的spring-boot&#xff0c;本地打包加入外部jar包太麻烦就&#xff0c;直接走pom引入了外部的包&#xff0c;就多了下面一个包
org.jdomjdom1.1
用到的外部jar包和工具类&#xff0c;jar包好像都没用到就导了pom文件就可以了&#xff0c;添加pom文件后还是有问题&#xff0c;可以把jar包导下&#xff0c;maven项目导外部jar包&#xff0c;打jar包会有问题&#xff0c;需要自己去解决下&#xff0c;我就是解决不了&#xff0c;所以选择了直接找网上的pom文件直接导
https://download.csdn.net/download/qq_40488121/11704169
前端代码
&#xff08;前端不是我写的&#xff0c;我找前端要了份代码&#xff0c;全不全就不晓得了&#xff09;
uni.request({url:baseUrl&#43;&#39;/front/NOyStepOne&#39;,// method: &#39;GET&#39;,data: { redisKey:this.rediskey,money:30},success: res &#61;> {console.log(res.data.data)var str&#61;res.data.data.packagevar arr&#61;str.split("&#61;")[1]//胜利ar as1 &#61;arr.toUpperCase()//console.log(as1)var obj&#61;{appid:res.data.data.appid, //id 应用idpartnerid:&#39;1557620991&#39;, //商户号 prepayid:arr, //预支付package:&#39;Sign&#61;WXPay&#39;,noncestr:res.data.data.nonceStr,timestamp:res.data.data.timeStamp, //时间戳sign:res.data.data.sign}var orderInfo&#61;JSON.stringify(obj)console.log("222")console.log(obj)var test&#61;true;uni.requestPayment({provider: &#39;wxpay&#39;,orderInfo:orderInfo, //微信、支付宝订单数据success: function (res) {console.log(&#39;success:&#39; &#43; JSON.stringify(res));},fail: function (err) {console.log(&#39;fail:&#39; &#43; JSON.stringify(err));}});},fail: () &#61;> {},complete: () &#61;> {}});
说下我踩的坑吧
- 生成第一次签名的sign和xml里的参数要一模一样&#xff0c;我最开始就是不一致导致统一下单接口一直没调通&#xff0c;具体代码位置如下俩处
Map packageParams &#61; new HashMap();// 小程序ID,微信分配的小程序IDpackageParams.put("appid", appId);// 小程序ID,微信分配的小程序IDpackageParams.put("attach", userId.toString());// 商户号,微信支付分配的商户号packageParams.put("mch_id", mch_id);// 随机字符串&#xff0c;长度要求在32位以内。packageParams.put("nonce_str", nonce_str);// 商品简单描述packageParams.put("body", body);// 商户系统内部订单号&#xff0c;要求32个字符内&#xff0c;只能是数字、大小写字母_-|*且在同一个商户号下唯一packageParams.put("out_trade_no", orderNo);// 商户订单号// 订单总金额&#xff0c;单位为分packageParams.put("total_fee", money.toString());// 终端IP,支持IPV4和IPV6两种格式的IP地址。调用微信支付API的机器IPpackageParams.put("spbill_create_ip", spbill_create_ip);// 异步接收微信支付结果通知的回调地址&#xff0c;通知url必须为外网可访问的url&#xff0c;不能携带参数。packageParams.put("notify_url", notify_url);// 交易类型(JSAPI--JSAPI支付&#xff08;或小程序支付&#xff09;、NATIVE--Native支付、APP--app支付&#xff0c;MWEB--H5支付&#xff0c;不同trade_type决定了调起支付的方式&#xff0c;请根据支付产品正确上传)packageParams.put("trade_type", trade_type);// 除去数组中的空值和签名参数packageParams &#61; WXPayUtils.paraFilter(packageParams);String prestr &#61; WXPayUtils.createLinkString(packageParams); // 把数组所有元素&#xff0c;按照“参数&#61;参数值”的模式用“&”字符拼接成字符串
// 拼接统一下单接口使用的xml数据&#xff0c;要将上一步生成的签名一起拼接进去String xml &#61; "" &#43; "" &#43; appId &#43; "" &#43; "" &#43; userId &#43; "" &#43; "" &#43; "" &#43; mch_id &#43; "" &#43; "" &#43; nonce_str &#43; ""&#43; "" &#43; notify_url &#43; "" &#43; "" &#43; orderNo &#43; ""&#43; "" &#43; spbill_create_ip &#43; "" &#43; "" &#43; money &#43; "" &#43; "" &#43; trade_type &#43; "" &#43; "" &#43; mysign &#43; ""&#43; "";System.out.println("调试模式_统一下单接口 请求XML数据&#xff1a;" &#43; xml);// 调用统一下单接口&#xff0c;并接受返回的结果String result &#61; WXPayUtils.httpRequest(pay_url, "POST", xml);
这俩处的参数要一模一样&#xff0c;我就是因为多了个参数加在xml里&#xff0c;签名那里漏了&#xff0c;导致一直不行
2. app是不需要进行第二次签名的&#xff0c;前台需要的sign参数直接是在调用统一下单接口后放回的
3. 随机字符串也是在调用统一下单接口后放回的&#xff0c;不是用的我传给微信的&#xff0c;而是要用微信那传回来的