已上传至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地址不能自动执行查单操作,应让用户去点击按钮触发查单操作。