微信支付-公众号支付-JSAPI调用(Java)

  1. 开发微信公众号支付准备资料
    ①APPID,这个数据我们可以从“申请微信支付成功”的邮件中获取。

    微信支付-公众号支付-JSAPI调用(Java)_第1张图片

    也可以从微信公众平台→开发→基本配置
    微信支付-公众号支付-JSAPI调用(Java)_第2张图片

    ②AppSecret,这个数据,大家可以看上图中获取。
    ③Mch_id,这是值是微信支付商户号,大家可以从邮件中获取
    ④KEY,这个参数KEY是在商户后台配置的一个32位的key,微信商户平台-账户设置-安全设置-api安全,在这里设置。这个值是可以自行设置的。

  2. 开发微信支付首先要看微信支付的业务流程,看官方文档。
    这里写图片描述
    微信支付的流程:
    →用户访问微信OAuth2.0网站,通过OAuth2.0的重定向获得code
    →根据code获得用户的标识符openid,这个参数在调用统一下单接口中会用到
    →调用统一下单API获得prepay_id
    →获得prepay_id后,接下来要调用通过“网页端调起支付API”,这个调用的API需要一系列参数,这些参数我们在后台进行组装,通过JSON传到前台去
    → 获得数据后通过调用JSPAI发起微信支付
    → 用户输入支付密码,支付完成
    → 等待微信回调,回调中处理业务
    →流程结束

    微信支付,这里我给大家详细介绍

    ①maven项目的pom.xml


    <dependencies>
        <dependency>
            <groupId>commons-codecgroupId>
            <artifactId>commons-codecartifactId>
            <version>1.6version>
        dependency>
        <dependency>
            <groupId>commons-logginggroupId>
            <artifactId>commons-loggingartifactId>
            <version>1.1.3version>
        dependency>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>fluent-hcartifactId>
            <version>4.3.6version>
        dependency>

        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclientartifactId>
            <version>4.3.6version>
        dependency>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclient-cacheartifactId>
            <version>4.3.6version>
        dependency>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpcoreartifactId>
            <version>4.3.3version>
        dependency>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpmimeartifactId>
            <version>4.3.6version>
        dependency>
        <dependency>
            <groupId>org.jsoupgroupId>
            <artifactId>jsoupartifactId>
            <version>1.7.2version>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.1version>
        dependency>
    dependencies>
 ②获得openid
 官方文档https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4
 先要设置OAuth2.0授权回调页面域名,不然会出现“redirect uri 参数错误”
 微信公众平台→开发→接口权限→网页服务→网页账号


 设置好后,根据文档组装OAuth2.0的URL
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize"//这个是文档里面提供的url
                + "?appid=" + appid//这个填写之前准备的appid
                + "&redirect_uri=" + REDIRECT_URI //这个是用户确认授权后重定向的url,url需要进行Enconding编码,微信会在这个url上附加参数code,后台可以用   String code = request.getParameter("code"); 获取到
                + "&response_type=code" //必填,这个参数不用改
                + "&scope=" + SCOPE// 这个参数建议填写"snsapi_base",就是让用户默认授权,即不会弹出是否授权界面
                + "&state=" +STATE//这个值可以随便修改,会附加在回调url中
                + "#wechat_redirect"; //必填,这个参数不用改

组装好url后,让用户访问。在我设置的回调url中,我们可以通过
String code = request.getParameter("code");
获得code值。
直接我写了个工具类是关于微信支付一些简单的调用

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import ***********************.WeChatConst;//一些常量
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 java.io.*;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/**
 * Created by Arthur on 2015/11/10 16:45.
 */
public class WeChatUtil {

