本文代码并非原创,是根据网上一篇博客修改而成,留作备忘。
微信支付有2种模式,第一种模式略微复杂,本文采用第二种模式;
微信扫码模式二
业务流程说明:
(1)商户后台系统根据用户选购的商品生成订单。
(2)用户确认支付后调用微信支付【统一下单API】生成预支付交易;
(3)微信支付系统收到请求后生成预支付交易单,并返回交易会话的二维码链接code_url。
(4)商户后台系统根据返回的code_url生成二维码。
(5)用户打开微信“扫一扫”扫描二维码,微信客户端将扫码内容发送到微信支付系统。
(6)微信支付系统收到客户端请求,验证链接有效性后发起用户支付,要求用户授权。
(7)用户在微信客户端输入密码,确认支付后,微信客户端提交授权。
(8)微信支付系统根据用户授权完成支付交易。
(9)微信支付系统完成支付交易后给微信客户端返回交易结果,并将交易结果通过短信、微信消息提示用户。微信客户端展示支付交易结果页面。
(10)微信支付系统通过发送异步消息通知商户后台系统支付结果。商户后台系统需回复接收情况,通知微信后台系统不再发送该单的支付通知。
(11)未收到支付通知的情况,商户后台系统调用【查询订单API】。
(12)商户确认订单已支付后给用户发货
参阅:
微信官方说明
- https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
- http://www.demodashi.com/demo/10268.html
CSDN实例
- http://blog.csdn.net/wangqiuyun/article/details/51241064
- http://blog.csdn.net/bjlf_1989/article/details/51829557
根据订单生成微信支付二维码
本部分包含了微信支付模式二的1、2、3、4步骤,APPID、商户号、key等参数需要到微信公众号中查找
WXPayController代码
@Controller
@RequestMapping(value = "${adminPath}/wxpay")
public class WXPayController extends BaseController {
@Autowired
private OrderService orderService;
@Autowired
private ProductService productService;
@Autowired
private WXPayService wxPayService;
/**
* 生成微信支付二维码图片
*
* @param orderId 订单ID
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/wxqrcode.jpg")
public void wxqrcode(String orderId, HttpServletRequest request, HttpServletResponse response, HttpSession session) throws Exception{
// 根据传入的订单号获取到订单的金额、订单内容等参数,此处自行编码实现
Order order = orderService.findById(orderId);
if (null == order){
throw new NullPointerException("订单不存在!");
}
if (!Order.STATUS_CREATE.equals(order.getStatus())){
throw new NullPointerException("订单已结算或状态错误!");
}
List<OrderDetail> orderDetails = order.getOrderDetails();
if (null == orderDetails || orderDetails.size() < 1){
throw new NullPointerException("订单错误,订单中没有商品!");
}
// 微信支付URL需要传入的金额单位是分,此处将订单金额转换成'分'单位
BigDecimal fen = order.getTotalAmount().multiply(new BigDecimal(100));
fen = fen.setScale(0, BigDecimal.ROUND_HALF_UP);
String order_price = fen.toString();
// 微信支付显示标题
String body = "微信支付测试demo";
/*if (order.getTotalNumber() > 1){
StringBuilder sb = new StringBuilder();
sb = sb.append(orderDetails.get(0).getProduct().getName())
.append(" 等")
.append(order.getTotalNumber())
.append("件商品");
body = sb.toString();
} else {
body = orderDetails.get(0).getProduct().getName();
}*/
// 微信支交易订单号,不能重复
String out_trade_no = "" + System.currentTimeMillis();
// 组装参数
Map<String, Object> param = new HashMap<>();
param.put("order_price", order_price);
param.put("body", body);
param.put("out_trade_no", out_trade_no);
param.put("attach", orderId);
// 生成微信支付二维码链接
Map<String, String> result = wxPayService.doUnifiedOrder(param, request);
if ("FAIL".equals(result.get("return_code"))){
logger.error("生成二维码错误: " + result.get("return_msg"));
session.setAttribute("create_wx_qrcode_error_msg", result.get("return_msg"));
} else {
String urlCode = result.get("code_url");
// 生成微信二维码,输出到response流中
String icon = WXPayController.class.getClassLoader().getResource("coffee_icon.png").getPath();
BufferedImage bufferedImage = MatrixToImageWriterWithLogo.genBarcode(urlCode, 512, 512, icon); // 二维码的内容,宽,高,二维码中心的图片地址
ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
}
}
}
WXPayService代码
@Service
@Transactional(readOnly = true)
public class WXPayService {
/ **
* 调用微信支付接口返回URL
* @param param 订单价格,订单显示内容,订单号
* @param request
* @return
* @throws Exception
*/
public Map<String, String> doUnifiedOrder(Map<String, Object> param, HttpServletRequest request) throws Exception {
String appid = PayConfigUtil.APP_ID; // appid
String mch_id = PayConfigUtil.MCH_ID; // 商户号
String key = PayConfigUtil.API_KEY; // key
String trade_type = "NATIVE";
String spbill_create_ip = PayCommonUtil.getIpAddress(request); // 获取发起电脑 ip
String notify_url = PayConfigUtil.NOTIFY_URL; // 回调接口
String currTime = PayCommonUtil.getCurrTime();
String strTime = currTime.substring(8, currTime.length());
String strRandom = PayCommonUtil.buildRandom(4) + "";
String nonce_str = strTime + strRandom; // 随机字符串
String order_price = (String) param.get("order_price"); // 价格 注意:价格的单位是分
String body = (String) param.get("body"); // 商品名称
String out_trade_no = (String) param.get("out_trade_no"); // 订单号
String attach = (String) param.get("attach"); // 附加参数,这里传的是我们的订单号orderId
SortedMap<Object,Object> packageParams = new TreeMap<>();
packageParams.put("appid", appid);
packageParams.put("mch_id", mch_id);
packageParams.put("nonce_str", nonce_str);
packageParams.put("body", body);
packageParams.put("out_trade_no", out_trade_no);
packageParams.put("total_fee", order_price);
packageParams.put("spbill_create_ip", spbill_create_ip);
packageParams.put("notify_url", notify_url);
packageParams.put("trade_type", trade_type);
packageParams.put("attach", attach);
// 签名
String sign = PayCommonUtil.createSign("UTF-8", packageParams, key);
packageParams.put("sign", sign);
// 微信支付接口传输数据使用xml方式进行的,此处将参数装换为xml
// map --> xml
String requestXML = PayCommonUtil.getRequestXml(packageParams);
System.out.println("---------- Request XML: " + requestXML);
String resXml = HttpUtil.postData(PayConfigUtil.UFDODER_URL, requestXML);
System.out.println("---------- Response XML: " + resXml);
// xml --> map
return XMLUtil.doXMLParse(resXml);
}
}
用户扫描二维码支付完成,微信回调我们的接口, 完成相应业务
用户支付成功后,微信会通知此接口传给我们必要的参数,此处用户处理我们的逻辑如:给用户配货发货等,本部分包含了微信支付模式二的10、12步,也可以不使用本接口通过模式二第11步主动查询微信支付结果
在WXPayController中添加微信支付成功后的回调接口及相应的方法
/**
* 微信支付成功回调方法,在此方法中修改订单为已付款给用户发货等操作,将此URL配置到微信公众号的支付成功的回调接口中处理支付成功的业务逻辑
*
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "wxnotify")
public void wxnotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
System.out.println("-------------------------- wxnotify ---------------------------------");
//读取参数
StringBuffer sb = new StringBuffer();
InputStream inputStream = request.getInputStream();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
String s ;
while ((s = in.readLine()) != null){
sb.append(s);
}
in.close();
inputStream.close();
//解析xml成map
Map<String, String> m = XMLUtil4jdom.doXMLParse(sb.toString());
if (null == m){
throw new NullPointerException("微信服务器未返回任何数据!");
}
//过滤空 设置 TreeMap
SortedMap<Object,Object> packageParams = new TreeMap<>();
Iterator it = m.keySet().iterator();
while (it.hasNext()) {
String parameter = (String) it.next();
String parameterValue = m.get(parameter);
String v = "";
if(null != parameterValue) {
v = parameterValue.trim();
}
packageParams.put(parameter, v);
}
logger.debug(packageParams.toString());
// 账号信息
String key = PayConfigUtil.API_KEY; //key
//判断签名是否正确
if(PayToolUtil.isTenpaySign("UTF-8", packageParams,key)) {
//------------------------------
//处理业务开始
//------------------------------
String resXml = "";
if("SUCCESS".equals((String)packageParams.get("result_code"))){ // 支付成功
try {
//////////执行自己的业务逻辑////////////////
//此处自行编码完成相应的支付成功后的业务逻辑
wxPayService.wxnotify(packageParams);
} catch (Exception e){ // 退款
e.printStackTrace();
logger.error(e.getMessage());
}
//////////执行自己的业务逻辑////////////////
//暂时使用最简单的业务逻辑来处理:只是将业务处理结果保存到session中
//(根据自己的实际业务逻辑来调整,很多时候,我们会操作业务表,将返回成功的状态保留下来)
request.getSession().setAttribute("_PAY_RESULT", "OK");
System.out.println("-------------------------- 支付成功 ---------------------------------");
//通知微信.异步确认成功.必写.不然会一直通知后台.八次之后就认为交易失败了.
resXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>"
+ "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
} else {
resXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>"
+ "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
System.out.println("-------------------------- 支付失败 ---------------------------------");
}
//------------------------------
//处理业务完毕
//------------------------------
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
out.write(resXml.getBytes());
out.flush();
out.close();
} else{
logger.debug("通知签名验证失败");
}
}
在WXPayService中添加支付成功的业务逻辑
@Transactional(readOnly = false)
public void wxnotify(SortedMap<Object,Object> packageParams) throws Exception{
String orderId = (String) packageParams.get("attach");
String realPay = (String) packageParams.get("total_fee"); // 分为单位
BigDecimal pay = new BigDecimal(realPay).divide(new BigDecimal(100));
System.out.println("订单" + orderId + "支付成功,支付金额为:¥" + pay.floatValue() + "元");
}
如何通知前台用户已经支付
前台web页面可用通过ajax轮训,根据订单id检测订单的状态是否是已经支付
代码中用到的工具类
PayConfigUtil工具类,保存了一些微信支付的参数
public class PayConfigUtil {
// 微信支付 公众号ID
public static final String APP_ID = "wx3c3a******a41b";
// 商户号ID
public static final String MCH_ID = "132****1";
// key设置路径:微信商户平台(pay.weixin.qq.com)-->账户设置-->API安全-->密钥设置
public static final String API_KEY = "12345678***********9012";
// 支付成功回调接口,我们在WXPayController中编写的回调接口的公网地址
// # 微信支付成功回调地址:微信公众平台(mp.weixin.qq.com) --> 微信支付 --> 扫码支付 --> 支付回调URL
public static final String NOTIFY_URL = "http://1*0.2**.**.*2:80**/wxpay/wxnotify";
// 微信支付官方接口
public static final String UFDODER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
}
XMLUtil4jdom类用于将map参数与xml互转
public class XMLUtil4jdom {
/**
* 解析xml,返回第一级元素键值对。如果第一级元素有子节点,则此节点的值是子节点的xml数据。
* @param strxml
* @return
* @throws JDOMException
* @throws IOException
*/
public static Map doXMLParse(String strxml) throws JDOMException, IOException {
strxml = strxml.replaceFirst("encoding=\".*\"", "encoding=\"UTF-8\"");
if(null == strxml || "".equals(strxml)) {
return null;
}
Map<String, String> m = new HashMap<String, String>();
InputStream in = new ByteArrayInputStream(strxml.getBytes("UTF-8"));
SAXBuilder builder = new SAXBuilder();
Document doc = builder.build(in);
Element root = doc.getRootElement();
List list = root.getChildren();
Iterator it = list.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String k = e.getName();
String v = "";
List children = e.getChildren();
if(children.isEmpty()) {
v = e.getTextNormalize();
} else {
v = XMLUtil4jdom.getChildrenText(children);
}
m.put(k, v);
}
//关闭流
in.close();
return m;
}
/**
* 获取子结点的xml
* @param children
* @return String
*/
public static String getChildrenText(List children) {
StringBuffer sb = new StringBuffer();
if(!children.isEmpty()) {
Iterator it = children.iterator();
while(it.hasNext()) {
Element e = (Element) it.next();
String name = e.getName();
String value = e.getTextNormalize();
List list = e.getChildren();
sb.append("<" + name + ">");
if(!list.isEmpty()) {
sb.append(XMLUtil4jdom.getChildrenText(list));
}
sb.append(value);
sb.append("</" + name + ">");
}
}
return sb.toString();
}
}
PayCommonUtil类里包装了微信支付签名算法和一些小工具
public class PayCommonUtil {
/**
* 微信支付签名算法sign
* @param characterEncoding
* @param parameters
* @return
*/
@SuppressWarnings("unchecked")
public static String createSign(String characterEncoding, SortedMap<Object,Object> parameters, String API_KEY){
StringBuffer sb = new StringBuffer();
Set es = parameters.entrySet();//所有参与传参的参数按照accsii排序(升序)
Iterator it = es.iterator();
while(it.hasNext()) {
Map.Entry entry = (Map.Entry)it.next();
String k = (String)entry.getKey();
Object v = entry.getValue();
if(null != v && !"".equals(v)
&& !"sign".equals(k) && !"key".equals(k)) {
sb.append(k + "=" + v + "&");
}
}
sb.append("key=" + API_KEY);
String sign = PayCommonUtil.MD5Encode(sb.toString(), characterEncoding).toUpperCase();
return sign;
}
/**
* @Description:将请求参数转换为xml格式的string
* @param parameters
* 请求参数
* @return
*/
public static String getRequestXml(SortedMap<Object, Object> parameters) {
StringBuffer sb = new StringBuffer();
sb.append("<xml>");
Set es = parameters.entrySet();
Iterator it = es.iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
String k = (String) entry.getKey();
String v = (String) entry.getValue();
if ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) {
sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">");
} else {
sb.append("<" + k + ">" + v + "</" + k + ">");
}
}
sb.append("</xml>");
return sb.toString();
}
/**
* 获取当前时间 yyyyMMddHHmmss
*
* @return String
*/
public static String getCurrTime() {
Date now = new Date();
SimpleDateFormat outFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String s = outFormat.format(now);
return s;
}
/**
* 取出一个指定长度大小的随机正整数.
*
* @param length
* int 设定所取出随机数的长度。length小于11
* @return int 返回生成的随机数。
*/
public static int buildRandom(int length) {
int num = 1;
double random = Math.random();
if (random < 0.1) {
random = random + 0.1;
}
for (int i = 0; i < length; i++) {
num = num * 10;
}
return (int) ((random * num));
}
/**
* 获取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;
}
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++)
resultSb.append(byteToHexString(b[i]));
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0)
n += 256;
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
/**
* 计算字符串MD5摘要值
*
* @param origin 原始字符串
* @param charsetname 字符集
* @return
*/
public static String MD5Encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname))
resultString = byteArrayToHexString(md.digest(resultString
.getBytes()));
else
resultString = byteArrayToHexString(md.digest(resultString
.getBytes(charsetname)));
} catch (Exception exception) {
}
return resultString;
}
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "a", "b", "c", "d", "e", "f" };
}
MatrixToImageWriterWithLogo类是用来生成微信二维码的类,在Java 利用google.zxing类生成的BitMatrix二维码添加logo图标中有具体的代码,此处就不写了