1.前端传来 code
2.后端通过code 获取微信用户唯一识别id ---->  openId
3.根据openId

package com.maege.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.wxpay.sdk.WXPay;
import com.maege.Token.TokenHelper;
import com.maege.Token.TokenModel;
import com.maege.Token.TokenModelStr;
import com.maege.annotation.NoneAuth;
import com.maege.config.AesEncryptUtils;
import com.maege.config.ReturnCommon;
import com.maege.service.OrderService;
import com.maege.service.ProductsService;
import com.maege.wxconfig.HttpClient;
import com.maege.wxconfig.JsonResult;
import com.maege.wxpay_SDK.MyWXConfig;
import com.maege.wxpay_SDK.WXPayConfig;
import com.maege.wxpay_SDK.WXPayConstants;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import com.maege.wxpay_SDK.WXPayUtil;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.URLEncoder;
import java.util.*;

@Controller
@RequestMapping("wx")
public class WXPayController {
    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    ProductsService productsService;
    @Autowired
    OrderService orderService;
    @Resource
    private TokenHelper tokenHelper;

    /**
     * 微信统一下单接口
     * id 商品id
     * code 前端传来的code 目的为了获取付款用户的唯一openid
     * @return
     */
    @NoneAuth
    @RequestMapping(value = "/prePay", method = RequestMethod.POST)
    @ResponseBody
    public Map<String,Object> prePay(@RequestBody Map<String,Object> str,HttpServletRequest request){
        String id = str.get("id").toString();//商品id
        String code = str.get("code").toString();
       

        //id 为下单的id,产品id
        //返回参数
        Map<String,Object> resMap = new HashMap<>();

        //获取请求ip地址
        String ip= request.getHeader("x-forwarded-for");
        if(ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)){
            ip = request.getHeader("Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknow".equalsIgnoreCase(ip)){
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
            ip = request.getRemoteAddr();
        }

        if(ip.indexOf(",")!=-1){
            String [] ips = ip.split(",");
            ip = ips[0].trim();
        }

        try {
            log.info("id = "+id);

            Map<String,String> paraMap = new HashMap<>();
            //根据id获取此商品的详细信息
            Map<String,Object> map =  productsService.profindbyid(id);
            String body = (String) map.get("name");//商品名称,数据库查出来的
            String out_trade_no = System.currentTimeMillis()+"";//订单号,时间戳生成
            String openid = null;
            try {//获取openid
                openid = getUserInfo(code);
                  

            }catch (Exception e){
                log.info("openid获取异常={}"+e);
                return new HashMap<>();
            }
            if(openid == null || openid.length()<=0){
                log.info("openid ="+openid);
                return new HashMap<>();
            }
            String money = (String) map.get("price");//金额,数据库查出来的
            Integer price = Integer.valueOf(money);
            log.info("body = "+body);
            String nostr =  WXPayUtil.generateNonceStr().toUpperCase();//微信随机字符串
            paraMap.put("appid", WXPayConstants.APP_ID);
            paraMap.put("body", body);//商品名称
            paraMap.put("mch_id", WXPayConstants.MCH_ID);//商家id
            paraMap.put("nonce_str", nostr);//微信随机字符串
            paraMap.put("notify_url", WXPayConstants.CALLBACK_URL);//异步通知地址
            paraMap.put("openid", openid);//用户标识,对应下单的用户
            paraMap.put("out_trade_no", out_trade_no);//商户订单号
            paraMap.put("spbill_create_ip", ip);//ip
            paraMap.put("total_fee", price.toString());//标价金额 ,单位分
            paraMap.put("trade_type", "JSAPI");//交易类型
            String sign = WXPayUtil.generateSignature(paraMap,WXPayConstants.PATERNER_KEY);//第一次签名
            paraMap.put("sign",sign);
            String xml1 = "<xml>" +
                    "<appid>"+ WXPayConstants.APP_ID +"</appid>"+
                    "<body>"+ body +"</body>"+
                    "<mch_id>"+ WXPayConstants.MCH_ID +"</mch_id>"+
                    "<nonce_str>"+ nostr +"</nonce_str>"+
                    "<notify_url>"+ WXPayConstants.CALLBACK_URL +"</notify_url>"+
                    "<openid>"+ openid +"</openid>"+
                    "<out_trade_no>"+ out_trade_no +"</out_trade_no>"+
                    "<spbill_create_ip>"+ ip +"</spbill_create_ip>"+
                    "<total_fee>"+ price.toString()+"</total_fee>"+
                    "<trade_type>JSAPI</trade_type>"+
                    "<sign>"+ sign +"</sign>"+
                    "</xml>";


            String xmlStr = HttpClient.doPostXml(WXPayConstants.UNIFIEDORDER_URL,xml1);
            log.info("xmlStr="+xmlStr);

            //处理下单业务,将订单存到数据库 ----------- start_1
            Map<String,Object> order_map = new HashMap<>();
            order_map.put("out_trade_no",out_trade_no);//商户订单号
            order_map.put("order_amount",price.toString());//订单金额
            order_map.put("paid_amount",price.toString());//实际支付金额
            order_map.put("product_id",id);//产品id
            order_map.put("buy_counts","1");//购买数量 默认 1
            order_map.put("open_id",openid);//用户openid 唯一识别
            int flag =  orderService.add_order(order_map);
            if(flag <= 0){
                log.error("创建订单失败");
                return  ReturnCommon.error("创建订单失败","","500");
            }
            //----------- end_1

            //预支付id: prepay_id
            String prepay_id ="";
            if(xmlStr.indexOf("SUCCESS") != -1){
                //xml 转 map
                Map<String,String> mapxml = WXPayUtil.xmlToMap(xmlStr);
                prepay_id = mapxml.get("prepay_id").toString();
                log.info("prepay_id1="+prepay_id);
            }else{
                //下单失败删除数据库里的此条订单信息,此处对应上面的下单存数据库但是调用下单信息失败,所以要删除这条信息
                int del_flag = orderService.del_order(out_trade_no);
                if(del_flag>0){
                    log.info("订单异常,数据库订单信息已删除");
                }
            }
            log.info("prepay_id2="+prepay_id);

            //此处用 appId,timeStamp,nonceStr,package,signType 生成 第二次签名
            HashMap<String, String> back = new HashMap<String, String>();
            String time = Long.toString(System.currentTimeMillis());
            back.put("appId", WXPayConstants.APP_ID);
            back.put("timeStamp", time);
            back.put("nonceStr", nostr);
            back.put("package", "prepay_id=" + prepay_id);
            back.put("signType", "MD5");
            String sign2 = WXPayUtil.generateSignature(back, WXPayConstants.PATERNER_KEY);
            log.info("二次签名后返回给前端的签名证书字符串是:" + sign2);


            //返给前端的数据
            Map<String,String> fresh = new HashMap<>();
            fresh.put("appId", WXPayConstants.APP_ID);
            fresh.put("timeStamp", time);
            fresh.put("nonceStr", nostr);
            fresh.put("package", "prepay_id=" + prepay_id);
            fresh.put("signType", "MD5");
            fresh.put("paySign", sign2);
            fresh.put("out_trade_no",out_trade_no);//商户订单号

            log.info("返给前端的数据={}",fresh);
            System.out.println("返给前端的数据={}"+fresh);
            //封装正常情况下返回数据
            resMap.put("success",true);
            resMap.put("payMap",fresh);
            return ReturnCommon.success("success",resMap,"200");
        }catch (Exception e){
            resMap.put("error",false);
            resMap.put("message","调用同一订单接口错误");
            e.printStackTrace();
            return ReturnCommon.success("error","调用同一订单接口错误","500");
        }
    }




