微信支付(公众号支付)仅三个文件让你分分钟搞定

微信支付,说起来容易,做起来真不容易,网上资料很多,但是随着微信支付的更新,有用的不多,bug也很多,调试了好久,终于弄明白如何让微信支付调通。这里涉及前后端代码,从而更方便直接用。

首先微信支付,需要弄明白一些流程,从而快速了解。
1、由于是h5页面支付,需要跳转一个地址,获取openid,可以是单独一个index.html,里面只有一个跳转地址,或者也可以直接在微信公众号的子菜单中直接增加这个地址:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=填写appid&redirect_uri=填写&response_type=code&scope=snsapi_base#wechat_redirect

这样跳转的时候就直接可以获取到openid,其中appid需要填写,redirect_uri需要填写,这个地址就是这个跳转地址成功后会获得一个code,然后传给这个地址,我是写到了后台的controller中,从而可以根据这个code,解析出openid。当然也可以单独一个页面,然后通过这个页面进入controller中,然后再跳转到支付页面。
2、由于是后台解析获得openid,所以从后台需要跳转到业务支付页面pay.html,在那里完成支付逻辑,代码后面统一上。
3、支付完成后调出支付窗口,完成支付,并跳到到支付通知地址。

核心流程是上面,但是关键的配置很多,一个不能少,准备工作如下:

1、微信公众号配置:公众号设置-》功能设置-》JS接口安全域名(请求controller的地址前缀),网页授权域名(可以是一级域名,在这个域名下请求的页面可以获得授权)
2、微信公众号配置:微信公众号-》基本设置-》APPID,APPSecert(用于获取openid的参数使用)
3、微信支付商户平台:产品中心-》开发配置-》JSAPI支付目录配置(用于支付页面在这个位置调起,本文是pay.html放在这个目录)
4、微信支付商户平台:账户中心-》API安全-》API证书(*.p12格式,用于代码配置证书用),API密钥(产生签名需要使用这个,会进行两次签名)

开始代码

1、下载最新版的微信官方java版本demo,地址为:

https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

由于WXPayConfig是抽象类,需要继承,同目录下新建一个类MyConfig,代码如下:

package com.wxpay.demo.sdk;
import com.wxpay.demo.sdk.WXPayConfig;
import java.io.*;

public class MyConfig extends WXPayConfig{
    
    private byte[] certData;

    public MyConfig() throws Exception {
        String certPath = "C:\\cert\\apiclient_cert.p12";//填写具体路径
        File file = new File(certPath);
        InputStream certStream = new FileInputStream(file);
        this.certData = new byte[(int) file.length()];
        certStream.read(this.certData);
        certStream.close();
    }

    public String getAppID() {
        return "wx***";//填写具体的公众号中的APPID
    }

    public String getMchID() {
        return "160***";//微信支付商户平台中的商户号
    }

    public String getKey() {
        return "d2ab1****";//微信公众号中的APPSecert
    }
    
    public String getAPIKey() {
        return "Ling****";//微信支付商户平台中的API密钥
    }

    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }

    @Override
    IWXPayDomain getWXPayDomain() {
        // TODO Auto-generated method stub
        IWXPayDomain iwxPayDomain = new IWXPayDomain() {
            
            public void report(String domain, long elapsedTimeMillis, Exception ex) {
        
            }
    
            public DomainInfo getDomain(WXPayConfig config) {
                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true);
            }
        };
        return iwxPayDomain;
    }
}

开始核心的后台java逻辑,涉及三个,一个是/wx/openid是开篇说的,重要:通过一个链接跳转后的回调地址,从而后台获得openid,然后跳转到自己的支付页面,第二是/wx/pay,就是支付页面需要ajax调用这个接口,从而在这个接口里面完成微信预支付的接口,获得prepay_id,然后返回给前端,前端再调起支付接口,成功后,进入第三步,/wx/notify,这个接口是成功后会调用这个接口,进行告诉微信成功,并调用自己的业务逻辑。

package com.wxpay.demo.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

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

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.wxpay.demo.sdk.MyConfig;
import com.wxpay.demo.sdk.WXPayConstants.SignType;
import com.wxpay.demo.sdk.WXPayUtil;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;

import com.alibaba.fastjson.JSON;


@Controller
@RequestMapping("/wx")
public class InterfaceController {
    
    private String m_openid;
    private String m_prepayid;
    private String m_nonce_str;
    
