微信公众号H5支付(SSM框架)

 首先感谢一下【架构之路】盆友!我就是在他的基础之上改的。原文链接。再三感谢!

在最下,我会贴上所有的代码。我代码log日志打的非常的多,可能会显得很罗嗦。见谅。

前面的一些纯文字介绍看的会很累。但是建议好好看一下。尤其是我贴上的微信的官方文档。里面其实已经写的非常清楚了。

前言

  • 微信公众号内支付在页面端较为简单。只需要调用微信内置的js方法,将微信需要的参数传递过去。
  • 与其他支付方式不同的是,公众号支付需要的参数有一项名为open_id。这个参数需要客户端请求固定连接、用户手动同意后才可以获取到。

开发

  开发前的准备

    • 申请微信商户

        略

    • 微信相关设置

        IP白名单:首页->基本配置->IP白名单。写入服务器的地址。

        进入微信商户中心,下载证书,安装控件、证书,设置API密钥(KEY)。

        设置支付授权目录。http://www.*.com/cms/wepay/。

    • 获取微信商户信息

        MCH_ID:首页->微信支付。可以查看到微信支付的商户号。

        APPID、APP_SECRET:首页->基本配置;可以找到开发者ID。同时重置APP_SECRET。

    • 注意1:我们已获得了4个微信支付的常量。包括APPID、APP_SECRET、MCH_ID、KEY;我们再加一个支付类型的常量:TRADE_TYPE=JSAPI。
    • 注意2:授权目录要求:1必须为通过ICP备案的地址。2访问项目端口必须为80或334。像我的项目最早部署在8080端口,就一直在报错。其中cms为项目名;wepay为controller的mapping值。下面是微信的要求。

        所有使用公众号支付方式发起支付请求的链接地址,都必须在支付授权目录之下;

        最多设置5个支付授权目录,且域名必须通过ICP备案;

        头部要包含http或https,须细化到二级或三级目录,以左斜杠“/”结尾。

  正式开发

    步骤1:【微信网页授权】获取用户open_id 。

     官方文档    官方测试地址(在线调试工具)

     简单说。设置好授权方式,来获取code。由code获取accessToken,由accessToken再获取用户的open_id和其他信息。

      1 第一步:用户同意授权,获取code

      2 第二步:通过code换取网页授权access_token

      3 第三步:刷新access_token(如果需要)。获得open_id

      4 第四步:拉取用户信息(需scope为 snsapi_userinfo)

      5 附:检验授权凭证(access_token)是否有效

    由微信的tab直接请求toPay方法。这个过程,首先是发起授权请求。用户允许后,开始获取openid。之后,再跳转至购买页面。

首先强调第一点:token是有区别的。
通过code换取的access_token(第二步)和之后拉取用户时的access_token(第四步)不一样。这点尤其需要注意。第二步的token是oauth2加密的token,而第四步的token只是普通的token。具体请看官方文档。
其次:token的使用。
下面代码中的使用方式为:每次使用时,都去微信api申请一个token。但是微信api每日调用token接口的次数是有限的(一天1万次)。所以需要更精确的对其进行控制。
可以将token存入数据库。使用定时任务刷新token。
我采用了比较简单的方式,将信息存到了session里面。取出时,判断下时间即可。

    步骤2 :【统一下单】准备工作完成。开始正式支付

     后台文档页面文档

    本项目操作过程为:用户手动点击支付按钮->获取支付需要的参数->将预付款订单参数传递给微信。

 1 function onBridgeReady(){
 2         WeixinJSBridge.invoke(
 3             'getBrandWCPayRequest', {
 4                  "appId":appId,     //公众号名称,由商户传入
 5                  "paySign":sign,         //微信签名
 6                  "timeStamp":timeStamp, //时间戳,自1970年以来的秒数
 7                  "nonceStr":nonceStr , //随机串
 8                  "package":packageStr,  //预支付交易会话标识
 9                  "signType":signType     //微信签名方式
10              },
11              function(res){
12           alert(JSON.stringify(res));//出错可以在这里看看.....
13                  if(res.err_msg == "get_brand_wcpay_request:ok" ) {
14                 //window.location.replace("index.html");
15                   alert('支付成功');
16               }else if(res.err_msg == "get_brand_wcpay_request:cancel"){
17                alert('支付取消');
18                }else if(res.err_msg == "get_brand_wcpay_request:fail" ){
19                 alert('支付失败');
20               }
21                  //使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。
22              }
23         );
24     }

    

    步骤3:【异步结果通知】微信支付异步回调通知

    后台文档

    • 该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
    • 通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”

    如果出现签名错误相关错误,我是用的此页面的校验工具进行测试的。微信在线校验工具

dto实体类。

用户信息类。如果只做支付不保存用户信息,此类可无。

public class UserInfo {
    
    private String openid;

    private String unionid;

    private String headimgurl;

    private String nickname;

    private String refreshtoken;

    private int siteid;

}

微信常量信息类。

public class WeChatConst {

    // 公众号支付APPID
    public static final String APPID = WePropertieUtil.getValue("APPID");
    
