一、 官方流程图

 

 

 

商户系统和微信支付系统主要交互:

  1. 小程序内调用登录接口,获取到用户的openid,api参见公共api 小程序登录API
  2. 商户server调用支付统一下单,api参见公共api 统一下单API
  3. 商户server调用再次签名,api参见公共api 再次签名
  4. 商户server接收支付通知,api参见公共api 支付结果通知API
  5. 商户server查询支付结果,api参见公共api 查询订单API

当然在开发之前,我们需要有下面这些东西:

  • appId(小程序分配)
  • 小程序密钥(小程序配置界面获取)
  • 商户号
  • api密钥(商家后台自己设置)

二、 简单说明

不同的公司需求各有不同,流程也有不同,由于公司是做支付的,因此流程中统一下单之前会经过加签,下单之后还要经过验签服务,保证参数传递的正确性及安全性,验签成功之后,才会去返回微信小程序支付所需参数(微信的统一下单是由商户组去调用)

三、 支付流程的代码实现

  • 配置文件类 WxMinPayConfig
public class WxMinPayConfig {

    //小程序appid
    public static final String appid = "小程序分配";
    //小程序appSecret
    public static final String appSecret = "微信公众平台小程序密钥";
    //微信支付的商户id
    public static final String mch_id = "商户号";
    //微信支付的商户密钥
    public static final String key = "商户密钥";
    //获取openID接口
    public static final String get_openid_url = "https://api.weixin.qq.com/sns/jscode2session";
    //支付成功后的服务器回调url
//  public static final String notify_url = "https://??/weixin/wxNotify";
    //签名方式(视公司而定)默认为MD5,支持HMAC-SHA256和MD5
//  public static final String SIGNTYPE = "MD5";
    //交易类型
//  public static final String TRADETYPE = "JSAPI";
    //微信统一下单接口地址(此文章不会直接调用此接口)
//  public static final String pay_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    //加签接口地址
    public static final String sign_url = "xxxxxxxx";

//  验签接口地址
    public static final String verify_url = "xxxxxxx";

    //商户下单接口地址
    public static final String create_order_url = "xxxxxx";
}
  • 小程序支付所需参数返回的实现类 WeiXinServiceImpl,日志打印直接用 LogFactory
private static Log log = LogFactory.getLog(WeiXinServiceImpl.class);

微信小程序支付流程 用code调取微信获取openID接口 拼接xml调加签验签服务接口 验签成功后调用下单接口返回微信小程序支付参数

@Override
    public WXMinPayOut Pay(WXMinPayIn wxMinPayIn, String deviceIp) {
        WXMinPayOut out = new WXMinPayOut();
        try{
            JSONObject jsStr = WeiXinServiceImpl.getOpenId(wxMinPayIn);
            String openid = String.valueOf(jsStr.get("openid"));
            String session_key = String.valueOf(jsStr.get("session_key"));
            log.info("openid :" + openid + "====" +"session_key :" + session_key);
            String xml = WeiXinServiceImpl.paramXml(wxMinPayIn, deviceIp, openid);
            //加签
            JSONObject signParam = new JSONObject();
            signParam.put("sourceMsg", xml);
            Map signMap = digitalCertificateService.sign(signParam.toJSONString());
//          JSONObject signObject = WeiXinServiceImpl.getJsonObject(WxMinPayConfig.base_url+WxMinPayConfig.sign_url,signParam); //由于签名服务此项目自己维护了,因此这种调用方法暂时不用了
            log.info("=====返回数据=signMap=================:" + signMap);
            String signMsg = String.valueOf(signMap.get("signMsg"));
            if (null != signMap.get("retCode") && "0000".equals(signMap.get("retCode")) && null != signMsg){

                String xmlSignStr = xml.replace("<SIGNED_MSG></SIGNED_MSG>", "<SIGNED_MSG>" + signMsg + "</SIGNED_MSG>");
                log.info("====调用下单接口参数xml=================:" + xmlSignStr);
                log.info("====调用下单接口传参appid=================:" + wxMinPayIn.getAppId());
                String xmlStr = WeiXinServiceImpl.httpXmlData(WxMinPayConfig.base_url+WxMinPayConfig.create_order_url,xmlSignStr);
                Map map = XmlCommonUtil.xml2map(xmlStr);
                log.info("=====返回数据=map=================:" + map);
                if (null != map.get("SIGNED_MSG") && "0000".equals(map.get("RET_CODE"))){
                    xmlStr = xmlStr.replace("<SIGNED_MSG>" + map.get("SIGNED_MSG") + "</SIGNED_MSG>","<SIGNED_MSG></SIGNED_MSG>");
                    //验签
                    JSONObject verifyParam = new JSONObject();
                    verifyParam.put("sourceMsg", xmlStr);
                    verifyParam.put("signMsg",signMsg);
                    Map vierfyMap = digitalCertificateService.sign(verifyParam.toJSONString());
//                  JSONObject verifyObject = WeiXinServiceImpl.getJsonObject(WxMinPayConfig.base_url+WxMinPayConfig.verify_url,verifyParam);//由于签名服务此项目自己维护了,因此这种调用方法暂时不用了

                    if (null != vierfyMap.get("retCode") && "0000".equals(vierfyMap.get("retCode"))){
                        log.info("=====返回数据=PAY_STR=================:" + map.get("PAY_STR"));
                        JSONObject resultMap = JSONObject.parseObject((String)map.get("PAY_STR")); //将字符串{“id”:1}
                        log.info("=====调用下单接口返回appId=================:" + resultMap.get("appId"));
                        out.setAppId(String.valueOf(resultMap.get("appId")));
                        out.setNonceStr(String.valueOf(resultMap.get("nonceStr")));
                        out.setPackageStr(String.valueOf(resultMap.get("package")));
                        out.setPaySign(String.valueOf(resultMap.get("paySign")));
                        out.setSignType(String.valueOf(resultMap.get("signType")));
                        Long timeStamp = System.currentTimeMillis() / 1000;
                        out.setTimeStamp(String.valueOf(timeStamp));
                    }
                }
            }
        }catch (Exception e){
            log.error(e.getMessage(), e);
        }
        return out;
    }