    //http中请求接口
    public String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            //out = new PrintWriter(conn.getOutputStream());
            // 获取URLConnection对象对应的输出流(进行编码)
            out = new PrintWriter(new OutputStreamWriter(conn.getOutputStream(),"UTF-8"));
 
 
            // 发送请求参数
            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    conn.getInputStream(), "utf-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
 
        } 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;
    }

    
    @RequestMapping("/openid")
    public void wet(HttpServletRequest request,HttpServletResponse response) throws Exception{
        
        MyConfig myconfig = new MyConfig();
        
        String json1 = "";
        String code = request.getParameter("code");
        String httpurl = "https://api.weixin.qq.com/sns/oauth2/access_token?"+"appid="+myconfig.getAppID()+"&secret="+myconfig.getKey()+"&code="+code+"&grant_type=authorization_code";//
        

        //网页授权获取用户信息时用于获取access_token以及openid的请求路径 https://api.weixin.qq.com/sns/oauth2/access_token?
         try {
             json1 = HttpUtil.get(httpurl);
             JSONObject jsonToken = new JSONObject(json1);
             m_openid = jsonToken.getStr("openid");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
         
         response.sendRedirect("https://**/pay.html");//需要填写自己的支付页面路径
    }
    
    @RequestMapping("/pay")
    public void pay(HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        
        MyConfig myconfig = new MyConfig();
        
        request.setCharacterEncoding("UTF-8");
        
        //网页授权后获取传递的参数
        String money = request.getParameter("money");//分为单位
        String ip = request.getParameter("ip");
        int intMoney = Integer.parseInt(money); 
        
        //用于获取随机数
        String strReq = WXPayUtil.generateNonceStr();//10位序列号,可以自行调整
        
        
        String orderNo=myconfig.getAppID()+Long.toString(WXPayUtil.getCurrentTimestamp());//随机生成了一个订单号

        Map<String, String> data = new HashMap<String, String>();
        data.put("appid", myconfig.getAppID());
        data.put("body", "商品名称");//填写自己的商品名称
        data.put("mch_id", myconfig.getMchID());
        data.put("nonce_str",strReq);
        data.put("notify_url", "需要填写回调地址");//如:https://**/wx/notify
        data.put("out_trade_no", orderNo);
        data.put("openid",m_openid);
        data.put("spbill_create_ip", ip);
        data.put("total_fee", money);
        data.put("trade_type", "JSAPI");  // 
   

        try {
            String sign = WXPayUtil.generateSignature(data, myconfig.getAPIKey(),SignType.MD5);
             
            data.put("sign", sign);
            String xml = WXPayUtil.mapToXml(data);
            String wxUnifiedorderUrl = "https://api.mch.weixin.qq.com/pay/unifiedorder";
            String xmlStr = sendPost(wxUnifiedorderUrl, xml);
            
            Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
            if (xmlStr.indexOf("SUCCESS") != -1) {
                m_prepayid = (String) map.get("prepay_id");
                m_nonce_str = (String) map.get("nonce_str");
            }

            System.out.println("---rush wxpay/pay,m_prepayid="+m_prepayid+",nonce_str="+m_nonce_str);
            
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


        Map<String, String> json = new HashMap<String, String>();
        json.put("appId", myconfig.getAppID());
        json.put("timeStamp", WXPayUtil.getCurrentTimestamp() + "");
        json.put("nonceStr", m_nonce_str);//需要用第一签名返回的值
        //json.put("nonceStr", WXPayUtil.generateNonceStr());
        json.put("package", "prepay_id=" + m_prepayid);     
        json.put("signType", "MD5");
        try {
            String sign = WXPayUtil.generateSignature(json, myconfig.getAPIKey(),SignType.MD5);
            json.put("paySign", sign);
        }catch(Exception e){
            e.printStackTrace();
        }
        
        String s = JSON.toJSONString(json);
        response.setCharacterEncoding("utf-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setContentType("application/json; charset=utf-8");//返回的格式必须设置为application/json
        response.getWriter().write(s);//写入到返回结果中
        System.out.println("---rush wxpay/pay,json="+json);

        }
        
    @RequestMapping("/notify")
    public void notify(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
         System.out.println("--rush wxpay/notify--------------------"); 

        response.getWriter()
        .write("<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>");
       // response.sendRedirect("https://***/payover.html");//可以进入自己的后续业务处理

    }
    
    
}

其中涉及的pay.html核心代码如下:

<!DOCTYPE html>
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=gbk" />
    <title>支付</title>   
    <script src="./jquery-3.5.1.min.js"></script>
    <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
    <script src="https://pv.sohu.com/cityjson?ie=utf-8"></script> 
</head>

<body>

<input type="button" onclick="fPlaceOrder()" value="确认购买"></input></div>

</body>

<script type="text/javascript">

    function fPlaceOrder() {

           $.ajax({
                        type: 'post',
                        url: '/wx/pay', //就是后台的controller中的pay地址
                        data: {
                            "money": "1", //支付的金额
                            "ip": returnCitySN["cip"]
                        },
                        dataType: 'json',
                        success: function(json) {
                            WeixinJSBridge.invoke('getBrandWCPayRequest', {
                                    "appId": json.appId,
                                    "timeStamp": json.timeStamp,
                                    "nonceStr": json.nonceStr,
                                    "package": json.package,
                                    "signType": "MD5",
                                    "paySign": json.paySign
                                },
                                //调起微信支付成功
                                function(res) {
                                    if(res.err_msg == "get_brand_wcpay_request:ok") {
                                        alert("支付成功!");

                                    } else if(res.err_msg == "get_brand_wcpay_request:cancel") {
                                        alert("用户取消支付!");
                                    } else {
                                        alert("用户支付失败!");
                                    }
                                });
                                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();
                                }

                        },
                        //调起微信支付失败
                        error: function(json) {
                            alert("调用微信支付失败,请刷新或稍后再试!");
                        }

                    })
        }
</script>
</html>
    

如果不出意外,这三个文件弄好,就可以调起微信支付了,恭喜成功哦。。。

当然,由于微信支付需要https,java代码工程别忘记引入域名的证书配置,tomcat下也需要配置ssl。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352