最近需要需要开发微信支付,微信官网API教程看的真的是让人一头雾水,而且对于Java开发很不友好,于是网上百度了很多博客,还是踩了不少坑,下面是我结合网上的资源加上个人在开发过程中发现的一些细节整合出来的Java微信支付开发流程。
下载Java微信官方SDK并导入自己的项目
https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1
支付授权目录配置
https://pay.weixin.qq.com/index.php/extend/pay_setting
商户平台->产品中心->开发配置
网页授权域名
配置网页授权域名,位置在公众号->开发->接口权限->网页授权->修改
后台代码
1.WXConstant 中定义了微信支付必须的几个常量,商户API密钥在商户平台->账户中心->API安全中设置
public class WXConstant {
/**
* appId
*/
public static final String APPID = "";
/**
* 商户id
*/
public static final String MCHID = "";
/**
* 商户API密钥
*/
public static final String PATERNERKEY = "";
}
2. WXPayController,与微信支付无关的代码请自己修改,如Result是我自己定义的返回类
import com.cqwu.haichijie.common.constant.CommonConstant;
import com.cqwu.haichijie.common.dto.WeChatUserDto;
import com.cqwu.haichijie.common.result.Result;
import com.cqwu.haichijie.common.result.ResultEnum;
import com.cqwu.haichijie.common.result.ResultUtil;
import com.cqwu.haichijie.common.util.CommonUtils;
import com.github.wxpay.sdk.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.*;
@RestController
@RequestMapping("/api/WXPay")
@Slf4j
public class WXPayController {
//方法名称,用于日志记录
private static final String payMethodName = "wxPay";
private static final String notifyMethodName = "notify";
/**
* xxx商品支付
* @param request
* @return
*/
@RequestMapping(value = "/order",method = RequestMethod.GET)
@ResponseBody
public Result<?> takeOutOrderPay(HttpServletRequest request){
Map<String,String> params = new HashMap<>();
String notifyUrl = "http://www.wx.com/api/WXPay/notify";//请更换为你自己的域名(需备案成功)
params.put("body","xxx商品费");//商家名称-销售商品类目
params.put("total_fee","1");//支付金额,单位分
params.put("notify_url",notifyUrl);//用户支付完成后,你想微信调你的哪个接口(完成的地址,如http://www.wx.com/order)
return weChatPay(request, params);//调用通用的微信支付方法
}
/**
* 微信支付方法
* @param request
* @param params
* @return
*/
private Result<?> weChatPay(HttpServletRequest request, Map<String, String> params) {
Date date = new Date();
try {
String openId = "";//这个地方需要你自己去获取用户oppId,通常用户登录后放在session中
// 拼接统一下单地址参数
Map<String, String> paraMap = new HashMap<String, String>();
// 获取请求ip地址
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.getRemoteAddr();
}
if (ip.contains(",")) {
String[] ips = ip.split(",");
ip = ips[0].trim();
}
paraMap.put("appid", WXConstant.APPID); //商家平台ID
paraMap.put("body", params.get("body")); //商家名称-销售商品类目、String(128)
paraMap.put("mch_id", WXConstant.MCHID); //商户ID
paraMap.put("nonce_str", WXPayUtil.generateNonceStr()); //UUID
paraMap.put("openid", openId);
paraMap.put("out_trade_no", UUID.randomUUID().toString().replaceAll("-", ""));// 订单号,每次都不同
paraMap.put("spbill_create_ip", ip);
paraMap.put("total_fee", params.get("total_fee")); //支付金额,单位分
paraMap.put("trade_type", "JSAPI"); //支付类型
paraMap.put("notify_url", params.get("notify_url"));//此路径是微信服务器调用支付结果通知路径
String sign = WXPayUtil.generateSignature(paraMap, WXConstant.PATERNERKEY);
paraMap.put("sign", sign);
String xml = WXPayUtil.mapToXml(paraMap);//将所有参数(map)转xml格式
// 统一下单 https://api.mch.weixin.qq.com/pay/unifiedorder
String unifiedorder_url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
log.info(CommonUtils.logInfo(openId,payMethodName,"xml为:" + xml));
// String xmlStr = HttpRequest.sendPost(unifiedorder_url,
// xml);//发送post请求"统一下单接口"返回预支付id:prepay_id
String xmlStr = HttpRequest.httpsRequest(unifiedorder_url, "POST", xml);
log.info(CommonUtils.logInfo(openId,payMethodName,"xmlStr为:" + xmlStr));
// 以下内容是返回前端页面的json数据
String prepay_id = "";// 预支付id
if (xmlStr.contains("SUCCESS")) {
Map<String, String> map = WXPayUtil.xmlToMap(xmlStr);
prepay_id = (String) map.get("prepay_id");
}
Map<String, String> payMap = new HashMap<String, String>();
payMap.put("appId", WXConstant.APPID);
payMap.put("timeStamp", WXPayUtil.getCurrentTimestamp() + "");
payMap.put("nonceStr", WXPayUtil.generateNonceStr());
payMap.put("signType", "MD5");
payMap.put("package", "prepay_id=" + prepay_id);
String paySign = WXPayUtil.generateSignature(payMap, WXConstant.PATERNERKEY);
payMap.put("paySign", paySign);
//将这个6个参数传给前端
return ResultUtil.success(payMap);//自定义的类,怎样返回请自定义,可返回Map<String,String>
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* @Title: callBack
* @Description: 支付完成的回调函数
* @param:
* @return:
*/
@RequestMapping(value = "/notify")
public void callBack(HttpServletRequest request, HttpServletResponse response) {
String openId = "";
InputStream is = null;
try {
//获取请求的流信息(这里是微信发的xml格式所有只能使用流来读)
is = request.getInputStream();
String xml = CommonUtils.InputStream2String(is);
Map<String, String> notifyMap = WXPayUtil.xmlToMap(xml);
//获取openid,打印日志,
openId = notifyMap.get("openid");
log.info(CommonUtils.logInfo(openId,notifyMethodName,"支付完成,微信返回给回调函数的信息为:"+xml));
//签名验证,为确保数据安全,微信支付文档中有提到
String sign = notifyMap.get("sign");
log.info("支付完成,收到的sign:"+sign);
notifyMap.remove("sign");
String signature = WXPayUtil.generateSignature(notifyMap, WXConstant.PATERNERKEY);
log.info("支付完成,重新生成的sign:" + signature);
if(signature.equals(sign)) {
String resultCode = notifyMap.get("result_code");
if (resultCode.equals("SUCCESS")) {
String ordersSn = notifyMap.get("out_trade_no");// 商户订单号
String amountpaid = notifyMap.get("total_fee");// 实际支付的订单金额:单位 分
BigDecimal amountPay = (new BigDecimal(amountpaid).divide(new BigDecimal("100"))).setScale(2);// 将分转换成元-实际支付金额:元
/*
* 以下是自己的业务处理------仅做参考 更新order对应字段/已支付金额/状态码
*/
log.info(CommonUtils.logInfo(openId, notifyMethodName, "外卖活动报名费支付成功!"));
}
}
// 告诉微信服务器收到信息了,不要在调用回调action了========这里很重要回复微信服务器信息用流发送一个xml即可
response.getWriter().write("<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>");
response.getWriter().close();
} catch (Exception e) {
log.info(CommonUtils.logInfo(openId,notifyMethodName,"微信回调方法try catch异常!" + e.getMessage()));
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
log.info(CommonUtils.logInfo(openId,notifyMethodName,"流关闭异常!"));
}
}
}
}
}
3.上个类中的InputStream2String方法
public static String InputStream2String(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = -1;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
baos.close();
is.close();
byte[] lens = baos.toByteArray();
String result = new String(lens,"UTF-8");//内容乱码处理
return result;
}
4.HttpRequest类
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
public class HttpRequest {
/**
* 向指定URL发送GET方法的请求
*
* @param url
* 发送请求的URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return URL 所代表远程资源的响应结果
*/
public static String sendGet(String url, String param) {
String result = "";
BufferedReader in = null;
try {
String urlNameString = url + "?" + param;
System.out.println(urlNameString);
URL realUrl = new URL(urlNameString);
// 打开和URL之间的连接
URLConnection connection = realUrl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立实际的连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
// 遍历所有的响应头字段
for (String key : map.keySet()) {
System.out.println(key + "--->" + map.get(key));
}
// 定义 BufferedReader输入流来读取URL的响应
in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
result += line;
}
} catch (Exception e) {
System.out.println("发送GET请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输入流
finally {
try {
if (in != null) {
in.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
return result;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param param
* 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
* @return 所代表远程资源的响应结果
*/
public static 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());
// 发送请求参数
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;
}
/**
* post请求并得到返回结果
*
* @param requestUrl
* @param requestMethod
* @param output
* @return
*/
public static String httpsRequest(String requestUrl, String requestMethod, String output) {
try {
URL url = new URL(requestUrl);
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setDoOutput(true);
connection.setDoInput(true);
connection.setUseCaches(false);
connection.setRequestMethod(requestMethod);
if (null != output) {
OutputStream outputStream = connection.getOutputStream();
outputStream.write(output.getBytes("UTF-8"));
outputStream.close();
}
// 从输入流读取返回内容
InputStream inputStream = connection.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
connection.disconnect();
return buffer.toString();
} catch (Exception ex) {
ex.printStackTrace();
}
return "";
}
}
前台代码
前台代码大多是固定的,WeixinJSBridge 是微信浏览器内置对象,给某个购买按钮,绑定pay()方法即可调用
function pay(){
$.ajax({
url:'',//自己后台的某商品的接口
type:'get',
data:{},
success:function (data) {
hideShadeMask();
var result = data.data;
appId = result.appId;
timeStamp = result.timeStamp;
nonceStr = result.nonceStr;
package = result.package;
signType = result.signType;
paySign = result.paySign;
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady',
onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady',
onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady',
onBridgeReady);
}
showInfoFrontend("请在微信上进行支付操作!");
onBridgeReady();
} else {
onBridgeReady();
}
},
error: function () {
hideShadeMask();
showInfoFrontend(TIME_OUT_ERROR);
}
});
}
function onBridgeReady(){
WeixinJSBridge.invoke( 'getBrandWCPayRequest', {
"appId":appId,
"timeStamp":timeStamp,
"nonceStr":nonceStr,
"package":package,
"signType":signType,
"paySign":paySign
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {
//支付成功后跳转的页面
}else if(res.err_msg == "get_brand_wcpay_request:cancel"){
showInfoFrontend('支付取消');
}else if(res.err_msg == "get_brand_wcpay_request:fail"){
showInfoFrontend('支付失败:'+JSON.stringify(res));
WeixinJSBridge.call('closeWindow');
}
});
}