1. 程式人生 > >微信公眾號支付開發Demo

微信公眾號支付開發Demo

微信官方文件
建議大家先看下流程圖:
這裡寫圖片描述

大概的流程是先獲取openid –>統一下單 –> 返回預支付交易會話標識 prepay_id等引數 –>然後返回頁面h5呼叫微信支付的頁面

程式碼如下:

package com.shine.house.controller;

import com.alibaba.fastjson.JSONObject;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil
; import com.shine.house.util.Constants; import com.shine.house.util.HttpUtil; import com.shine.house.util.IpUtil; import com.shine.house.util.MyConfig; import com.shine.house.util.OnlyId; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework
.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.ByteArrayOutputStream
; import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; @Controller public class WxPayController { /** * @Description : 前往微信獲取openId * @Author : 高冷的美男子 * @Date : Created in 8:52 2018/5/25 */ @RequestMapping(value = {"/toGetOpenID", "/"}) public void toWeChat(HttpServletRequest request, HttpServletResponse response) throws Exception { //第一步:使用者同意授權,獲取code,會重定向到backUrl String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + Constants.APP_ID + "&redirect_uri=" + URLEncoder.encode(Constants.BACKURL) + "&response_type=code" + "&scope=snsapi_base" + "&state=STATE#wechat_redirect"; response.sendRedirect(url); } /** * 微信網頁授權獲得微信詳情 * * @param code * @param state * @throws ServletException * @throws IOException */ @RequestMapping("/getOpenInfo") public String getOpenInfo(HttpServletRequest request, HttpServletResponse response, Model model) throws Exception { String code=request.getParameter("code"); if(code==null) { System.out.println("未獲取到微信授權,返回引數如下:"); Map<String, String[]> params=request.getParameterMap(); for (Entry<String, String[]> entry: params.entrySet()) { System.out.println("key:"+entry.getKey()+" Value:"+entry.getValue()); } return "wepay"; } //使用者同意 JSONObject result = getAccess_token(code); if (result.get("errcode") == null) { //成功 返回到首頁 String openid = (String) result.get("openid"); String access_token = (String) result.get("access_token"); HttpSession session = request.getSession(); session.setAttribute("openid", openid); session.setAttribute("access_token", access_token); if (openid==null){ model.addAttribute("errmsg","openid為空"); } if (access_token==null){ model.addAttribute("errmsg","access_token為空"); } System.out.println("openid:"+openid); return "wepay"; } else { //失敗 model.addAttribute("errmsg","獲取openid失敗,請重新嘗試"); } return "wepay"; } /** * @Description : 統一下單 * @Author : 高冷的美男子 * @Date : Created in 9:20 2018/5/25 */ @RequestMapping("/unifiedOrder") public String unifiedOrder(@RequestParam("total_fee") Double total_fee,HttpServletRequest request,Model model) throws Exception { HttpSession session=request.getSession(); String spbill_create_ip=IpUtil.getIp(request); //統一下單 MyConfig config = new MyConfig(); WXPay wxpay = new WXPay(config); Map<String, String> data = new HashMap<String, String>(); //商品描述 data.put("body", "test充錢"); //商戶訂單號 data.put("out_trade_no", OnlyId.getOnlyOrderNo()); int fee=(int) (total_fee*100); //標價金額 付款金額 data.put("total_fee", String.valueOf(fee)); //客戶終端IP data.put("spbill_create_ip", spbill_create_ip); //非同步接收微信支付結果通知的回撥地址,通知url必須為外網可訪問的url,不能攜帶引數。 data.put("notify_url", Constants.NOTIFY_URL); //交易型別 公眾號支付 data.put("trade_type", "JSAPI"); //使用者標識 data.put("openid",(String) session.getAttribute("openid")); Map<String, String> resp=null; try { resp = wxpay.unifiedOrder(data); for (Entry<String, String> entry: resp.entrySet()) { System.out.println("key:"+entry.getKey()+" Value:"+entry.getValue()); } } catch (Exception e) { e.printStackTrace(); } if (resp.get("return_code").equals("SUCCESS")){ if (resp.get("result_code").equals("SUCCESS")){ Map<String, String> Param = new HashMap<String, String>(); Param.put("appId", Constants.APP_ID); Param.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); Param.put("nonceStr", WXPayUtil.generateNonceStr()); Param.put("package", "prepay_id=" + resp.get("prepay_id")); Param.put("signType", Constants.SIGN_TYPE); String sign=WXPayUtil.generateSignature(Param,config.getKey(),WXPayConstants.SignType.MD5); Param.put("paySign",sign); model.addAttribute("appId", Param.get("appId")); model.addAttribute("timeStamp", Param.get("timeStamp")); model.addAttribute("nonceStr",Param.get("nonceStr")); model.addAttribute("pa", Param.get("package")); model.addAttribute("signType",Param.get("signType")); model.addAttribute("paySign",sign); return "wxpay"; }else { model.addAttribute("errmsg",resp.get("err_code")); } }else { //通訊失敗,請檢查網路 model.addAttribute("errmsg",resp.get("return_msg")); } return "wxpay"; } /** * @Description : 支付結果通知 * @Author : 高冷的美男子 * @Date : Created in 10:51 2018/5/25 */ @RequestMapping("/notify_url1") public void payNotify(HttpServletRequest request,HttpServletResponse response){ Map<String,String>map=new HashMap<String, String>(); String out_trade_no=null; String return_code =null; try { InputStream inStream = request.getInputStream(); ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len = 0; while ((len = inStream.read(buffer)) != -1) { outSteam.write(buffer, 0, len); } outSteam.close(); inStream.close(); String resultStr = new String(outSteam.toByteArray(),"utf-8"); System.out.print("支付成功的回撥:"+resultStr); Map<String, String> resultMap =WXPayUtil.xmlToMap(resultStr); String result_code = (String) resultMap.get("result_code"); String is_subscribe = (String) resultMap.get("is_subscribe"); String transaction_id = (String) resultMap.get("transaction_id"); String sign = (String) resultMap.get("sign"); String time_end = (String) resultMap.get("time_end"); String bank_type = (String) resultMap.get("bank_type"); out_trade_no = (String) resultMap.get("out_trade_no"); return_code = (String) resultMap.get("return_code"); request.setAttribute("out_trade_no", out_trade_no); //通知微信.非同步確認成功了. map.put("SUCCESS","SUCCESS"); response.getWriter().write(WXPayUtil.mapToXml(map)); } catch (Exception e) { System.out.print("微信回撥接口出現錯誤:"); try { map.put("FAIL","error"); response.getWriter().write(WXPayUtil.mapToXml(map)); } catch (Exception e1) { e1.printStackTrace(); } } if(return_code.equals("SUCCESS")){ //支付成功的業務邏輯 System.out.println(">>>>>>>>>>>>>>>>>>支付成功了"); }else{ //支付失敗的業務邏輯 } } public JSONObject getAccess_token(String code) { String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + Constants.APP_ID + "&secret=" + Constants.APP_SECRET + "&code=" + code + "&grant_type=authorization_code"; JSONObject jsonObject = HttpUtil.httpRequest(url, "GET", null); return jsonObject; } }

配置類MyConfig 繼承微信給SDK的配置類

package com.shine.house.util;

import com.github.wxpay.sdk.WXPayConfig;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

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

    public MyConfig() throws Exception {
        String certPath = "D:/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();
    }
    //APPID
    public String getAppID() {
        return "000000000000000000";
    }
    //商戶ID
    public String getMchID() {
        return "00000000000000000";
    }
    //獲取介面祕鑰
    public String getKey() {
        return "0000000000000000000000000000000";
    }
    //獲取商戶證書內容
    public InputStream getCertStream() {
        ByteArrayInputStream certBis = new ByteArrayInputStream(this.certData);
        return certBis;
    }

    public int getHttpConnectTimeoutMs() {
        return 8000;
    }

    public int getHttpReadTimeoutMs() {
        return 10000;
    }

}

工具類IpUtil 獲取IP

/**
 * 
 */
package com.shine.house.util;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 * @Description : IP 相關操作
 * @Author : 高冷的葉子
 * @Date : Created in 2018年3月10日 下午4:55:46
 */
public class IpUtil {

static Logger LOGGER= LoggerFactory.getLogger(IpUtil.class);


    /**
     * @Description : 獲取IP
     * @Author : 高冷的葉子
     * @Date : Created in 2018年3月10日 下午5:00:20
     * @param request
     * @return
     */
    public static String getIp(HttpServletRequest request) {
        String ipAddress = request.getHeader("x-forwarded-for");
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getHeader("Proxy-Client-IP");
        }
        if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getHeader("WL-Proxy-Client-IP");
        }
        if (StringUtils.isBlank(ipAddress) || "unknown".equalsIgnoreCase(ipAddress)) {
        ipAddress = request.getRemoteAddr();
        if (ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")) {
        //根據網絡卡取本機配置的IP
        InetAddress inet = null;
        try {
        inet = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
        e.printStackTrace();
        }
        ipAddress = inet.getHostAddress();
        }
        }
        //對於通過多個代理的情況,第一個IP為客戶端真實IP,多個IP按照','分割
        if (ipAddress != null && ipAddress.length() > 15) { //"...".length() = 15
        if (ipAddress.indexOf(",") > 0) {
        ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
        }
        }
        return ipAddress;
        }





}

