package com.llisoft.pay.service; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.apache.commons.codec.digest.DigestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import com.llisoft.common.util.DecimalUtil; import com.llisoft.common.util.HttpUtil; import com.llisoft.common.util.JsonUtil; import com.llisoft.common.util.XmlUtil; /** * 微信支付 */ @Service public class WxPayService { private Logger logger = LoggerFactory.getLogger(WxPayService.class); /** 微信接口 统一下单 */ public static final String WX_API_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder"; /** 微信接口 查询订单 */ public static final String WX_API_ORDERQUERY = "https://api.mch.weixin.qq.com/pay/orderquery"; @Value("${mta.domain}") private String domain; @Value("${wxpay.mchid}") private String mchid; @Value("${wxpay.mchkey}") private String mchkey; @Value("${wxpay.appid}") private String appid; @Value("${wxpay.secret}") private String secret; @Autowired private PayService payService; /** * 支付 * @param paynum * @param money * @param body * @return */ public String pay(String paynum, int money, String body) { Map paramMap = new HashMap(); paramMap.put("appid", appid); // 公众账号ID paramMap.put("mch_id", mchid); // 商户号 paramMap.put("nonce_str", getNonceStr()); // 随机字符串 paramMap.put("out_trade_no", paynum); // 商户订单号 paramMap.put("body", body); // 商品描述 paramMap.put("total_fee", String.valueOf(money)); // 支付金额, 单位为【分】 paramMap.put("spbill_create_ip", "127.0.0.1"); // 终端IP, APP和网页支付提交用户端ip paramMap.put("notify_url", domain+"/wxpay/notify"); // 通知地址 paramMap.put("trade_type", "NATIVE"); // 交易类型, 取值如下:JSAPI,NATIVE,APP paramMap.put("sign", getSign(paramMap)); // 签名 String xmlParamString = mapToXml(paramMap); // 封装xml格式参数字符串 logger.info("微信统一下单api请求参数: {}", xmlParamString); String result = HttpUtil.postXml(WX_API_ORDER, xmlParamString); logger.info("微信统一下单api返回结果: {}", result); HashMap resultMap = XmlUtil.toObject(result, HashMap.class, String.class, String.class); if ("SUCCESS".equals(resultMap.get("return_code"))) { return resultMap.get("code_url"); } logger.error("微信统一下单api调用失败: {}", resultMap); return null; } /** * 支付 移动端 * @param paynum * @param money * @param body * @return */ public JsApiPayConfig paym(String paynum, int money, String body, String openid) throws Exception { Map paramMap = new HashMap(); paramMap.put("appid", appid); // 公众账号ID paramMap.put("mch_id", mchid); // 商户号 paramMap.put("nonce_str", getNonceStr()); // 随机字符串 paramMap.put("out_trade_no", paynum); // 商户订单号 paramMap.put("body", body); // 商品描述 paramMap.put("total_fee", String.valueOf(money)); // 支付金额, 单位为【分】 paramMap.put("spbill_create_ip", "127.0.0.1"); // 终端IP, APP和网页支付提交用户端ip paramMap.put("notify_url", domain+"/wxpay/notify"); // 通知地址 paramMap.put("trade_type", "JSAPI"); // 交易类型, 取值如下:JSAPI,NATIVE,APP paramMap.put("openid", openid); // 用户标识, trade_type=JSAPI,此参数必传,用户在商户appid下的唯一标识。 paramMap.put("sign", getSign(paramMap)); // 签名 String xmlParamString = mapToXml(paramMap); // 封装xml格式参数字符串 logger.info("微信统一下单api请求参数: {}", xmlParamString); String result = HttpUtil.postXml(WX_API_ORDER, xmlParamString); logger.info("微信统一下单api返回结果: {}", result); HashMap resultMap = XmlUtil.toObject(result, HashMap.class, String.class, String.class); if ("SUCCESS".equals(resultMap.get("return_code"))) { String prepayid = resultMap.get("prepay_id"); if (prepayid!=null && !prepayid.trim().isEmpty()) { // 微信js支付参数 paramMap = new HashMap(); paramMap.put("appId", appid); paramMap.put("nonceStr", getNonceStr()); paramMap.put("timeStamp", getTimeStamp()); paramMap.put("package", "prepay_id=" + prepayid); paramMap.put("signType", "MD5"); paramMap.put("paySign", getSign(paramMap)); paramMap.put("packageStr", paramMap.get("package")); // 解决java中package关键字问题 JsApiPayConfig jsApiPayConfig = JsonUtil.toObject(JsonUtil.toJson(paramMap), JsApiPayConfig.class); // 微信jsapi初始化参数 // TODO 未完待续,需要集成微信公众号网页授权 // String result = HttpUtil.get(authurl + "/jsapi/config"); // logger.info("调用微信授权服务: 获取jsapi初始化参数: {}", result); // jsApiPayConfig.setInitConfig(JsonUtil.toObject(result, JsApiConfig.class)); // logger.info("返回微信js支付需要的参数信息: {}", JsonUtil.toJson(jsApiPayConfig)); return jsApiPayConfig; } } logger.error("微信统一下单api调用失败: {}", resultMap); return null; } /** * 查询 * @param paynum * @return */ public Map query(String paynum) { Map paramMap = new HashMap(); paramMap.put("appid", appid); // 公众账号ID paramMap.put("mch_id", mchid); // 商户号 paramMap.put("nonce_str", getNonceStr()); // 随机字符串 paramMap.put("out_trade_no", paynum); // 商户订单号 paramMap.put("sign", getSign(paramMap)); // 签名 // 封装xml格式参数字符串 String xmlParamString = mapToXml(paramMap); logger.info("微信订单查询api请求参数: {}", xmlParamString); String result = HttpUtil.postXml(WX_API_ORDERQUERY, xmlParamString); logger.info("微信订单查询api返回结果: {}", result); Map resultMap = XmlUtil.toObject(result, HashMap.class, String.class, String.class); return resultMap; } /** * 处理微信支付返回结果 * @param resultMap * @return * @author YangJie * @throws Exception * @throws Throwable * @createTime 2015年3月31日 下午5:36:27 */ public boolean dispose(Map resultMap) throws Exception { // 判断通信标识 if (!"SUCCESS".equals(resultMap.get("return_code"))) { logger.error("微信支付处理: 通信标识异常: {}", resultMap.get("return_code")); return false; } // 验证签名 if (!getSign(resultMap).equals(resultMap.get("sign"))) { logger.error("微信支付处理: 签名验证失败: {}", resultMap.get("sign")); return false; } // 判断交易状态 if (!"SUCCESS".equals(resultMap.get("result_code"))) { logger.error("微信支付处理: 交易状态异常: {}", resultMap.get("result_code")); return false; } // 完成支付状态 String paynum = resultMap.get("out_trade_no"); // paynum String tradenum = resultMap.get("trade_no"); // 支付宝交易号 int totalAmount = DecimalUtil.toInt(resultMap.get("total_amount")); // 实际支付金额 return payService.finish(paynum, tradenum, totalAmount, PayService.FLAG_NOTIFY); } /** * 获取随机字符串 * @return 32字符以内 * @author YangJie * @createTime 2015年3月26日 下午5:12:47 */ private String getNonceStr(){ return UUID.randomUUID().toString().replace("-", ""); } /** * 获取时间戳 * @return 精确到秒 * @author YangJie * @createTime 2015年3月26日 下午5:12:47 */ private String getTimeStamp(){ return String.valueOf(System.currentTimeMillis()/1000); } /** * 获取微信认证签名(md5) * @param paramMap * @author YangJie * @createTime 2015年3月26日 下午2:48:17 */ private String getSign(Map paramMap) { List keyList = new ArrayList(paramMap.keySet()); // 获取参数key Collections.sort(keyList); // key 排序 StringBuffer paramBuffer = new StringBuffer(); String value = null; for (String key : keyList) { // 循环封装非空参数 value = paramMap.get(key); if (key != null && !key.equals("sign") && value!=null && !value.isEmpty()) { paramBuffer.append(key).append("=").append(value).append("&"); } } // 添加私钥 paramBuffer.append("key=").append(mchkey); return DigestUtils.md5Hex(paramBuffer.toString()).toUpperCase(); } /** * 获取xml格式参数字符串 * @param paramMap * @return * @author YangJie * @createTime 2015年3月26日 下午3:06:26 */ private String mapToXml(Map paramMap) { StringBuilder xmlBuilder = new StringBuilder(""); for (String key : paramMap.keySet()) { if (key != null && paramMap.get(key)!=null && !paramMap.get(key).isEmpty()) { xmlBuilder.append("<").append(key).append(">").append(paramMap.get(key)) .append(""); } } xmlBuilder.append(""); return xmlBuilder.toString(); } /** * 微信js api 初始化参数 */ public class JsApiConfig{ private String appId; private String timestamp; private String nonceStr; private String signature; public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimestamp() { return timestamp; } public void setTimestamp(String timestamp) { this.timestamp = timestamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } } /** * 微信js api 支付参数 */ public class JsApiPayConfig{ private String appId; private String timeStamp; private String nonceStr; private String packageStr; private String signType; private String paySign; private JsApiConfig initConfig; public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getTimeStamp() { return timeStamp; } public void setTimeStamp(String timeStamp) { this.timeStamp = timeStamp; } public String getNonceStr() { return nonceStr; } public void setNonceStr(String nonceStr) { this.nonceStr = nonceStr; } public String getPackageStr() { return packageStr; } public void setPackageStr(String packageStr) { this.packageStr = packageStr; } public String getSignType() { return signType; } public void setSignType(String signType) { this.signType = signType; } public String getPaySign() { return paySign; } public void setPaySign(String paySign) { this.paySign = paySign; } public JsApiConfig getInitConfig() { return initConfig; } public void setInitConfig(JsApiConfig initConfig) { this.initConfig = initConfig; } } }