|
@@ -0,0 +1,335 @@
|
|
|
|
+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.HttpUtil;
|
|
|
|
+import com.llisoft.common.util.JsonUtil;
|
|
|
|
+import com.llisoft.common.util.MoneyUtil;
|
|
|
|
+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("${mta.wx.appid}")
|
|
|
|
+ private String appid;
|
|
|
|
+ @Value("${mta.wx.secret}")
|
|
|
|
+ private String secret;
|
|
|
|
+ @Value("${mta.wx.mchid}")
|
|
|
|
+ private String mchid;
|
|
|
|
+ @Value("${mta.wx.mchkey}")
|
|
|
|
+ private String mchkey;
|
|
|
|
+
|
|
|
|
+ @Autowired
|
|
|
|
+ private PayService payService;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * 支付
|
|
|
|
+ * @param paynum
|
|
|
|
+ * @param money
|
|
|
|
+ * @param body
|
|
|
|
+ * @return
|
|
|
|
+ */
|
|
|
|
+ public String pay(String paynum, int money, String body) {
|
|
|
|
+ Map<String, String> paramMap = new HashMap<String ,String>();
|
|
|
|
+ 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<String, String> 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<String, String> paramMap = new HashMap<String ,String>();
|
|
|
|
+ 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<String, String> 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<String, String>();
|
|
|
|
+ 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<String, String> query(String paynum) {
|
|
|
|
+ Map<String, String> paramMap = new HashMap<String ,String>();
|
|
|
|
+ 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<String, String> 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 doResult(Map<String, String> resultMap, byte payFlag) 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 = MoneyUtil.toFen(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<String, String> paramMap) {
|
|
|
|
+ List<String> keyList = new ArrayList<String>(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<String, String> paramMap) {
|
|
|
|
+ StringBuilder xmlBuilder = new StringBuilder("<xml>");
|
|
|
|
+ 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("</").append(key).append(">");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ xmlBuilder.append("</xml>");
|
|
|
|
+ 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;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|