工具類 HttpUtil 以及 MyX509TrustManager

package com.shine.house.util;

import com.alibaba.fastjson.JSONObject;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;

public class HttpUtil {


    /**
     * 發起https請求並獲取結果
     * @param requestUrl 請求地址
     * @param requestMethod 請求方式(GET、POST)
     * @param outputStr 提交的資料
     * @return JSONObject(通過JSONObject.get(key)的方式獲取json物件的屬性值)
     */
    public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
        JSONObject jsonObject = null;
        StringBuffer buffer = new StringBuffer();
        try {
            // 建立SSLContext物件,並使用我們指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 從上述SSLContext物件中得到SSLSocketFactory物件
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            URL url = new URL(requestUrl);
            HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
            httpUrlConn.setSSLSocketFactory(ssf);
            httpUrlConn.setDoOutput(true);
            httpUrlConn.setDoInput(true);
            httpUrlConn.setUseCaches(false);
            // 設定請求方式(GET/POST)
            httpUrlConn.setRequestMethod(requestMethod);
            if ("GET".equalsIgnoreCase(requestMethod))
                httpUrlConn.connect();
            // 當有資料需要提交時
            if (null != outputStr) {
                OutputStream outputStream = httpUrlConn.getOutputStream();
                // 注意編碼格式,防止中文亂碼
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
            // 將返回的輸入流轉換成字串
            InputStream inputStream = httpUrlConn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }
            bufferedReader.close();
            inputStreamReader.close();
            // 釋放資源
            inputStream.close();
            inputStream = null;
            httpUrlConn.disconnect();
            jsonObject = JSONObject.parseObject(buffer.toString());
        } catch (ConnectException ce) {
            ce.printStackTrace();
        } catch (Exception e) {
        e.printStackTrace();
        }
        return jsonObject;
    }


}
package com.shine.house.util;


