微信小程序支付实现

作者: 南少cc | 来源:发表于2019-03-14 15:28 被阅读0次

    一、 官方流程图

    wxpay.jpg
    商户系统和微信支付系统主要交互:
    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://www.haomeiwen.com/subject/ycxcmqtx.html