    /**
     *  此订单是否被支付
     *  此处用来修改定单状态,
     *  支付成功修改定但状态为 1,失败修改为 0
     *  id: 订单id
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/callBack")
    @NoneAuth
    public JsonResult callBack(HttpServletRequest request, HttpServletResponse response) {
        JsonResult result = new JsonResult();
        log.info("微信支付成功,微信发送的callback信息,请注意修改订单信息");
        System.out.println("微信支付成功,微信发送的callback信息,请注意修改订单信息");
        InputStream is = null;
        String resXml = "";
        try {
            is = request.getInputStream();//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
            String xml = WXPayUtil.inputStream2String(is);
            Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);//将微信发的xml转map
            log.info("接受到微信的订单异步通知="+notifyMap);
            System.out.println("接受到微信的订单异步通知="+notifyMap);
            if(notifyMap.get("return_code").equals("SUCCESS")){
                String ordersNum = notifyMap.get("out_trade_no").toString();//商户订单号
                //处理订单状态
                String openid = notifyMap.get("openid");
                Date zhifutime = new Date();
                Integer ordertype = 1;//1支付完成
                try {
                    log.info("商户订单号:"+ordersNum+"--支付状态:"+ordertype+"--支付时间:"+zhifutime+"---complete");
                    //修改订单状态
                    int flag = orderService.updatOrder(ordersNum);
                    if(flag>0){
                        log.info("定单回调状态修改成功");
                        //生成流水*************************************
                        int order = orderService.addflow(notifyMap);

                        //付款成功 redis中存入登录信息
                        TokenModelStr model = tokenHelper.create(openid);
                        String vcode = AesEncryptUtils.encrypt(model.toString2());//Aes 加密token
                    }

                    result.setData("SUCCESS");
                    result.setData("支付回调成功,修改订单状态为支付成功");
                    //告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
                    resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
                            + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
                    BufferedOutputStream out = new BufferedOutputStream(
                            response.getOutputStream());
                    out.write(resXml.getBytes());
                    out.flush();
                    out.close();
                    System.err.println("返回给微信的值:"+resXml.getBytes());
                    is.close();
                }catch (Exception e){
                    result.setErrmsg("订单状态修改失败");
                }


            }

        } catch (Exception e) {
            log.info("订单回调失败,原因="+e);
            e.printStackTrace();
        }
        return result;
    }


    /***
     * 查询订单状态接口,
     * @return
     * @throws Exception
     */
    @RequestMapping("/findorder")
    @ResponseBody
    @NoneAuth
    public Map<String,Object> findorder( @RequestBody Map<String,Object> str ){
        String ordersNum =  str.get("out_trade_no").toString();
        //根据商户订单号查询该条已支付订单
        Map<String,Object> map = orderService.findByid(ordersNum);
        Map<String,Object> maptoken = new HashMap<>();
        if(!map.isEmpty()){
            try {
                //获取redis中的token
                TokenModelStr model = tokenHelper.getkv(map.get("open_id").toString());
                String vcode = AesEncryptUtils.encrypt(model.toString2());//Aes 加密token
                maptoken.put("token",vcode);
                maptoken.put("username","游客");
            }catch (Exception e){
                log.info("查询订单出错,"+e);
            }

        }
        return ReturnCommon.success("订单查询成功",maptoken,"200");
    }