import javax.net.ssl.X509TrustManager;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class MyX509TrustManager implements X509TrustManager {
    public void checkClientTrusted(X509Certificate[] chain, String authType)
            throws CertificateException
    {
    }

    public void checkServerTrusted(X509Certificate[] chain, String authType)
            throws CertificateException
    {
    }

    public X509Certificate[] getAcceptedIssuers()
    {
        return null;
    }
}

JSP頁面 wxpay.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<script src="/js/jquery.min.js"/>
<script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js"></script>
<body>
<h2>Hello World!</h2>
</body>
<script >
$(function () {
    function onBridgeReady(){
        WeixinJSBridge.invoke(
            'getBrandWCPayRequest', {
                "appId":"${appId}",     //公眾號名稱,由商戶傳入
                "timeStamp":"${timeStamp}",         //時間戳,自1970年以來的秒數
                "nonceStr":"${nonceStr}", //隨機串
                "package":"${pa}",
                "signType":"MD5",         //微信簽名方式:
                "paySign":"${paySign}" //微信簽名
            },
            function(res){
                if(res.err_msg == "get_brand_wcpay_request:ok" ) {

                }     // 使用以上方式判斷前端返回,微信團隊鄭重提示:res.err_msg將在使用者支付成功後返回    ok,但並不保證它絕對可靠。
            }
        );
    }
    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();
    }
})


</script>
</html>

jsp頁面wepay.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<h2>Hello World!</h2>
${errmsg}
<form action="/unifiedOrder" method="post">

支付金額:<input name="total_fee" />
    <input type="submit" value="支付">
</form>
</body>
</html>

pom檔案

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.shine</groupId>
    <artifactId>phsoft</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>phsoft</name>
    <url>http://maven.apache.org</url>
    <properties>
        <!-- spring版本號 -->
        <spring.version>4.0.2.RELEASE</spring.version>
        <!-- mybatis版本號 -->
        <mybatis.version>3.2.6</mybatis.version>
        <!-- log4j日誌檔案管理包版本 -->
        <slf4j.version>1.7.7</slf4j.version>
        <log4j.version>1.2.15</log4j.version>
        <java.version>1.7</java.version>
        <java.encoding>UTF-8</java.encoding>
          <project.build.sourceEncoding>${java.encoding}</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <!-- 表示開發的時候引入,釋出的時候不會載入此包 -->
            <scope>test</scope>
        </dependency>
        <!-- spring核心包 -->
        <dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.8.7</version>
</dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-oxm</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
        </dependency>


        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <!-- mybatis核心包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>${mybatis.version}</version>
        </dependency>
        <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>
        <!-- mybatis/spring包 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.2</version>
        </dependency>
        <!-- 匯入java ee jar 包 -->
        <dependency>
            <groupId>javax</groupId>
            <artifactId>javaee-api</artifactId>
            <version>7.0</version>
        </dependency>
        <!-- 匯入Mysql資料庫連結jar包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.30</version>
        </dependency>
        <!-- 匯入dbcp的jar包,用來在applicationContext.xml中配置資料庫 -->
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.2.2</version>
        </dependency>
        <!-- JSTL標籤類 -->
        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.1.0</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.6.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.15</version>
        </dependency>
         <dependency>
            <gr