    /**
     * 通过Code 获取用户 openid 和 access_token
     * @param code
     * @return
     * @throws IOException
     */
    public static Map getOpenIdByCode(String code) throws IOException {

        /**
         * 设置访问路径
         */
        HttpPost httppost = new HttpPost("https://api.weixin.qq.com/sns/oauth2/access_token");

        /**
         * 组装请求参数
         */
        String reqEntityStr = "appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";
        reqEntityStr = reqEntityStr.replace("APPID", WeChatConst.APPID);
        reqEntityStr = reqEntityStr.replace("SECRET", WeChatConst.APP_SECRET);
        reqEntityStr = reqEntityStr.replace("CODE", code);

        StringEntity reqEntity = new StringEntity(reqEntityStr);

        /**
         * 设置浏览器
         */
        DefaultHttpClient httpclient = new DefaultHttpClient();

        /**
         * 设置参数
         */
        httppost.setEntity(reqEntity);

        /**
         * 发起请求
         */
        HttpResponse response = httpclient.execute(httppost);

        /**
         * 获得请求内容
         */
        String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));

        /**
         * 分析内容
         */
        JSONObject jsonObject = (JSONObject) JSON.parse(strResult);
        Map map = new HashMap<>();
        map.put("openid",jsonObject.get("openid"));
        map.put("access_token",jsonObject.get("access_token"));
        map.put("refresh_token",jsonObject.get("refresh_token"));

        return map;
    }

    /**
     * 统一下单
     * 获得PrePayId
     * @param body   商品或支付单简要描述
     * @param out_trade_no 商户系统内部的订单号,32个字符内、可包含字母
     * @param total_fee  订单总金额,单位为分
     * @param IP    APP和网页支付提交用户端ip
     * @param notify_url 接收微信支付异步通知回调地址
     * @param openid 用户openId
     * @throws IOException
     */
    public static String unifiedorder(String body,String out_trade_no,Integer total_fee,String IP,String notify_url,String openid)throws IOException {
        /**
         * 设置访问路径
         */
        HttpPost httppost = new HttpPost("https://api.mch.weixin.qq.com/pay/unifiedorder");

        /**
         * 组装请求参数
         * 按照ASCII排序
         */
        String reqEntityStr = "appid=APPID" +
                "&body=BODY" +

                "&mch_id=MCH_ID" +
                "&nonce_str=NONCE_STR" +
                "¬ify_url=NOTIFY_URL" +

                "&openid=OPENID" +
                "&out_trade_no=OUT_TRADE_NO" +

                "&spbill_create_ip=IP" +

                "&total_fee=TOTAL_FEE" +
                "&trade_type=TRADE_TYPE"


                ;//这个字段是用于之后MD5加密的,字段要按照ascii码顺序排序

        /**
         * 设置数据
         */
        String nonce_str = getNonceStr().toUpperCase();//随机数据
        reqEntityStr = reqEntityStr.replace("APPID", WeChatConst.APPID);
        reqEntityStr = reqEntityStr.replace("MCH_ID", WeChatConst.MCH_ID);
        reqEntityStr = reqEntityStr.replace("NONCE_STR", nonce_str);
        reqEntityStr = reqEntityStr.replace("BODY", body);
        reqEntityStr = reqEntityStr.replace("OUT_TRADE_NO", out_trade_no);
        reqEntityStr = reqEntityStr.replace("TOTAL_FEE", total_fee.toString());
        reqEntityStr = reqEntityStr.replace("IP", IP);
        reqEntityStr = reqEntityStr.replace("NOTIFY_URL", notify_url);
        reqEntityStr = reqEntityStr.replace("TRADE_TYPE", WeChatConst.TRADE_TYPE);
        reqEntityStr = reqEntityStr.replace("OPENID", openid);

        /**
         * 生成sign的数据
         */
        String sign = reqEntityStr + "&key="+WeChatConst.KEY;
        sign = MD5Util.MD5(sign).toUpperCase();

        /**
         * 组装XML
         */
        StringBuilder sb = new StringBuilder("");
        sb.append("");
        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("");



//        reqEntityStr = reqEntityStr.replace("sign", sign);
        StringEntity reqEntity = new StringEntity(new String (sb.toString().getBytes("UTF-8"),"ISO8859-1"));//这个处理是为了防止传中文的时候出现签名错误
        httppost.setEntity(reqEntity);
        DefaultHttpClient httpclient = new DefaultHttpClient();
        HttpResponse response = httpclient.execute(httppost);
        String strResult = EntityUtils.toString(response.getEntity(), Charset.forName("utf-8"));


//        System.out.println(strResult);

        return getPrePayId(strResult);

    }


    /**
     * 获得32位随机字符串
     * @return
     */
    public static String getNonceStr(){
        String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder sb = new StringBuilder();
        Random rd = new Random();
        for(int i = 0 ; i < 32 ; i ++ ){
            sb.append(str.charAt(rd.nextInt(str.length())));
        }
        return sb.toString();
    }


    /**
     * 插入XML标签
     * @param sb
     * @param Key
     * @param value
     * @return
     */
    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
     * @param xml
     * @return
     */
    public static String getPrePayId(String xml){
        int start = xml.indexOf("");
        int end = xml.indexOf("");

        if(start < 0 && end < 0){
            return null;
        }
        return xml.substring(start + "".length(),end)
                .replace(","").replace("]]>","");
    }

}
public interface WeChatConst {

    String APPID = "";//填写APPID
    String APP_SECRET = "";//填写AppSecret
    String MCH_ID = "";//Mch_id,这是值是微信支付商户号,大家可以从邮件中获取
    String KEY = "";//这个参数KEY是在商户后台配置的一个32位的key,微信商户平台-账户设置-安全设置-api安全