    //获取openid
    public String getUserInfo(String code) throws Exception {
        System.out.println("code=" + code);
        String url = "https://api.weixin.qq.com/sns/jscode2session";
        url += "?appid="+WXPayConstants.APP_ID;//自己的appid
        url += "&secret="+WXPayConstants.APP_SECRET;//自己的appSecret
        url += "&js_code=" + code;
        url += "&grant_type=authorization_code";
       // url += "&connect_redirect=1";
        String res = null;
        CloseableHttpClient httpClient = HttpClientBuilder.create().build();
        // DefaultHttpClient();
        HttpGet httpget = new HttpGet(url);    //GET方式
        CloseableHttpResponse response = null;
        // 配置信息
        RequestConfig requestConfig = RequestConfig.custom()          // 设置连接超时时间(单位毫秒)
                .setConnectTimeout(5000)                    // 设置请求超时时间(单位毫秒)
                .setConnectionRequestTimeout(5000)             // socket读写超时时间(单位毫秒)
                .setSocketTimeout(5000)                    // 设置是否允许重定向(默认为true)
                .setRedirectsEnabled(false).build();           // 将上面的配置信息 运用到这个Get请求里
        httpget.setConfig(requestConfig);                         // 由客户端执行(发送)Get请求
        response = httpClient.execute(httpget);                   // 从响应模型中获取响应实体
        HttpEntity responseEntity = response.getEntity();
        System.out.println("响应状态为:" + response.getStatusLine());
        if (responseEntity != null) {
            res = EntityUtils.toString(responseEntity);
            System.out.println("响应内容长度为:" + responseEntity.getContentLength());
            System.out.println("响应内容为:" + res);
        }
        // 释放资源
        if (httpClient != null) {
            httpClient.close();
        }
        if (response != null) {
            response.close();
        }
        JSONObject jo = JSON.parseObject(res);
        String openid = jo.getString("openid");
        System.out.println("openid" + openid);
        return openid;
    }

}