    // 公众号支付AppSecret
    public static final String APP_SECRET = WePropertieUtil.getValue("APP_SECRET");
    
    // 公众号支付商户号
    public static final String MCH_ID = WePropertieUtil.getValue("MCH_ID");
    
    // 商户后台配置的一个32位的key,位置:微信商户平台-账户中心-API安全
    public static final String KEY = WePropertieUtil.getValue("KEY");
    
    // 交易类型
    public static final String TRADE_TYPE = WePropertieUtil.getValue("TRADE_TYPE");
    
    // 根路径
    public static final String BASE_URL = WePropertieUtil.getValue("BASE_URL");
}

用户信息类。如果只做支付不保存用户信息,此类可无。

public class WeixinLoginUser implements Serializable {

    private static final long serialVersionUID = -8449856597137213512L;

    private String openID;

    private String unionID;

    private String headImageUrl;

    private String nickName;

    private String refreshToken;

    private int siteID;
}

微信支付参数。事实上我只用了里面极少一部分。

public class WxPaySendData {

    /**
     *  公众账号ID
     */
    private String appid;
    
    /**
     *  附加数据
     */
    private String attach;
    
    /**
     *  商品描述
     */
    private String body;
    /**
     *  商户号
     */
    private String mch_id;
    
    /**
     * 随机字符串
     */
    private String nonce_str;
    
    /**
     * 通知地址
     */
    private String notiry_url;
    
    /**
     * 商户订单号
     */
    private String out_trade_no;
    
    /**
     * 标价金额
     */
    private String total_fee;
    
    /**
     * 交易类型
     */
    private String trade_type;
    
    /**
     * 终端IP
     */
    private String spbill_create_ip;
    
    /**
     * 用户标识
     */
    private String openid;

    /**
     *  签名
     */
    private String sign;

    /**
     *  预支付id
     */
    private String prepay_id;
    
    /**
     * 签名类型:MD5
     */
    private String signType;
    
    /**
     * 时间戳
     */
    private String timeStamp;
    
    /**
     * 微信支付时用到的prepay_id
     */
    private String packageStr;

    private String return_code;
    private String return_msg;
    private String result_code;

    private String bank_type;
    private Integer cash_fee;
    private String fee_type;
    private String is_subscribe;
    private String time_end;

    /**
     *  微信支付订单号
     */
    private String transaction_id;
}

所用的工具类

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.http.HttpServletRequest;

/**
 * @Description http请求工具类
 */
public class HttpUtil {

    /**
     * 向指定URL发送GET方法的请求
     * 
     * @param url
     *            发送请求的URL
     * @param param
     *            请求Map参数,请求参数应该是 {"name1":"value1","name2":"value2"}的形式。
     * @param charset
     *            发送和接收的格式
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, Map<String, Object> map) {
        StringBuffer sb = new StringBuffer();
        // 构建请求参数
        if (map != null && map.size() > 0) {
            Iterator<?> it = map.entrySet().iterator(); // 定义迭代器
            while (it.hasNext()) {
                Map.Entry<?, ?> er = (Entry<?, ?>) it.next();
                sb.append(er.getKey());
                sb.append("=");
                sb.append(er.getValue());
                sb.append("&");
            }
        }
        return sendGet(url, sb.toString());
    }

    /**
     * 向指定URL发送POST方法的请求
     * 
     * @param url
     *            发送请求的URL
     * @param param
     *            请求Map参数,请求参数应该是 {"name1":"value1","name2":"value2"}的形式。
     * @param charset
     *            发送和接收的格式
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendPost(String url, Map<String, Object> map) {
        StringBuffer sb = new StringBuffer();
        // 构建请求参数
        if (map != null && map.size() > 0) {
            for (Entry<String, Object> e : map.entrySet()) {
                sb.append(e.getKey());
                sb.append("=");
                sb.append(e.getValue());
                sb.append("&");
            }
        }
        return sendPost(url, sb.toString());
    }

    /**
     * 向指定URL发送GET方法的请求
     * 
     * @param url
     *            发送请求的URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @param charset
     *            发送和接收的格式
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        String line;
        StringBuffer sb = new StringBuffer();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性 设置请求格式
            conn.setRequestProperty("contentType", "utf-8");
            conn.setRequestProperty("content-type",
                    "application/x-www-form-urlencoded");
            // 设置超时时间
            conn.setConnectTimeout(600);
            conn.setReadTimeout(600);
            // 建立实际的连接
            conn.connect();
            // 定义 BufferedReader输入流来读取URL的响应,设置接收格式
            in = new BufferedReader(new InputStreamReader(
                    conn.getInputStream(), "utf-8"));
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            result = sb.toString();
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 发送POST方法的请求
     * 
     * @param url
     *            发送请求的 URL
     * @param param
     *            请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @param charset
     *            发送和接收的格式
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        String line;
        StringBuffer sb = new StringBuffer();
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性 设置请求格式
            conn.setRequestProperty("contentType", "utf-8");
            conn.setRequestProperty("content-type",
                    "application/x-www-form-urlencoded");
            // 设置超时时间
            conn.setConnectTimeout(600);
            conn.setReadTimeout(600);
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应 设置接收格式
            in = new BufferedReader(new InputStreamReader(
                    conn.getInputStream(), "utf-8"));
            while ((line = in.readLine()) != null) {
                sb.append(line);
            }
            result = sb.toString();
        } catch (Exception e) {
            System.out.println("发送 POST请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }

    public static String getRemoteIpAddr(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
    
// 如果手机是双卡双待,那么会获取到两个IP。我们仅需要有一个。
    return ip.split(",")[0];
  }
import java.security.MessageDigest;
 
public class MD5Util {
    private final static String[] hexDigits = { "0", "1", "2", "3", "4", "5",
            "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };

    /**
     * MD5加密解密工具类
     * 
     * @param b
     *            字节数组
     * @return 16进制字串
     */
    public static String byteArrayToHexString(byte[] b) {
        StringBuffer resultSb = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            resultSb.append(byteToHexString(b[i]));
        }
        return resultSb.toString();
    }

    private static String byteToHexString(byte b) {
        int n = b;
        if (n < 0)
            n = 256 + n;
        int d1 = n / 16;
        int d2 = n % 16;
        return hexDigits[d1] + hexDigits[d2];
    }

    public static String MD5Encode(String origin) {
        String resultString = null;
        try {
            resultString = new String(origin);
            MessageDigest md = MessageDigest.getInstance("MD5");
            resultString = byteArrayToHexString(md.digest(resultString
                    .getBytes("utf-8")));
        } catch (Exception ex) {
        }
        return resultString;
    }
    
    public static boolean isValidate(String input,String output){
        
        boolean status = false;
        
        if(MD5Util.MD5Encode(input).equals(output)){
            status = true;
        }else{
            status = false;
        }
        
        return status;
    }
    
}
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.meihe.dto.wePay.WeChatConst;