    /**
     * 交易类型
     */
    String TRADE_TYPE = "JSAPI";


}

我们可以调用里面的“getOpenIdByCode”方法通过code 获得openid,这个openid我们先记录下来,稍后会用到的,接下来我们要经行调用统一下单接口。

③前端页面中,让用户选择产品

JS代码

    /**
     * 充值  提交信息
     */
    function recharge(){
    var $select = $('.price_caseSelected');
        var productId = $select.attr("productId");

        var options = {
            dataType : 'json',
            data:{
                'productId':productId
            },
            success : function(data) {
                if (data.success) {
                    prepay_id = data.prepay_id;
                    paySign = data.paySign;
                    appId = data.appId;
                    timeStamp = data.timeStamp;
                    nonceStr = data.nonceStr;
                    packageStr = data.packageStr;
                    signType = data.signType;
                    callpay();
                } else {
                    alert(data.message);
                }
            }
        };
        $("#recharge_form").ajaxSubmit(options);

    }
      /**************************  微信支付  *************************/
    var prepay_id ;
    var paySign ;
    var appId ;
    var timeStamp ;
    var nonceStr ;
    var packageStr ;
    var signType ;

    function onBridgeReady(){
        WeixinJSBridge.invoke(
                'getBrandWCPayRequest', {
                    "appId"     :   appId,     //公众号名称,由商户传入
                    "timeStamp" :   timeStamp,         //时间戳,自1970年以来的秒数
                    "nonceStr"  :   nonceStr , //随机串
                    "package"   : packageStr,
                    "signType"  : signType,         //微信签名方式:
                    "paySign"   : paySign //微信签名
                },
                function(res){

                    if(res.err_msg == "get_brand_wcpay_request:ok" ) {
    //这里填写支付成功的事件
}     // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回    ok,但并不保证它绝对可靠。
                }
        );
    }

    function callpay()
    {
        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();
        }
    }

    /******************* 微信支付 结束**************************************/
④后台调佣统一下单的API,获得prepay_id
**这步我们先要设置支付授权目录**
微信公众平台→微信支付→开发配置→测试授权目录和测试白名单。
直接配置支付授权目录也行。

后台获得到需要用户支付的金额,以及一些列的产品信息后,系统经行预下单,也是就调用调用统一下单的API,生成一个需要支付的订单,获得prepay_id,该值用于在前端页面,通过JSAPI中调用微信支付需要用到,代码看下面的⑤中的controller的方法,以及WeChatUtil。

统一下单大家可以调用我刚刚提供给大家的工具类中的WeChatUtil.unifiedorder这个方法,参数在方法上面的注释有写清楚。

这里如果你们想自行封装需要注意一个编码问题,这个会导致你回调产生签名错误。



⑤获得prepay_id后,我们需要在后台组装,调用微信JSAPI的接口数据。
   /**
     * 进行预下单
     * @param request
     * @return
     */
    @RequestMapping(value = Mobile.URL.TEL.DO_RECHARGE)
    @ResponseBody
    public ReturnBean doRecharge(HttpServletRequest request,String productId){
        String openid = getMsg(request, CacheConst.SESSION,CacheConst.OPENID);//获得openid,从缓存中取出
        String openid = getMsg(request, CacheConst.SESSION,CacheConst.OPENID);//获得openid,从缓存中取出
        String IP = HttpUtil.getIpAddr(request);//获得IP
        /*************************************/
        Map map = XXXXXXXXXX;//业务处理,根据自己的业务经行处理
    //在业务层调用WeChatUtil.unifiedorder方法获得prepay_id
      /*************************************/
        String prepay_id = (String) map.get("prepay_id");
        String timeStamp = String.valueOf((System.currentTimeMillis() / 1000));//1970年到现在的秒数
        String nonceStr = WeChatUtil.getNonceStr().toUpperCase();//数据字符串
        String packageStr = "prepay_id="+prepay_id;//prepay_id
        String signType = "MD5";
        String paySign =
                "appId=" +WeChatConst.APPID+
                "&nonceStr=" +nonceStr+
                "&package=prepay_id=" + prepay_id +
                "&signType=" + signType +
                "&timeStamp=" + timeStamp+
                "&key="+ WeChatConst.KEY;//注意这里的参数要根据ASCII码 排序
        paySign = MD5Util.MD5(paySign).toUpperCase();//将数据MD5加密

    /**
     * 这里是将一系列的信息传到前台
     */
        ReturnBean rb = new ReturnBean(true,"下单成功");
        rb.setAppId(WeChatConst.APPID);
        rb.setTimeStamp(timeStamp);
        rb.setNonceStr(nonceStr);
        rb.setPackageStr(packageStr);
        rb.setSignType(signType);
        rb.setPaySign(paySign);
        rb.setPrepay_id(prepay_id);


        return rb;
    }
