已上傳至github庫 https://github.com/gaoruiqiang2017/weixinpay.git
1、用戶在商戶側完成下單,使用微信支付進行支付
2、由商戶後臺向微信支付發起下單請求(調用統一下單接口)注:交易類型trade_type=MWEB
3、統一下單接口返回支付相關參數給商戶後臺,如支付跳轉url(參數名“mweb_url”),商戶通過mweb_url調起微信支付中間頁
4、中間頁進行H5權限的校驗,安全性檢查(此處常見錯誤請見下文)
5、如支付成功,商戶後臺會接收到微信側的異步通知
6、用戶在微信支付收銀臺完成支付或取消支付,返回商戶頁面(默認爲返回支付發起頁面)
7、商戶在展示頁面,引導用戶主動發起支付結果的查詢
8,9、商戶後臺判斷是否接到收微信側的支付結果通知,如沒有,後臺調用我們的訂單查詢接口確認訂單狀態
10、展示最終的訂單支付結果給用戶
pom
<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.weixinpay</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--微信支付SDK-->
<dependency>
<groupId>com.github.wxpay</groupId>
<artifactId>wxpay-sdk</artifactId>
<version>0.0.3</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<finalName>demo</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/*.properties</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
<includes>
<include>**</include>
</includes>
</resource>
</resources>
</build>
</project>
util
/**
* @Description
* @Date:03
*/
public class HttpUtil {
public static String doPost(String url, String requestXml) {
CloseableHttpClient httpClient = null;
CloseableHttpResponse httpResponse = null;
//創建httpClient連接對象
httpClient = HttpClients.createDefault();
//創建post請求連接對象
HttpPost httpPost = new HttpPost(url);
//創建連接請求對象,並設置連接參數
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(15000) //連接服務區主機超時時間
.setConnectionRequestTimeout(60000) //連接請求超時時間
.setSocketTimeout(60000).build(); //設置讀取響應數據超時時間
//爲httppost請求設置參數
httpPost.setConfig(requestConfig);
//將上傳參數放到entity屬性中
httpPost.setEntity(new StringEntity(requestXml, "UTF-8"));
//添加頭信息
httpPost.addHeader("Content-type", "text/xml");
String result = "";
try {
//發送請求
httpResponse = httpClient.execute(httpPost);
//從相應對象中獲取返回內容
HttpEntity entity = httpResponse.getEntity();
result = EntityUtils.toString(entity, "UTF-8");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 獲取IP地址
*
* @param request
* @return
*/
public static String getIpAddress(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.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
}
properties
appid=wx12822223?22sss
mchId=149232323312333sss
weixinKey=34234234er2werwerwer
unifiedorderUrl=https://api.mch.weixin.qq.com/pay/unifiedorder
接口
/**
* @Description
* @Date:03
*/
@RestController
@RequestMapping("/weixinH5pay")
public class WeixinH5pay {
@Value("${appid}")
private String appid; //公衆賬號id
@Value("${mchid}")
private String mchId; //商戶號
@Value("${weixinKey}")
private String weixinKey; //密匙
@Value("${unifiedorderUrl}")
private String unifiedorderUrl; //統一下單接口
/**
* @param httpServletRequest
* @param httpServletResponse
* @param orderNo 訂單號(統一下單接口前自己要又訂單)
* @param money 金額
* @param body 商品內容
*/
@RequestMapping("/h5pay")
public void h5pay(HttpServletRequest httpServletRequest, HttpServletResponse
httpServletResponse, String orderNo, String money, String body, Writer writer)
throws Exception {
String mapStr = "";
try {
HashMap<String, String> dataMap = new HashMap<>();
dataMap.put("appid", appid); //公衆賬號ID
dataMap.put("mch_id", mchId); //商戶號
dataMap.put("nonce_str", WXPayUtil.generateNonceStr()); //隨機字符串,長度要求在32位以內。
dataMap.put("body", body); //商品描述
dataMap.put("out_trade_no", orderNo); //商品訂單號
dataMap.put("total_fee", money); //商品金
dataMap.put("spbill_create_ip", HttpUtil.getIpAddress(httpServletRequest)); //客戶端ip
dataMap.put("notify_url", "www.baidu.com"); //通知地址(假設是百度)
dataMap.put("trade_type", "MWEB"); //交易類型
// dataMap.put("scene_info", "{\"h5_info\": {\"type\":\"Wap\",\"wap_url\":
// \"http://www" +
// ".baidu.com\",\"wap_name\": \"學易資源分享平臺\"}}"); //場景信息(其實不寫能用)
//生成簽名
String signature = WXPayUtil.generateSignature(dataMap, weixinKey);
dataMap.put("sign", signature);//簽名
//將類型爲map的參數轉換爲xml
String requestXml = WXPayUtil.mapToXml(dataMap);
//發送參數,調用微信統一下單接口,返回xml
String responseXml = HttpUtil.doPost(unifiedorderUrl, requestXml);
System.out.print(responseXml);
Map<String, String> map = WXPayUtil.xmlToMap(responseXml);
if ("FAIL".equals(map.get("return_code"))) {
mapStr = map.get("return_msg");
writer.write(mapStr);
return;
}
if ("FAIL".equals(map.get("result_code"))) {
mapStr = map.get("err_code_des");
writer.write(mapStr);
return;
}
if (map.get("mweb_url") == null || "".equals(map.get("mweb_url"))) {
mapStr = "mweb_url爲null";
writer.write(mapStr);
return;
}
//成功返回了mweb_url,拼接支付成功後微信跳轉自定義頁面
//確認支付過後跳的地址redirectUrl,需要經過urlencode處理(可以不寫,會跳轉默認原吊起微信的頁面
// 寫了之後前端接收訂單id後再傳給後端,處理訂單狀態)
//String redirectUrl = "http://www.xxxx.com/xxxxx/my_waRecord.html?orderNo=" + orderNo;
//redirectUrl = URLEncoder.encode(redirectUrl, "utf-8");
String url = map.get("mweb_url");//+ "&redirect_url=" + redirectUrl;
//自動跳轉微信
StringBuilder urlHtml = new StringBuilder();
urlHtml.append("<form id=\"weixinPay\" name=\"weixinPay\" action=\"" + url + "\" " +
"method=\"" + "post" + "\">");
urlHtml.append("<input type=\"submit\" value=\"" + "payButton" + "\" " +
"style=\"display:none;\"></form>");
urlHtml.append("<script>document.forms['weixinPay'].submit();</script>");
httpServletResponse.setContentType("text/html;charset=utf-8");
httpServletResponse.getWriter().write(urlHtml.toString());
httpServletResponse.getWriter().flush();
} catch (Exception e) {
mapStr = "異常";
}
writer.write(mapStr);
}
/**
* 異步回調(必須有,得發佈到外網)
*
* @param unifiedorderUrl
* @param requestXml
* @return
*/
@RequestMapping("/notifyUrl")
public String notifyUrl(String unifiedorderUrl, String requestXml) {
System.out.print("進入支付h5回調=====================");
//如果沒有加redirectUrl,就這這個接口處理訂單信息
//判斷接受到的result_code是不是SUCCESS,如果是,則返回成功,具體業務具體分析
return "success";
}
}
常見問題
一、回調頁面
正常流程用戶支付完成後會返回至發起支付的頁面,如需返回至指定頁面,則可以在MWEB_URL後拼接上redirect_url參數,來指定回調頁面。
如,您希望用戶支付完成後跳轉至https://www.wechatpay.com.cn,則可以做如下處理:
假設您通過統一下單接口獲到的MWEB_URL= https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096
注意:
1.需對redirect_url進行urlencode處理
2.由於設置redirect_url後,回跳指定頁面的操作可能發生在:1,微信支付中間頁調起微信收銀臺後超過5秒 2,用戶點擊“取消支付“或支付完成後點“完成”按鈕。因此無法保證頁面回跳時,支付流程已結束,所以商戶設置的redirect_url地址不能自動執行查單操作,應讓用戶去點擊按鈕觸發查單操作。