微信公眾號支付開發Demo
阿新 • • 發佈:2019-01-31
微信官方文件
建議大家先看下流程圖:
大概的流程是先獲取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