⑥前端获得到需要的值后,调用callpay()方法,经行调用微信支付JSAPI,然后用户输入支付密码,完成微信支付。后台系统等待微信支付的回调,也是在统一下单时候填写的url。

⑦回调controller
/**
     * 微信回调
     * @param request
     * @param response
     */
    @RequestMapping(value = Mobile.URL.CALLBACK.WE_CHAT_BACK)
    public void weChatBack(HttpServletRequest request,HttpServletResponse response){

        Document document = null;
        try(
                InputStream is = request.getInputStream()//获取流
        ) {

            SAXReader reader = new SAXReader();
            document = reader.read(is);//将流放到document中去
        } catch (IOException | DocumentException e) {
             wcbackLogger.error("",e);
        }

        Element root = document.getRootElement();
        WeChatBean weChatBean = new WeChatBean();

     /**
         * 分析xml文件的内容
         */
        weChatBean.setAppid(root.elementText("appid"));
        weChatBean.setBank_type(root.elementText("bank_type"));
        weChatBean.setCash_fee(root.elementText("cash_fee"));
        weChatBean.setFee_type(root.elementText("fee_type"));
        weChatBean.setIs_subscribe(root.elementText("is_subscribe"));
        weChatBean.setMch_id(root.elementText("mch_id"));
        weChatBean.setNonce_str(root.elementText("nonce_str"));
        weChatBean.setOpenid(root.elementText("openid"));
        weChatBean.setOut_trade_no(root.elementText("out_trade_no"));
        weChatBean.setResult_code(root.elementText("result_code"));
        weChatBean.setReturn_code(root.elementText("return_code"));
        weChatBean.setSign(root.elementText("sign"));
        weChatBean.setTime_end(root.elementText("time_end"));
        weChatBean.setTotal_fee(root.elementText("total_fee"));
        weChatBean.setTrade_type(root.elementText("trade_type"));
        weChatBean.setTransaction_id(root.elementText("transaction_id"));


        /**
         * 判断签名是否正确,防止别人恶意调用
         */
        if(!weChatBean.calculateSign().equals(weChatBean.getSign())
                || !"SUCCESS".equals(weChatBean.getResult_code())
                || !"SUCCESS".equals(weChatBean.getReturn_code())  ){

            try (
                    Writer writer = response.getWriter()
            ){
                writer.write("fail");
                writer.flush();

            } catch (IOException e) {
                e.printStackTrace();
            }

        }

        /**
         * 调用业务
         */
        Object result = XXXXXXXXXX;//这里是业务调用,大家自行修改

        String message = "SUCCESS";
        if(result instanceof String
                || (result instanceof Boolean && !(Boolean) result)){
            message = "FAIL";
        }




        /**
         * 输出信息,如果信息是SUCCESS,微信就不会继续调用了,不然微信会连续调用8次,确保我们收到信息
         */
        try (
                Writer writer = response.getWriter()
        ){
            writer.write(message);
            writer.flush();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
/**每个字段具体含义大家可以去看文档,这里我就不一一列举了*/
public class WeChatBean {
    private String appid;
    private String bank_type;
    private String cash_fee;
    private String fee_type;
    private String is_subscribe;
    private String mch_id;
    private String nonce_str;
    private String openid;
    private String out_trade_no;
    private String result_code;
    private String return_code;
    private String sign;
    private String time_end;
    private String total_fee;
    private String trade_type;
    private String transaction_id;
/*** get set 方法 ***/
    /**
     * 计算出sign的值
     * @return
     */
    public String calculateSign(){
        String str = "appid=" + appid +
                "&bank_type=" + bank_type +
                "&cash_fee=" + cash_fee +
                "&fee_type=" + fee_type+
                "&is_subscribe=" + is_subscribe +
                "&mch_id=" + mch_id +
                "&nonce_str=" + nonce_str +
                "&openid=" + openid +
                "&out_trade_no=" + out_trade_no +
                "&result_code=" + result_code +
                "&return_code=" + return_code +
                "&time_end=" + time_end +
                "&total_fee=" + total_fee +
                "&trade_type=" + trade_type +
                "&transaction_id=" + transaction_id +
                "&key=" + WeChatConst.KEY;

        return MD5Util.MD5(str).toUpperCase();
    }
}

到这里整个微信支付就完成了。
第一次发表博客,有什么不足还请大家多多指教。

你可能感兴趣的:(支付)