获取openID

public static JSONObject getOpenId(WXMinPayIn wxMinPayIn){
        String wxspAppid = wxMinPayIn.getAppId();
        //小程序的 app secret (在微信小程序管理后台获取)
        String wxspSecret = WxMinPayConfig.appSecret;
        String grantType = "authorization_code";

        //请求参数
        String params = "appid=" + wxspAppid + "&secret=" + wxspSecret + "&js_code=" + wxMinPayIn.getWxcode() + "&grant_type=" + grantType;
        //发送请求
        String turl = String.format("%s?%s",WxMinPayConfig.get_openid_url,params);
        CloseableHttpClient client = HttpClients.createDefault();
        HttpGet get = new HttpGet(turl);
        JSONObject jsStr = null; // 响应内容
        try {
            HttpResponse res = client.execute(get);
            HttpEntity entity = res.getEntity();
            log.info("WeiXin open id : " + entity);
            String responseContent = EntityUtils.toString(entity, "UTF-8");
            log.info("WeiXin getToken response content : " + responseContent);
            jsStr = JSONObject.parseObject(responseContent); //将字符串{“id”:1}
            log.info("jsStr : " + jsStr.get("openid"));
        }catch (Exception e){
            log.info(e.getMessage(),e);
        }
        return jsStr;
    }

xml参数拼接

public static String paramXml(WXMinPayIn wxMinPayIn, String deviceIp, String openid){
        //生成的随机字符串
        //String nonce_str = StringUtils.getRandomStringByLength(32);
        long rand = RandomUtils.nextInt();
        String nonce_str = DateFormatUtils.format(new Date(), "yyyyMMdd")+rand;
        //订单日期
        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");//设置日期格式
        System.out.println(df.format(new Date()));
        String orderDate = df.format(new Date());
        //商品名称
        String body = wxMinPayIn.getOrderSubject();
        //获取本机的ip地址
        String spbill_create_ip = deviceIp;
        //支付金额,单位:分,这边需要转成字符串类型,否则后面的签名会失败,(xml拼接字段依据自己公司需求而定)
        String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                + "<AIPG>"
                + "<INFO>"
                + "<TRX_CODE>" + "xxx" +"</TRX_CODE>"
                + "<VERSION>"+ "xxx"+"</VERSION>"
                + "<DATA_TYPE>"+ "xxx"+ "</DATA_TYPE>"
                + "<REQ_SN>" + nonce_str + "</REQ_SN>"
                + "<SIGNED_MSG>" + "" + "</SIGNED_MSG>"
                + "</INFO>"
                + "<BODY>"
                + "<MERCHANT_ID>" + wxMinPayIn.getMerchantId() + "</MERCHANT_ID>"
                + "<MERC_ORDER_NO>" + nonce_str + "</MERC_ORDER_NO>"
                + "<MERC_ORDER_DATE>" + orderDate + "</MERC_ORDER_DATE>"
                + "<TRANS_AMT>" + wxMinPayIn.getTransAmt() + "</TRANS_AMT>"
                + "<PLACE_ORDER_IP>" + spbill_create_ip + "</PLACE_ORDER_IP>"
                + "<OPEN_ID>" + openid + "</OPEN_ID>"
                + "<APPID>" + wxMinPayIn.getAppId() + "</APPID>"
                + "<ORDER_SUBJECT>" + body + "</ORDER_SUBJECT>"
                + "<PAY_CHANNEL>" + wxMinPayIn.getPayChannel() + "</PAY_CHANNEL>"
                + "<SPLIT_FLAG>" + "xxxx" + "</SPLIT_FLAG>"
                + "<ASSURE_FLAG>" + "xxxx" + "</ASSURE_FLAG>"
                + "</BODY>"
                + "</AIPG>";
        log.info("调试模式_统一下单接口 请求XML数据:" + xml);
        return xml;
    }

