app 调用微信支付疯狂踩坑(前端uni-app 后端java)
之前有做了小程序的微信支付以为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: () => {}
});
踩的坑
说下我踩的坑吧
- 生成第一次签名的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. 随机字符串也是在调用统一下单接口后放回的,不是用的我传给微信的,而是要用微信那传回来的
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)