@SuppressWarnings("deprecation")
public class WeChatUtil {

    private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class);

    /**
     * 统一下单 获得PrePayId
     * 
     * @param body
     *            商品或支付单简要描述
     * @param out_trade_no
     *            商户系统内部的订单号,32个字符内、可包含字母
     * @param total_fee
     *            订单总金额,单位为分
     * @param IP
     *            APP和网页支付提交用户端ip
     * @param notify_url
     *            接收微信支付异步通知回调地址
     * @param openid
     *            用户openId
     * @throws Exception 
     */
    public static String unifiedorder(String body, String out_trade_no,
            Integer total_fee, String ip, String notify_url, String openId)
            throws Exception {
        log.debug(">>>>>>>>>>>>>>>>>开始准备统一下单流程....");
        
        /**
         * 组装请求参数 按照ASCII排序
         */
        SortedMap<Object, Object> parameters = new TreeMap<Object, Object>();
        String nonce_str = getNonceStr();// 随机数据
        parameters.put("appid", WeChatConst.APPID);
        parameters.put("body", body);
        parameters.put("mch_id", WeChatConst.MCH_ID);
        parameters.put("nonce_str", nonce_str);
        parameters.put("notify_url", notify_url);
        parameters.put("openid", openId);
        parameters.put("out_trade_no", out_trade_no);
        parameters.put("spbill_create_ip", ip);
        parameters.put("total_fee", total_fee.toString());
        parameters.put("trade_type", WeChatConst.TRADE_TYPE);
        String sign = WxSign.createSign(parameters, WeChatConst.KEY);

        /**
         * 组装XML
         */
        log.debug(">>>>>>>>>>>>>>>>>统一下单数据准备完成,准备组装....{}",parameters.toString());
        StringBuilder sb = new StringBuilder("");
        sb.append("<xml>");
        setXmlKV(sb, "appid", WeChatConst.APPID);
        setXmlKV(sb, "body", body);
        setXmlKV(sb, "mch_id", WeChatConst.MCH_ID);
        setXmlKV(sb, "nonce_str", nonce_str);
        setXmlKV(sb, "notify_url", notify_url);
        setXmlKV(sb, "openid", openId);
        setXmlKV(sb, "out_trade_no", out_trade_no);
        setXmlKV(sb, "spbill_create_ip", ip);
        setXmlKV(sb, "total_fee", total_fee.toString());
        setXmlKV(sb, "trade_type", WeChatConst.TRADE_TYPE);
        setXmlKV(sb, "sign", sign);
        sb.append("</xml>");
        // 这个处理是为了防止传中文的时候出现签名错误
        StringEntity reqEntity = new StringEntity(new String(sb.toString().getBytes("UTF-8"), "ISO8859-1"));
        log.debug(">>>>>>>>>>>>>>>>>统一下单数据组装完成,准备下单。");
        
        HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");
        httppost.setEntity(reqEntity);
        log.debug(">>>>>>>>>>>>>>>>>请求地址、请求数据封装完成。开始下单。");
        
        @SuppressWarnings("resource")
        DefaultHttpClient httpclient = new DefaultHttpClient();
        HttpResponse response = httpclient.execute(httppost);
        
        log.debug(">>>>>>>>>>>>>>>>>下单请求结束。获取返回值。");
        String strResult = EntityUtils.toString(response.getEntity(),Charset.forName("utf-8"));
        log.debug(">>>>>>>>>>>>>>>>>下单请求结束。转换字符串返回值结束。");
        
        return strResult;

    }

    // 获得随机字符串
    public static String getNonceStr() {
        String str = "";
        Random random = new Random();
        try {
            str = MD5Util.MD5Encode(String.valueOf(random.nextInt(10000)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return str;
    }

    // 插入XML标签
    public static StringBuilder setXmlKV(StringBuilder sb, String Key,
            String value) {
        sb.append("<");
        sb.append(Key);
        sb.append(">");

        sb.append(value);

        sb.append("</");
        sb.append(Key);
        sb.append(">");

        return sb;
    }

    // 解析XML 获得 PrePayId
    public static String getPrePayId(String xml) {
        int start = xml.indexOf("<prepay_id>");
        int end = xml.indexOf("</prepay_id>");
        if (start < 0 && end < 0) {
            return null;
        }
        return xml.substring(start + "<prepay_id>".length(), end)
                .replace("<![CDATA[", "").replace("]]>", "");
    }

    // 商户订单号
    public static String getOut_trade_no() {
        DateFormat df = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return df.format(new Date()) + buildRandom(7);
    }

    // 时间戳
    public static String getTimeStamp() {
        return String.valueOf(System.currentTimeMillis() / 1000);
    }

    // 随机4位数字
    public static int buildRandom(int length) {
        int num = 1;
        double random = Math.random();
        if (random < 0.1) {
            random = random + 0.1;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

    public static String inputStream2String(InputStream inStream,
            String encoding) {
        String result = null;
        try {
            if (inStream != null) {
                ByteArrayOutputStream outStream = new ByteArrayOutputStream();
                byte[] tempBytes = new byte[1024];
                int count = -1;
                while ((count = inStream.read(tempBytes, 0, 1024)) != -1) {
                    outStream.write(tempBytes, 0, count);
                }
                tempBytes = null;
                outStream.flush();
                result = new String(outStream.toByteArray(), encoding);
            }
        } catch (Exception e) {
            result = null;
        }
        return result;
    }

}
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Properties;

/**
 * 读取微信properties配置文件
 * 
 *
 */
public class WePropertieUtil {

    private static Properties env;

    private static Properties getEnv() {
        try {
            if (env == null) {
                env = new Properties();
                env.load(WePropertieUtil.class.getClassLoader().getResourceAsStream("config/wechat-const.properties"));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return env;
    }

    /**
     * 获取对应的key
     * 
     * @param key
     * @return
     */
    public static String getValue(String key) {
        if (env == null) {
            return getEnv().getProperty(key);
        } else {
            return env.getProperty(key);
        }
    }

    /**
     * 获取key对应的值,并替换其中的参数
     * 
     * @param key
     * @param arguments
     * @return
     */
    public static String getValue(String key, Object... arguments) {
        return MessageFormat.format(getValue(key), arguments);
    }
    
}
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WxSign {
    
    private static final Logger log = LoggerFactory.getLogger(WeChatUtil.class);

    /**
     * 签名封装类。创建签名。
     * @param parameters
     * @param key
     * @return
     * @throws Exception
     */
    @SuppressWarnings("rawtypes")
    public static String createSign(SortedMap<Object, Object> parameters,String key) throws Exception {
        log.info(">>>>>>>>>>>>>>>>>开始封装数据,创建签名....");
        StringBuffer sb = new StringBuffer();
        // 所有参与传参的参数按照accsii排序(升序)
        Set es = parameters.entrySet();
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k + "=" + v + "&");
            }
        }
        sb.append("key=" + key);
        log.info(">>>>>>>>>>>>>>>>>封装数据{}",sb.toString());
        String str = sb.toString();
        log.info(str);
        String sign = MD5Util.MD5Encode(str).toUpperCase();
        log.info(">>>>>>>>>>>>>>>>>封装数据完成,签名为:{}",sign);
        return sign;
    }
    
}

 

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.TimeZone;

import org.apache.commons.lang3.StringUtils;

public abstract class DateTimeUtils {

    /**
     * 获取今天日期
     * 
     * @return
     */
    public static String getToday(String format) {
        if (StringUtils.isEmpty(format)) {
            format = "yyyyMMdd";
        }
        return LocalDate.now().format(DateTimeFormatter.ofPattern(format));
    }

    /**
     * 获取当前时间
     * 
     * @param format
     * @return
     */
    public static String getCurrentTime(String format) {
        if (StringUtils.isEmpty(format)) {
            format = "HHmmss";
        }
        return LocalTime.now().format(DateTimeFormatter.ofPattern(format));

    }
    
    /**
     * 将字符串转换成LocalDate格式
     * 
     * @param format
     * @return
     * @throws ParseException 
     */
    public static LocalDate toLocalDate(String LocalDate) throws ParseException{
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        Date date = sdf.parse(LocalDate);
        Instant instant = date.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
        LocalDate localDate = localDateTime.toLocalDate();
        return localDate;
    }
    
    /**
     * 将字符串转换成LocalTime格式
     * 
     * @param format
     * @return
     * @throws ParseException 
     */
    public static LocalTime toLocalTime(String LocalTime) throws ParseException{
        SimpleDateFormat sdf = new SimpleDateFormat("HHmmss");
        sdf.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
        Date time = sdf.parse(LocalTime);
        Instant instant = time.toInstant();
        ZoneId zone = ZoneId.systemDefault();
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, zone);
        LocalTime localTime = localDateTime.toLocalTime();
        return localTime;
    }

}

准备完成!开始进行业务操作。

代码流程:页面->controller->service;

 支付页面:

    // 页面在执行成功之前,pay方法只允许调用一次。
    clickstatus=true;
    var sign ;
    var appId ;
    var timeStamp ;
    var nonceStr ;
    var packageStr ;
    var signType,
    var url = 'http://**/cms/wepay/';
    function pay(){
        if(clickstatus){
            clickstatus=false;
            $.ajax({
                type:"post",
                url:url+'pay',
                dataType:"json",
                data:{openId:'${openId}',totalFee:totalFee,pointAmount:pointAmount,memberId:$.cookie('memberId'),cellphone: $.cookie('cellphone'),accessToken: $.cookie('accessToken')},
                success:function(data) {
                    if(data.result_code == 'SUCCESS'){
                        appId = data.appid;
                        sign = data.sign;
                        timeStamp = data.timeStamp;
                        nonceStr = data.nonce_str;
                        packageStr = data.packageStr;
                        signType = data.signType;
                        // 调用微信支付控件
                        if (typeof WeixinJSBridge == "undefined"){
                            if( document.addEventListener ){
                                document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
                            }else if (document.attachEvent){
                                document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
                                document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
                            }
                        }else{
                            onBridgeReady();
                        }
                    }else{
                        alert("统一下单失败");
                    }
                }
            })
        }
    }

    function onBridgeReady(){
        WeixinJSBridge.invoke('getBrandWCPayRequest', {
            "appId":appId,
            "timeStamp":timeStamp,
            "nonceStr":nonceStr,
            "package": packageStr,
            "signType":signType,
            "paySign": sign
        },function (res) {
            if (res.err_msg == "get_brand_wcpay_request:ok") {
                window.location.href = url+'paySuccess';
            } else if (res.err_msg == "get_brand_wcpay_request:fail") {
                window.location.href = url+'payFailure';
            } else if (res.err_msg == "get_brand_wcpay_request:cancel") {
                alert("支付取消");
            }
        })
    }

Controller

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;

import com.meihe.dto.wePay.WxPaySendData;
import com.meihe.service.wePay.IPreparedToPayService;

@RestController
@RequestMapping("wepay")
public class wePayController {

    @Autowired
    private IPreparedToPayService preparedToPayService;

    /**
     *   跳转支付界面,将code带过去
     **/
    @ResponseBody
    @RequestMapping("toPay")
    public ModelAndView toPay(HttpServletRequest request,HttpServletResponse response){
        ModelAndView res = new  ModelAndView();
        try {
            res = preparedToPayService.getUserOpenId(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;  
    }

    /**
     *  点击确认充值 统一下单,获得预付id(prepay_id)   
     * @param request
     * @param response
     * @return   
     */
    @ResponseBody
    @RequestMapping("pay")
    public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response,String openId, String totalFee, String pointAmount,
            String memberId){
        WxPaySendData data = new WxPaySendData();
        try {
            data = preparedToPayService.prePay(request, response, openId,totalFee,pointAmount,memberId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return data;
    }
    
    /**
     * 微信支付回调接口
     * @param request
     * @param response
     * @throws Exception 
     */
    @ResponseBody
    @RequestMapping("callback")
    public void callBack(HttpServletRequest request, HttpServletResponse response) {
        try {
            preparedToPayService.callBack(request, response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 支付成功
     **/
    @ResponseBody
    @RequestMapping("paySuccess")
    public ModelAndView paySuccess(HttpServletRequest request,HttpServletResponse response){
        return new ModelAndView("wepay/paySuccess");
    }
    
    /**
     * 支付失败
     **/
    @ResponseBody
    @RequestMapping("payFailure")
    public ModelAndView payFailure(HttpServletRequest request,HttpServletResponse response){
        return new ModelAndView("wepay/payFailure");  
    }

Service

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.ModelAndView;

import com.alibaba.fastjson.JSON;
import com.meihe.dto.AjaxResult;
import com.meihe.dto.wePay.UserInfo;
import com.meihe.dto.wePay.WeChatConst;
import com.meihe.dto.wePay.WeixinLoginUser;
import com.meihe.dto.wePay.WxPaySendData;
import com.meihe.model.marketing.MarketingOrder;
import com.meihe.service.marketing.IMarketingOrderService;
import com.meihe.service.wePay.IOauthService;
import com.meihe.service.wePay.IPreparedToPayService;
import com.meihe.utils.wePayUtils.HttpUtil;
import com.meihe.utils.wePayUtils.WeChatUtil;
import com.meihe.utils.wePayUtils.WxSign;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;


@Service
public class PreparedToPayServiceImpl implements IPreparedToPayService {
    
    @Autowired
    private IOauthService oauth = null;
    
    @Autowired
    private IMarketingOrderService mOrder = null;

    private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class);
    
    @Override
    public ModelAndView getUserOpenId(HttpServletRequest request,HttpServletResponse response) throws Exception {
        log.info(">>>>>>>>>>>>>>>>>准备获取用户code....");
        ModelAndView modelAndView = new ModelAndView();
        @SuppressWarnings("deprecation")
        String redirecUri = URLEncoder.encode(WeChatConst.BASE_URL + "toPay");
        String code = null;
        if (request.getParameter("code") != null) {
            code = request.getParameter("code");
        }
        if (code == null) {
            log.info(">>>>>>>>>>>>>>>>>用户首次登陆,授权中...准备获取code....");
            String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid="
                    +WeChatConst.APPID+"&redirect_uri="+redirecUri+
                    "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect";
            return new ModelAndView(siteURL);
        }
        code = request.getParameter("code");
        log.debug(">>>>>>>>>>>>>>>>>获得用户code[{}]成功!准备获取用户信息...",code);
        WeixinLoginUser weixinLoginUser = getWeixinLoginUser(code,request,response);
        if(null == weixinLoginUser){
            log.debug(">>>>>>>>>>>>>>>>>code[{}]已被使用,重新获取code...",code);
            // 进入页面后,如果用户刷新,那么会提示code被使用的错误。此判断主要应对获取用户信息后刷新的操作。
            String siteURL = "redirect:https://open.weixin.qq.com/connect/oauth2/authorize?appid="
                    +WeChatConst.APPID+"&redirect_uri="+redirecUri+
                    "&response_type=code&scope=snsapi_userinfo&state=1234#wechat_redirect";
            return new ModelAndView(siteURL);
        }
        modelAndView.addObject("openId", weixinLoginUser.getOpenID());
        request.setAttribute("openId", weixinLoginUser.getOpenID());
        log.debug(">>>>>>>>>>>>>>>>>准备工作完成!跳转至购买页面...");
        String viewName = "wepay/payIndex";
        modelAndView.setViewName(viewName);
        return modelAndView;
    }
    
    /**
     * 获取微信授权登陆用户
     * 
     * @param code
     * @return
     * @throws Exception
     */
    private WeixinLoginUser getWeixinLoginUser(String code,HttpServletRequest request,HttpServletResponse response) throws Exception {
        String str = oauth.getToken(code, WeChatConst.APPID,WeChatConst.APP_SECRET);
        String openID = (String) JSON.parseObject(str, Map.class).get("openid");
        String accessToken = (String) JSON.parseObject(str, Map.class).get("access_token");
        String refreshToken = (String) JSON.parseObject(str, Map.class).get("refresh_token");
        if(null == openID || null == accessToken || null == refreshToken){
            return null;
        }
        log.debug(">>>>>>>>>>>>>>>>>openid[{}],access_token[{}],refresh_token[{}]..转JSON为String中...",openID,accessToken,refreshToken);
        // 获取到的用户信息。暂时没用。后台暂时只保存一个openId。
        // 支付只需要用户的openId即可完成。这个方法可以在此结束。
        UserInfo userInfo = oauth.getSnsUserInfo(openID, accessToken);
        WeixinLoginUser weixinLoginUser = new WeixinLoginUser();
        weixinLoginUser.setOpenID(openID);
        weixinLoginUser.setUnionID(userInfo.getUnionid());
        weixinLoginUser.setHeadImageUrl(userInfo.getHeadimgurl());
        weixinLoginUser.setNickName(userInfo.getNickname());
        weixinLoginUser.setRefreshToken(refreshToken);
        log.debug(">>>>>>>>>>>>>>>>>获取用户信息中完成!");
        return weixinLoginUser;
    }

    @Override
    public WxPaySendData prePay(HttpServletRequest request,HttpServletResponse response, String openId, String totalFee, String pointAmount, String memberId) throws Exception {
        log.debug(">>>>>>>>>>>>>>>>>预备工作完成。开始准备预付订单信息...");
        WxPaySendData result = new WxPaySendData();
        MarketingOrder marketingOrder = new MarketingOrder();
        // 商户订单号 
            String out_trade_no = WeChatUtil.getOut_trade_no();
            // 产品价格,单位:分
            Double total_fee = Double.parseDouble(totalFee)*100;
            // 客户端IP
            String ip = HttpUtil.getRemoteIpAddr(request);
            // 支付成功后回调的url地址
            String notify_url = WeChatConst.BASE_URL+"callback";
            // 商品或支付产品的简要描述
            String body = pointAmount+"积分";
            log.debug(">>>>>>>>>>>>>>>>>预付订单信息准备完成。");
            log.debug(">>>>>>>>>>>>>>>>>订单号:[{}],价格:[{}],客户端IP[{}],回调URL:[{}],商品支付信息:[{}]",out_trade_no,total_fee,ip,notify_url,body);
            //统一下单
            String strResult = WeChatUtil.unifiedorder(body, out_trade_no, total_fee.intValue(), ip, notify_url,openId);
        
            log.debug(">>>>>>>>>>>>>>>>>解析统一下单XML....");
            XStream stream = new XStream(new DomDriver());
            stream.alias("xml", WxPaySendData.class);
            WxPaySendData wxReturnData = (WxPaySendData)stream.fromXML(strResult);
            log.debug(">>>>>>>>>>>>>>>>>统一下单XML解析完成....[{}]",wxReturnData.toString());
            
            //两者都为SUCCESS才能获取prepay_id
            if(wxReturnData.getResult_code().equals("SUCCESS") &&wxReturnData.getReturn_code().equals("SUCCESS") ){
                log.debug(">>>>>>>>>>>>>>>>>统一下单请求成功!开始准备正式支付....");
                //时间戳、随机字符串
                String timeStamp = WeChatUtil.getTimeStamp();
            String nonce_str = WeChatUtil.getNonceStr();
            // 注:上面这两个参数,一定要拿出来作为后续的value,不能每步都创建新的时间戳跟随机字符串,不然H5调支付API,会报签名参数错误
            result.setResult_code(wxReturnData.getResult_code());
            result.setAppid(WeChatConst.APPID);
            result.setTimeStamp(timeStamp);
            result.setNonce_str(nonce_str);
            result.setPackageStr("prepay_id="+wxReturnData.getPrepay_id());
            result.setSignType("MD5");
            
            // 第二次签名,将微信返回的数据再进行签名
            SortedMap<Object,Object> signMap = new TreeMap<Object,Object>();
            signMap.put("appId", WeChatConst.APPID);
            signMap.put("nonceStr", nonce_str);
            // 值为:prepay_id=xxx
            signMap.put("package", "prepay_id="+wxReturnData.getPrepay_id());  
            signMap.put("signType", "MD5");
            signMap.put("timeStamp", timeStamp);
            log.debug(">>>>>>>>>>>>>>>>>根据预支付订单号再次进行签名....");
            String paySign = WxSign.createSign(signMap, WeChatConst.KEY);
            log.debug(">>>>>>>>>>>>>>>>>签名完成!准备生成本地支付订单....");
            result.setSign(paySign);
        } else {
            result.setResult_code("fail");
        }
            log.debug(">>>>>>>>>>>>>>>>>开始生成一条新的本地支付订单。");
          marketingOrder.setPaymentType(1);
          marketingOrder.setMemberId(Integer.parseInt(memberId));
          marketingOrder.setOutTradeNo(out_trade_no);
          marketingOrder.setTotalFee(total_fee.toString());
          marketingOrder.setPointAmount(pointAmount);
          marketingOrder.setOpenId(openId);
          AjaxResult ajaxResult = mOrder.insertMarketingOrder(marketingOrder);
          if(!ajaxResult.getSuccess()){
              result.setResult_code("fail");
          }
          log.debug(">>>>>>>>>>>>>>>>>开始正式支付!");
            return result;
    }
    
    @Override
    public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception{
        log.debug(">>>>>>>>>>>>>>>>>微信开始回调本地。校验签名。准备校验签名....");
        response.setContentType("text/xml;charset=UTF-8");
        try {
            InputStream is = request.getInputStream();
            String result = IOUtils.toString(is, "UTF-8");
            if("".equals(result)){
                response.getWriter().write("<xm><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数错误!]]></return_msg></xml>");
                return ;
            }
            // 解析xml
            XStream stream = new XStream(new DomDriver());
            stream.alias("xml", WxPaySendData.class);
            WxPaySendData wxPaySendData = (WxPaySendData)stream.fromXML(result);
            System.out.println(wxPaySendData.toString());
                
            String appid = wxPaySendData.getAppid();
            String mch_id =wxPaySendData.getMch_id();
            String nonce_str = wxPaySendData.getNonce_str();
            String out_trade_no = wxPaySendData.getOut_trade_no();
            String total_fee = wxPaySendData.getTotal_fee();
            String trade_type = wxPaySendData.getTrade_type();
            String openid =wxPaySendData.getOpenid();
            String return_code = wxPaySendData.getReturn_code();
            String result_code = wxPaySendData.getResult_code();
            String bank_type = wxPaySendData.getBank_type();
            Integer cash_fee = wxPaySendData.getCash_fee();
            String fee_type = wxPaySendData.getFee_type();
            String is_subscribe = wxPaySendData.getIs_subscribe();
            String time_end = wxPaySendData.getTime_end();
            String transaction_id = wxPaySendData.getTransaction_id();
            String sign = wxPaySendData.getSign();
            
            //签名验证
            SortedMap<Object,Object> parameters = new TreeMap<Object,Object>();
            parameters.put("appid",appid);
            parameters.put("mch_id",mch_id);
            parameters.put("nonce_str",nonce_str);
            parameters.put("out_trade_no",out_trade_no);
            parameters.put("total_fee",total_fee);
            parameters.put("trade_type",trade_type);
            parameters.put("openid",openid);
            parameters.put("return_code",return_code);
            parameters.put("result_code",result_code);
            parameters.put("bank_type",bank_type);
            parameters.put("cash_fee",cash_fee);
            parameters.put("fee_type",fee_type);
            parameters.put("is_subscribe",is_subscribe);
            parameters.put("time_end",time_end);
            parameters.put("transaction_id",transaction_id);
            
            String sign2 = WxSign.createSign(parameters, WeChatConst.KEY);
            
            if(sign.equals(sign2)){
                log.debug(">>>>>>>>>>>>>>>>>校验签名完成。正常交易。开始校验支付状态...");
                if(return_code.equals("SUCCESS") && result_code.equals("SUCCESS")){
                    log.debug(">>>>>>>>>>>>>>>>>校验支付状态完成。支付成功!");
                    // 业务逻辑(先判断数据库中订单号是否存在,并且订单状态为未支付状态)
                    MarketingOrder marketingOrder = new MarketingOrder();
                    marketingOrder.setOutTradeNo(out_trade_no);
                    marketingOrder.setPayAmount(total_fee);
                    AjaxResult affordOrder = mOrder.affordOrder(marketingOrder);
                    if(!affordOrder.getSuccess()){
                        response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失败]]></return_msg></xml>");
                    }
                    // TODO:这个有什么用吗?暂未确定...
                    request.setAttribute("out_trade_no", out_trade_no);
                    // 通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
                    log.debug(">>>>>>>>>>>>>>>>>本地异步确认支付成功!交易结束!");
                    response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
                    }else{
                        log.debug(">>>>>>>>>>>>>>>>>本地异步确认支付失败!交易结束!");
                        response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[交易失败]]></return_msg></xml>");
                    }
                }else{
                    log.debug(">>>>>>>>>>>>>>>>>校验签名完成。异常交易!结束交易流程。");
                    response.getWriter().write("<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名校验失败]]></return_msg></xml>");
                }
                response.getWriter().flush();
                response.getWriter().close();
                return ;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.meihe.dto.wePay.UserInfo;
import com.meihe.service.wePay.IOauthService;
import com.meihe.utils.wePayUtils.HttpUtil;

@Service
public class OauthServiceImpl implements IOauthService {
    
    private static final Logger log = LoggerFactory.getLogger(PreparedToPayServiceImpl.class);
    
//    private static final String CODE_URI = "http://open.weixin.qq.com/connect/oauth2/authorize";
    private static final String TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/access_token";
    private static final String REFRESH_TOKEN_URI = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
    private static final String SNS_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo";

    public String getToken(String code, String appid, String secret) throws Exception {
        log.debug(">>>>>>>>>>>>>>>>>准备根据code获取用户open_id/accessToken..",code,appid,secret);
        Map<String,Object> params = new HashMap<String,Object>();
        params.put("appid", appid);
        params.put("secret", secret);
        params.put("code", code);
        params.put("grant_type", "authorization_code");
        String result = HttpUtil.sendGet(TOKEN_URI,params);
        log.debug(">>>>>>>>>>>>>>>>>获取信息完成[{}]...",result);
        return result;
    }

    public String getRefreshToken(String refreshToken, String appid,String secret) throws Exception {
        Map<String,Object> params = new HashMap<String,Object>();
        params.put("appid", appid);
        params.put("grant_type", "refresh_token");
        params.put("refresh_token", refreshToken);
        return HttpUtil.sendGet(REFRESH_TOKEN_URI, params);
    }

    public UserInfo getSnsUserInfo(String openid, String accessToken)throws Exception {
        Map<String,Object> params = new HashMap<String,Object>();
        log.info(">>>>>>>>>>>>>>>>>转格式成功!准备根据open_id/accessToken获取用户信息...!");
        params.put("access_token", accessToken);
        params.put("openid", openid);
        params.put("lang", "zh_CN");
        log.debug(">>>>>>>>>>>>>>>>>token:[{}],openid[{}].开始连接微信获取信息..",accessToken,openid);
        String jsonStr = HttpUtil.sendGet(SNS_USER_INFO_URL, params);
        log.debug(">>>>>>>>>>>>>>>>>成功获取信息[{}]。转JSON为UserInfo..",jsonStr);
        if (StringUtils.isNotEmpty(jsonStr)) {
            JSONObject obj = JSONObject.parseObject(jsonStr);
            if (obj.get("errcode") != null) {
                throw new Exception(obj.getString("errmsg"));
            }
            UserInfo user = (UserInfo) JSONObject.toJavaObject(obj,UserInfo.class);
            log.debug(">>>>>>>>>>>>>>>>>转UserInfo格式完成!");
            return user;
        }
        return null;
    }
}

 

转载于:https://www.cnblogs.com/yugure/p/7090605.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值