小程序开发——微信支付篇

本文详细介绍了微信小程序支付的流程,从收集用户信息到生成预付单,再到小程序调起支付,以及支付结果的回调处理。通过代码示例展示了如何在后端生成支付所需参数,并提供了模拟回调接口供前端测试使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

小程序开发中常常会用到微信支付。微信支付里面的坑谁做谁知道。

先来个官方贴图谈一下支付的流程

对,支付是个异步的过程。

收集到用户的openid,支付金额,ip地址之后,我们生成随机串nonce_str和时间戳、订单号。当然商户号、商户秘钥、小程序的APPID、通知地址这些也是提前存好的。然后用这些信息按字典排序,它要求的是键值对,也就是key=value&  这样来拼接(具体的下面说)。拼接完成后尾处加上商户秘钥。最后用md5加密,提交到微信服务器生成预付单。拿到预付单之后我们传给小程序,小程序中用预付单的参数调起微信支付。然后支付结果通过我们前面传递的通知地址来通知我们(就是普通post请求传给你一个xml)。收到回调的xml后,我们去验证sign,如果验证通过则返回一个回馈给微信服务器(不然它还会持续发给你),然后我们执行支付成功的业务操作。

那我们现在从代码的角度来走一遍支付流程。

首先我们应该已知以下参数:appid(小程序appid)、mch_id(商户号)、key(商户秘钥)、trade_type(交易类型,JSAPI)、notify_url(支付回调地址,也就是通知我们的请求地址)

然后前端的提交一些信息,我们算出支付金额,获取支付用户的openid、支付用户客户端的IP地址、商品名称(其实可以固定写测试商品)。然后我们生成订单号,随机串和时间戳在后面生成。一个简单的controller如下:

/**
 * 请求支付接口,当然参数不可能这么简单,其他业务参数我们就省去了
 * @param openid   支付用户的openid
 * @param request  用来获取支付用户的IP地址
 *
 * @return  预付单的信息  ,你也可以用其他类来进行封装,这里我直接用Map传回给前端了
 */
@RequestMapping("pay")
public Map insert(String openid,HttpServletRequest request) {
   //我们假装计算出了支付金额,记住不能支付0元
   //如果你不想用BigDecimal ,用其他也行。但是记住,最后提交的时候是以分为单位的
   BigDecimal money = new BigDecimal(20);
   //生成一个订单号,生成策略自己决定
   String id = "123456";
   //准备一个回调地址,为了给pay方法解耦,我们每种不同支付采用不同回调地址
   String notifyUrl ="http://www.baidu.com/wxnotify";//要填能访问到的,别用本地localhost或者内网IP,尽量使用域名和公网IP
   Map map = null;
   //用异常包围起来,也许你想用事物来做
   try {
      map = pay(id,openid,money,request);
   }catch (Exception e){
      //处理一下异常
   }
   return map;
}

然后我们看一下这个pay方法。下面会调用StringUtil和HttpRequestor ,这两个是我自己准备的工具包,文末会贴上

/**
 *  发起支付请求
 * @param orderid  订单ID
 * @param openid   用户ID
 * @param money   支付金额
 * @param request   请求体
 * @param notifyUrl 支付结果回调通知地址
 * @return   预付单参数
 * @throws Exception
 */
public  Map pay(String orderid, String openid, BigDecimal money,String notifyUrl, HttpServletRequest request) throws Exception {
   //转化为分为单位
   money = money.multiply(new BigDecimal(100));
   //转换格式,最终还是转换成了long类型。。。。。但是绝对不要忘记是以分为单位
   Long price = money.longValue();
   //不能支付0元
   if(price==0L){
      return  null;
   }
   // 生成的随机字符串,最长32位,uuid去掉“-”正好
   String nonce_str = UUID.randomUUID().toString().replaceAll("-", "").toUpperCase();
   // 商品名称,这里我写死了商品名称,如果你不想这样就改成参数吧
   String productName = "测试商品";
   // 获取终端IP
   String ip = HttpRequestor.getIpFromRequest(request);
  //GlobalPerties 是一个参数类,里面的所有变量都是static final的,方便访问。我用来存不会变动的参数
   // 组装参数,用户生成统一下单接口的签名,这里用TreeMap 来就不需要自己排序了
   SortedMap<String, String> packageParams = new TreeMap<String, String>();
   packageParams.put("appid", GlobalProperties.APPID);// 小程序的aapid
   packageParams.put("mch_id", GlobalProperties.MCHID);// 商户号
   packageParams.put("nonce_str", nonce_str);// 随机字符串
   packageParams.put("body", productName);// 商品名称
   packageParams.put("out_trade_no", orderid);// 商户订单号
   packageParams.put("total_fee", price + "");// 标价金额
   packageParams.put("spbill_create_ip", ip);// 终端IP
   packageParams.put("notify_url",notifyUrl);// 通知地址
   packageParams.put("trade_type", "JSAPI");// 交易类型
   packageParams.put("openid", openid);// 微信用户的openid
   //参数map准备好之后我们进行拼接并加密。具体方法往后看
   // MD5运算生成签名,这里是第一次签名,用于调用统一下单接口
   String mysign = getSign(packageParams);
   // 拼接统一下单接口使用的xml数据,要将上一步生成的签名一起拼接进去
   String xml = "<xml>" + "<appid>" + GlobalProperties.APPID + "</appid>" + "<body><![CDATA[" + productName + "]]></body>"
         + "<mch_id>" + GlobalProperties.MCHID + "</mch_id>" + "<nonce_str>" + nonce_str + "</nonce_str>"
         + "<notify_url>" + notifyUrl + "</notify_url>" + "<openid>" + openid + "</openid>"
         + "<out_trade_no>" + orderid + "</out_trade_no>" + "<spbill_create_ip>" + ip + "</spbill_create_ip>"
         + "<total_fee>" + price + "</total_fee>" + "<trade_type>" + GlobalProperties.TRADETYPE + "</trade_type>"
         + "<sign>" + mysign + "</sign>" + "</xml>";
   //打印一下日志方便调试,用了@Slf4j
   log.info("调试模式_统一下单接口 请求XML数据:" + xml);
   
   // 调用统一下单接口,并接受预付订单参数。这里我执行了一个post请求,这个请求工具类我放在文末
   String result = HttpRequestor.doPost(GlobalProperties.PAYREQUEST_URL, xml);
   //打印日志
   log.info("调试模式_统一下单接口 返回XML数据:" + result);
   
   // 将解析结果存储在HashMap中,xml转map 我是用了dom4j
   
   Map<String, Object> map = StringUtil.doXMLParse(result);
   /**
    * 返回状态码
    */
   String return_code = (String) map.get("return_code");
   /**
    * 准备返回给前端的map集合
    */
   Map<String, Object> response = new HashMap<String, Object>(16);
   // 判断一下预付订单是否成功的
   if ("SUCCESS".equals(return_code)) {
      /**
       * 分析预付单信息
       */
      String prepay_id = (String) map.get("prepay_id");
      //前端需要的参数:nonceStr、package、timeStamp、paySign、appid,所以我们只需要关心这几个
      response.put("nonceStr", nonce_str);
      response.put("package", "prepay_id=" + prepay_id);
      Long timeStamp = System.currentTimeMillis() / 1000;
      /**
       * 这边要将返回的时间戳转化成字符串,不然小程序端调用wx.requestPayment方法会报签名错误
       */
      response.put("timeStamp", timeStamp + "");
      // 拼接签名需要的参数
      String stringSignTemp = "appId=" + GlobalProperties.APPID + "&nonceStr=" + nonce_str + "&package=prepay_id="
           
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值