加签验签(暂时不用此写法)

public static JSONObject getJsonObject(String url, JSONObject jsonParam){
        CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
        JSONObject jsonObject = null;
        try {
            HttpPost post = new HttpPost(url);
            StringEntity stringEntity = new StringEntity(jsonParam.toString(),"utf-8");//解决中文乱码问题
            stringEntity.setContentEncoding("UTF-8");
            stringEntity.setContentType("application/json");
            post.setEntity(stringEntity);
            HttpResponse response = closeableHttpClient.execute(post);
            HttpEntity httpEntity = response.getEntity();
            log.info("httpEntity : " + httpEntity);
            String signResponseContent = EntityUtils.toString(httpEntity, "UTF-8");
            log.info("signResponseContent : " + signResponseContent);
            jsonObject = JSONObject.parseObject(signResponseContent); //将字符串{“id”:1}
        }catch (Exception e){
            log.info(e.getMessage(),e);
        }
        return jsonObject;
    }

调商户下单接口

public static String httpXmlData(String url, String xml){
        CloseableHttpClient HttpClient = HttpClients.createDefault();
        String xmlStr = null;
        try {
            HttpPost orderPost = new HttpPost(url);
            orderPost.addHeader("Content-Type","text/html;charset=UTF-8");
            String base64Str = GZipUtil.gzipString(xml);
            StringEntity orderEntity = new StringEntity(base64Str,"utf-8");//解决中文乱码问题
            orderEntity.setContentEncoding("UTF-8");
            orderPost.setEntity(orderEntity);
            HttpResponse orderResponse = HttpClient.execute(orderPost);
            HttpEntity orderhttpEntity = orderResponse.getEntity();
            log.info("orderhttpEntity : " + orderhttpEntity);
            String ordersignResponseContent = EntityUtils.toString(orderhttpEntity, "UTF-8");
            String s = GZipUtil.ungzipString(ordersignResponseContent);
            log.info("ordersignResponseContent : " + s);
            xmlStr = XmlFormatter.format(s); //转化为字符串xml
        }catch (Exception e){
            log.info(e.getMessage(),e);
        }
        return xmlStr;
    }
  • controller对外提供接口类实现 WeiXinController
@ResponseBody
    @RequestMapping(value = "/wxminpay", method = RequestMethod.POST)
    public WXMinPayOut wxminpay(@RequestBody WXMinPayIn wxMinPayIn, HttpServletRequest request){
        WXMinPayOut out = new WXMinPayOut();
        String ip = IpUtils.getIpAddr(request);
        try {
            out = weiXinServiceImpl.Pay(wxMinPayIn,ip);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return out;
    }

四、小程序调用实现

小小程序端调用wx.login获取code,然后配合支付接口所需参数一同传递给后端

  • 具体实现
wx.login({
      success(res) {
 console.log("====code=====:" + res.code)
wx.request({
          url: "http://xxxxxxx//wxminpay",
          data: {
            wxcode: res.code,
            xxx: xxxx (下单支付所需参数,可能会有很多)
          },
          method: 'POST',
          success(res) {
            const payargs = res.data
            wx.requestPayment({
              timeStamp: payargs.timeStamp,
              nonceStr: payargs.nonceStr,
              package: payargs.package,
              signType: payargs.signType,
              paySign: payargs.paySign
            })
          }
        })
      }
    })

五、结束

至此,微信小程序后端及小程序实现就完成了!如有疑问欢迎交流,觉得不错公众号“十二公子”可以走一波,谢谢!

 微信支付:https://zhuanlan.zhihu.com/p/147366901