commit c54d4008c80a737309dd71950e13fd1d7d88bc9b Author: dimengzhe Date: Sun Jan 7 15:48:57 2024 +0800 支付相关 diff --git a/doc/Order.sql b/doc/Order.sql new file mode 100644 index 0000000..cfa69ef --- /dev/null +++ b/doc/Order.sql @@ -0,0 +1,27 @@ +DROP TABLE IF EXISTS `pay_order`; +CREATE TABLE `pay_order` +( + `id` int(11) NOT NULL AUTO_INCREMENT, + `sid` varchar(64) NOT NULL COMMENT 'sid', + `lockVersion` int(11) NOT NULL DEFAULT '0' COMMENT '记录版本,锁', + `createTime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', + `modifyTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '记录最后修改时间', + `isEnable` int(11) NOT NULL DEFAULT '1' COMMENT '记录是否可用,1:可用,0:不可用', + `state` int(11) DEFAULT '1' COMMENT '状态', + `isDelete` int(11) DEFAULT NULL COMMENT '记录是否被删除,0:未删除,1:已经删除', + `remarks` varchar(255) DEFAULT NULL COMMENT '备注信息', + `createBySid` varchar(64) DEFAULT NULL COMMENT '创建者', + `updateBySid` varchar(64) DEFAULT NULL COMMENT '更新者', + `outTradeNo` varchar(500) DEFAULT NULL COMMENT '订单编号', + `source` int(64) DEFAULT NULL COMMENT '来源:0、云菜窖', + `name` varchar(500) DEFAULT NULL COMMENT '需传入应用市场上的APP名字-实际商品名称,如天天爱消除-游戏充值', + `totalTee` varchar(500) DEFAULT NULL COMMENT '金额', + `openId` varchar(500) DEFAULT NULL COMMENT 'openId', + `payTypeValue` varchar(500) DEFAULT NULL COMMENT '支付方式Value', + `payType` varchar(500) DEFAULT NULL COMMENT '支付方式key', + `timeRemarks` int(64) DEFAULT NULL COMMENT '过期时间设置(以分钟为准,例如5分钟,则为5)', + PRIMARY KEY (`id`), + KEY `id` (`id`) +) ENGINE = InnoDB + AUTO_INCREMENT = 18 + DEFAULT CHARSET = utf8 COMMENT ='订单表'; \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..95047d9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,130 @@ + + + + + com.yxt + yxt-parent + 0.0.1 + + + 4.0.0 + + yxt-pay + com.yxt.pay + 1.0.0 + + + + com.yxt + yxt-common-base + 0.0.1 + + + + org.springframework.boot + spring-boot-starter-web + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + junit + junit + compile + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + cn.hutool + hutool-all + 5.0.4 + + + + cn.hutool + hutool-crypto + 5.0.4 + + + cn.hutool + hutool-core + 5.0.4 + + + com.google.zxing + core + 3.5.2 + + + com.alibaba + easyexcel + 3.3.2 + + + + mysql + mysql-connector-java + runtime + + + dom4j + dom4j + 1.6.1 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + 2.5.6 + + + + repackage + + + + + + + + src/main/java + + **/*Mapper.xml + + + + src/main/resources + + **/*.* + + false + + + + + diff --git a/src/main/java/com/yxt/pay/PayApplication.java b/src/main/java/com/yxt/pay/PayApplication.java new file mode 100644 index 0000000..6e03dba --- /dev/null +++ b/src/main/java/com/yxt/pay/PayApplication.java @@ -0,0 +1,23 @@ +package com.yxt.pay; + + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.cloud.openfeign.EnableFeignClients; + +/** + * @author dimengzhe + */ +@SpringBootApplication(scanBasePackages = { + "com.yxt.common.base.config", + "com.yxt.pay" +}) +@EnableDiscoveryClient +@EnableFeignClients(basePackages = {}) +public class PayApplication { + + public static void main(String[] args) { + SpringApplication.run(PayApplication.class, args); + } +} diff --git a/src/main/java/com/yxt/pay/api/order/OrderDto.java b/src/main/java/com/yxt/pay/api/order/OrderDto.java new file mode 100644 index 0000000..251ce8b --- /dev/null +++ b/src/main/java/com/yxt/pay/api/order/OrderDto.java @@ -0,0 +1,29 @@ +package com.yxt.pay.api.order; + +import com.yxt.common.core.dto.Dto; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/6 + **/ +@Data +public class OrderDto implements Dto { + + @ApiModelProperty("来源:0、云菜窖") + private int source; + @ApiModelProperty("金额") + private String totalTee; + @ApiModelProperty("微信唯一标识openid") + private String openId; + @ApiModelProperty("用户sid") + private String userSid; + @ApiModelProperty("商品名称") + private String name; + @ApiModelProperty("过期时间:以分钟为单位") + private int timeRemarks; + + +} diff --git a/src/main/java/com/yxt/pay/api/order/OrderQuery.java b/src/main/java/com/yxt/pay/api/order/OrderQuery.java new file mode 100644 index 0000000..d325c43 --- /dev/null +++ b/src/main/java/com/yxt/pay/api/order/OrderQuery.java @@ -0,0 +1,19 @@ +package com.yxt.pay.api.order; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/7 + **/ +@Data +public class OrderQuery { + + private String mainSid; + @ApiModelProperty("支付类型:微信wxpay") + private String type; + + +} diff --git a/src/main/java/com/yxt/pay/api/order/OrderVo.java b/src/main/java/com/yxt/pay/api/order/OrderVo.java new file mode 100644 index 0000000..bb7ebc4 --- /dev/null +++ b/src/main/java/com/yxt/pay/api/order/OrderVo.java @@ -0,0 +1,17 @@ +package com.yxt.pay.api.order; + +import lombok.Data; + +import java.util.Map; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/7 + **/ +@Data +public class OrderVo { + + + private Map orderInfo; +} diff --git a/src/main/java/com/yxt/pay/api/order/PayOrder.java b/src/main/java/com/yxt/pay/api/order/PayOrder.java new file mode 100644 index 0000000..c529ee1 --- /dev/null +++ b/src/main/java/com/yxt/pay/api/order/PayOrder.java @@ -0,0 +1,65 @@ +package com.yxt.pay.api.order; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.yxt.common.base.utils.StringRandom; +import com.yxt.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/6 + **/ +@Data +public class PayOrder extends BaseEntity { + @ApiModelProperty("订单编号") + private String outTradeNo; + @ApiModelProperty("来源:0云菜窖") + private int source; + @ApiModelProperty("金额") + private String totalTee; + @ApiModelProperty("支付方式:wxPay微信") + private String payType; + private String payTypeValue; + + private String name; + + private String openId; + @ApiModelProperty("过期时间描述:以分钟为单位,例如:5") + private int timeRemarks; + + /*@TableField(exist = false) + private String appId; + @TableField(exist = false) + private String mchId; + @TableField(exist = false) + private String secret; + @TableField(exist = false) + private String remark;*/ + + + public String getTime() { + SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); + return format.format(new Date()); + } + + public PayOrder(int source) { + String Randomstr = StringRandom.getRandomString(15); + if (source == 0) {//云菜窖 + outTradeNo = "YCJ" + getTime() + Randomstr; + /* appId = "wx4724e3a3c27f36b5"; + mchId = "1664882765"; + secret = "yxtcxjshbyxgs1234567898765432101";*/ + + } else if (source == 1) { + + } else if (source == 2) { + + } + this.source = source; + } +} diff --git a/src/main/java/com/yxt/pay/api/order/PayTypeEnum.java b/src/main/java/com/yxt/pay/api/order/PayTypeEnum.java new file mode 100644 index 0000000..4648c72 --- /dev/null +++ b/src/main/java/com/yxt/pay/api/order/PayTypeEnum.java @@ -0,0 +1,39 @@ +package com.yxt.pay.api.order; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/7 + **/ +public enum PayTypeEnum { + + WECHAT("wxPay","微信支付"), + ZHIFUBAO("zfbPay","支付宝支付"), + + + ; + + private String code; + + private String remarks; + + public String getCode() { + return code; + } + + + + public String getRemarks() { + return remarks; + } + + + + PayTypeEnum(String code,String remarks){ + this.code = code; + this.remarks = remarks; + } + + + +} diff --git a/src/main/java/com/yxt/pay/api/wxpay/WxPayVo.java b/src/main/java/com/yxt/pay/api/wxpay/WxPayVo.java new file mode 100644 index 0000000..ebeb8e2 --- /dev/null +++ b/src/main/java/com/yxt/pay/api/wxpay/WxPayVo.java @@ -0,0 +1,36 @@ +package com.yxt.pay.api.wxpay; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.yxt.common.base.utils.StringRandom; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/7 + **/ +@Data +public class WxPayVo { + + @ApiModelProperty("来源:0云菜窖") + private int source; + + private String appId; + private String mchId; + private String secret; + + public WxPayVo(int source) { + if (source == 0) {//云菜窖 + appId = "wx4724e3a3c27f36b5"; + mchId = "1664882765"; + secret = "yxtcxjshbyxgs1234567898765432101"; + + } else if (source == 1) { + + } else if (source == 2) { + + } + this.source = source; + } +} diff --git a/src/main/java/com/yxt/pay/biz/order/OrderMapper.java b/src/main/java/com/yxt/pay/biz/order/OrderMapper.java new file mode 100644 index 0000000..f29cf5e --- /dev/null +++ b/src/main/java/com/yxt/pay/biz/order/OrderMapper.java @@ -0,0 +1,15 @@ +package com.yxt.pay.biz.order; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.yxt.pay.api.order.PayOrder; +import org.apache.ibatis.annotations.Mapper; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/6 + **/ +@Mapper +public interface OrderMapper extends BaseMapper { + PayOrder selectBySn(String out_trade_no); +} diff --git a/src/main/java/com/yxt/pay/biz/order/OrderMapper.xml b/src/main/java/com/yxt/pay/biz/order/OrderMapper.xml new file mode 100644 index 0000000..98dcd75 --- /dev/null +++ b/src/main/java/com/yxt/pay/biz/order/OrderMapper.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/src/main/java/com/yxt/pay/biz/order/OrderRest.java b/src/main/java/com/yxt/pay/biz/order/OrderRest.java new file mode 100644 index 0000000..07f358a --- /dev/null +++ b/src/main/java/com/yxt/pay/biz/order/OrderRest.java @@ -0,0 +1,52 @@ +package com.yxt.pay.biz.order; + +import com.yxt.common.core.result.ResultBean; +import com.yxt.pay.api.order.OrderDto; +import com.yxt.pay.api.order.OrderQuery; +import com.yxt.pay.api.order.OrderVo; +import com.yxt.pay.utils.ApiBaseAction; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/6 + **/ +@RestController +@RequestMapping("/order") +public class OrderRest extends ApiBaseAction { +//9370ec84-2723-40ee-bb6f-680fce6a25a2 + //05ba58d6-f1f0-4f68-9bcc-62ceeaf4c088 + //o81zC60V3ymrfjgK-BifvcyWfJBo + + @Autowired + private OrderService orderService; + + @PostMapping("createOrder") + ResultBean createOrder(@RequestBody OrderDto dto) { + return orderService.createOrder(dto); + } + + @PostMapping("/pay") + ResultBean pay(@RequestBody OrderQuery query) { + return orderService.pay(query, getClientIp()); + } + + @PostMapping("wxPayNotify") + ResultBean wxPayNotify(HttpServletRequest request) throws IOException { + return orderService.wxPayNotify(request.getInputStream()); + } + + @PostMapping("selectOrder") + ResultBean selectOrder(@RequestBody OrderQuery query) { + return orderService.selectOrder(query); + } +} diff --git a/src/main/java/com/yxt/pay/biz/order/OrderService.java b/src/main/java/com/yxt/pay/biz/order/OrderService.java new file mode 100644 index 0000000..d9a39a7 --- /dev/null +++ b/src/main/java/com/yxt/pay/biz/order/OrderService.java @@ -0,0 +1,248 @@ +package com.yxt.pay.biz.order; + +import com.yxt.common.base.service.MybatisBaseService; +import com.yxt.common.core.result.ResultBean; +import com.yxt.pay.api.order.*; +import com.yxt.pay.api.wxpay.WxPayVo; +import com.yxt.pay.biz.wxpay.WxPayService; +import com.yxt.pay.utils.*; +import com.yxt.pay.utils.applet.WechatRefundApiResult; +import com.yxt.pay.utils.applet.WechatUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.axis.i18n.RB; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.servlet.ServletInputStream; +import java.io.ByteArrayOutputStream; +import java.math.BigDecimal; +import java.util.*; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/6 + **/ +@Service +@Slf4j +public class OrderService extends MybatisBaseService { + + @Autowired + private UrlComponent urlComponent; + + String tradeType = "JSAPI"; + //微信统一下单 + String uniformorder = "https://api.mch.weixin.qq.com/pay/unifiedorder"; + + public ResultBean createOrder(OrderDto dto) { + ResultBean rb = ResultBean.fireFail(); + PayOrder order = new PayOrder(dto.getSource()); + order.setTotalTee(dto.getTotalTee()); + order.setTimeRemarks(dto.getTimeRemarks()); + order.setName(dto.getName()); + order.setCreateBySid(dto.getUserSid()); + order.setOpenId(dto.getOpenId()); + baseMapper.insert(order); + /* OrderVo orderVo = new OrderVo(); + Map resultObj = new TreeMap(); + if (dto.getPayType() == 0) {//支付宝 + return rb.setMsg("暂不支持支付宝支付"); + } else if (dto.getPayType() == 1) { + try { + Map parame = new TreeMap(); + parame.put("appid", order.getAppId()); + // 商家账号。 + parame.put("mch_id", order.getMchId()); + String randomStr = CharUtil.getRandomNum(18).toUpperCase(); + // 随机字符串 + parame.put("nonce_str", randomStr); + // 商户订单编号 + parame.put("out_trade_no", order.getOutTradeNo()); + + // 商品描述 + parame.put("body", order.getRemark()); + + //支付金额 + parame.put("total_fee", new BigDecimal(dto.getTotalTee()).multiply(new BigDecimal(100)).intValue()); + // 回调地址 + parame.put("notify_url", "http://192.168.0.111:7777/wxPay/payNotify"); + // 交易类型APP + parame.put("trade_type", tradeType); + parame.put("spbill_create_ip", ip); + parame.put("openid", dto.getOpenId()); + String sign = WechatUtil.arraySign(parame, order.getSecret()); + // 数字签证 + parame.put("sign", sign); + String xml = MapUtils.convertMap2Xml(parame); + log.info("xml:" + xml); + Map resultUn = XmlUtil.xmlStrToMap(WechatUtil.requestOnce(uniformorder, xml)); + // 响应报文 + String return_code = MapUtils.getString("return_code", resultUn); + String return_msg = MapUtils.getString("return_msg", resultUn); + if (return_code.equalsIgnoreCase("FAIL")) { + return rb.setMsg("支付失败," + return_msg); + } else if (return_code.equalsIgnoreCase("SUCCESS")) { + String prepay_id = MapUtils.getString("prepay_id", resultUn); + // 先生成paySign 参考https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5 + resultObj.put("appId", order.getAppId()); + resultObj.put("timeStamp", DateUtils.timeToStr(System.currentTimeMillis() / 1000, DateUtils.DATE_TIME_PATTERN)); + resultObj.put("nonceStr", nonceStr); +// resultObj.put("package", "prepay_id=" + prepay_id); + resultObj.put("package", "Sign=WXPay"); + resultObj.put("partnerid", order.getMchId()); + resultObj.put("signType", "MD5"); + resultObj.put("prepayid", prepay_id); + String paySign = WechatUtil.arraySign(resultObj, order.getSecret()); +// resultObj.put("paySign", paySign); + resultObj.put("sign", paySign); + return rb.success().setData(orderVo); + } + } catch (Exception e) { + e.printStackTrace(); + return rb.setMsg("支付失败,error=" + e.getMessage()); + } + }*/ + + return rb.success().setData(order.getSid()); + } + + public ResultBean pay(OrderQuery query, String ip) { + ResultBean rb = ResultBean.fireFail(); + PayOrder payOrder = fetchBySid(query.getMainSid()); + if (payOrder == null) { + return rb.setMsg("此订单不存在"); + } + //验证订单是否已过期 + Date createTime = payOrder.getCreateTime(); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(createTime); + calendar.add(Calendar.MINUTE, payOrder.getTimeRemarks()); + //过期时间 + long newTimeInMillis = calendar.getTimeInMillis(); + // 获取当前时间的毫秒表示 + long currentTimeInMillis = System.currentTimeMillis(); + if (newTimeInMillis < currentTimeInMillis) { + return rb.setMsg("订单已过期"); + } + //订单是否已支付过 + if (payOrder.getState() == 2) { + return rb.setMsg("订单不是未支付,不能重复支付"); + } + Map resultObj = new TreeMap(); + if (query.getType().equals(PayTypeEnum.WECHAT.getCode())) {//微信 + WxPayVo wxPayVo = new WxPayVo(payOrder.getSource()); + String nonceStr = CharUtil.getRandomString(32); + try { + Map parame = new TreeMap(); + parame.put("appid", wxPayVo.getAppId()); + // 商家账号。 + parame.put("mch_id", wxPayVo.getMchId()); + String randomStr = CharUtil.getRandomNum(18).toUpperCase(); + // 随机字符串 + parame.put("nonce_str", randomStr); + // 商户订单编号 + parame.put("out_trade_no", payOrder.getOutTradeNo()); + + // 商品描述 + parame.put("body", payOrder.getName()); + + //支付金额 + parame.put("total_fee", new BigDecimal(payOrder.getTotalTee()).multiply(new BigDecimal(100)).intValue()); + // 回调地址 + parame.put("notify_url", urlComponent.getDoMainUrl() + "/wxPay/payNotify"); + // 交易类型APP + parame.put("trade_type", tradeType); + parame.put("spbill_create_ip", ip); + parame.put("openid", payOrder.getOpenId()); + String sign = WechatUtil.arraySign(parame, wxPayVo.getSecret()); + // 数字签证 + parame.put("sign", sign); + String xml = MapUtils.convertMap2Xml(parame); + log.info("xml:" + xml); + Map resultUn = XmlUtil.xmlStrToMap(WechatUtil.requestOnce(uniformorder, xml)); + // 响应报文 + String return_code = MapUtils.getString("return_code", resultUn); + String return_msg = MapUtils.getString("return_msg", resultUn); + if (return_code.equalsIgnoreCase("FAIL")) { + return rb.setMsg("支付失败," + return_msg); + } else if (return_code.equalsIgnoreCase("SUCCESS")) { + String prepay_id = MapUtils.getString("prepay_id", resultUn); + // 先生成paySign 参考https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=7_7&index=5 + resultObj.put("appId", wxPayVo.getAppId()); + resultObj.put("timeStamp", DateUtils.timeToStr(System.currentTimeMillis() / 1000, DateUtils.DATE_TIME_PATTERN)); + resultObj.put("nonceStr", nonceStr); +// resultObj.put("package", "prepay_id=" + prepay_id); + resultObj.put("package", "Sign=WXPay"); + resultObj.put("partnerid", wxPayVo.getMchId()); + resultObj.put("signType", "MD5"); + resultObj.put("prepayid", prepay_id); + String paySign = WechatUtil.arraySign(resultObj, wxPayVo.getSecret()); +// resultObj.put("paySign", paySign); + resultObj.put("sign", paySign); + payOrder.setPayType(PayTypeEnum.WECHAT.getCode()); + baseMapper.updateById(payOrder); + return rb.success().setData(resultObj); + } + } catch (Exception e) { + e.printStackTrace(); + return rb.setMsg("支付失败,error=" + e.getMessage()); + } + } else { + + } + return rb; + } + + public ResultBean wxPayNotify(ServletInputStream inputStream) { + ResultBean rb = ResultBean.fireFail(); + try { + ByteArrayOutputStream outSteam = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int len = 0; + while ((len = inputStream.read(buffer)) != -1) { + outSteam.write(buffer, 0, len); + } + outSteam.close(); + inputStream.close(); //xml数据 + String reponseXml = new String(outSteam.toByteArray(), "utf-8"); + WechatRefundApiResult result = (WechatRefundApiResult) XmlUtil.xmlStrToBean(reponseXml, WechatRefundApiResult.class); + String result_code = result.getResult_code(); + //订单编号 + String out_trade_no = result.getOut_trade_no(); + log.error("订单" + out_trade_no + "支付成功"); + // 业务处理 + //根据订单编号查询 + PayOrder payOrder = baseMapper.selectBySn(out_trade_no); + payOrder.setState(2); + payOrder.setModifyTime(new Date()); + payOrder.setPayTypeValue("微信"); + if (result_code.equalsIgnoreCase("FAIL")) { + log.error("订单" + out_trade_no + "支付失败"); + return rb.setMsg("订单" + out_trade_no + "支付失败"); + } else if (result_code.equalsIgnoreCase("SUCCESS")) { + baseMapper.updateById(payOrder); + return rb.success(); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + return rb; + } + + public ResultBean selectOrder(OrderQuery query) { + ResultBean rb = ResultBean.fireFail(); + Map map = new HashMap<>(); + PayOrder payOrder = fetchBySid(query.getMainSid()); + if (payOrder == null) { + return rb.setMsg("订单不存在"); + } + map.put("createTime", DateUtils.format(payOrder.getCreateTime(), "yyyy-MM-dd HH:mm:ss")); + map.put("payTime", DateUtils.format(payOrder.getModifyTime(), "yyyy-MM-dd HH:mm:ss")); + map.put("outTradeNo", payOrder.getOutTradeNo()); + map.put("mainSid", payOrder.getSid()); + map.put("payType", payOrder.getPayTypeValue()); + return rb.success().setData(map); + } +} diff --git a/src/main/java/com/yxt/pay/biz/wxpay/WxPayRest.java b/src/main/java/com/yxt/pay/biz/wxpay/WxPayRest.java new file mode 100644 index 0000000..00543d4 --- /dev/null +++ b/src/main/java/com/yxt/pay/biz/wxpay/WxPayRest.java @@ -0,0 +1,144 @@ +/* +package com.yxt.pay.biz.wxpay; + +import com.alibaba.fastjson.JSON; +import com.alibaba.nacos.client.utils.ValidatorUtils; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.yxt.common.base.utils.StringUtils; +import com.yxt.common.core.result.ResultBean; +import com.yxt.pay.api.wxpay.*; +import com.yxt.pay.config.WxPayApi; +import com.yxt.pay.utils.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.math.BigDecimal; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +*/ +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/3 + **//* + +@RestController +@RequestMapping("/wxPay") +public class WxPayRest extends AbstractWxPayApiController { + + private Logger log = LoggerFactory.getLogger(this.getClass()); + + @Autowired + WxPayBean wxPayBean; + + private String notifyUrl; + private String refundNotifyUrl; + private static final String USER_PAYING = "USERPAYING"; + + @Override + public WxPayApiConfig getApiConfig() { + WxPayApiConfig apiConfig; + try { + apiConfig = WxPayApiConfigKit.getApiConfig(wxPayBean.getAppId()); + } catch (Exception e) { + apiConfig = WxPayApiConfig.builder() + .appId(wxPayBean.getAppId()) + .mchId(wxPayBean.getMchId()) + .partnerKey(wxPayBean.getPartnerKey()) + .certPath(wxPayBean.getCertPath()) + .domain(wxPayBean.getDomain()) + .build(); + } + notifyUrl = apiConfig.getDomain().concat("/wxPay/payNotify"); + refundNotifyUrl = apiConfig.getDomain().concat("/wxPay/refundNotify"); + return apiConfig; + } + + @PostMapping(value = "/miniAppPay") + ResultBean miniAppPay(HttpServletRequest request, PayOrderQuery payOrderQuery) { + ResultBean rb = ResultBean.fireFail(); + try { + String ip = IpKit.getRealIp(request); + if (StringUtils.isBlank(ip)) { + ip = "127.0.0.1"; + } + WxPayBean wxPayApiConfig = new WxPayBean(); + + wxPayApiConfig.setAppId(payOrderQuery.getAppId()); + wxPayApiConfig.setMchId(payOrderQuery.getMchId()); + wxPayApiConfig.setDomain(payOrderQuery.getNotifyUrl()); + wxPayApiConfig.setPartnerKey(payOrderQuery.getPaySignKey()); + Map params = UnifiedOrderModel + .builder() + .appid(wxPayApiConfig.getAppId()) + .mch_id(wxPayApiConfig.getMchId()) + .nonce_str(WxPayKit.generateStr()) + .body("小程序支付") + .attach("Node.js 版:https://gitee.com/javen205/TNW") + .out_trade_no(payOrderQuery.getOrderSn()) + .total_fee(new BigDecimal(payOrderQuery.getAmount()).multiply(new BigDecimal("100")) + "") + .spbill_create_ip(ip) + .notify_url(notifyUrl) + .trade_type(TradeType.JSAPI.getTradeType()) + .openid(payOrderQuery.getOpenId()) + .build() + .createSign(wxPayApiConfig.getPartnerKey(), SignType.HMACSHA256); + //统一下单 + String xmlResult = WxPayApi.pushOrder(false, params); + log.info(xmlResult); + Map result = WxPayKit.xmlToMap(xmlResult); + + String returnCode = result.get("return_code"); + String returnMsg = result.get("return_msg"); + if (!WxPayKit.codeIsOk(returnCode)) { + return rb.setMsg(returnMsg); + } + String resultCode = result.get("result_code"); + if (!WxPayKit.codeIsOk(resultCode)) { + return rb.setMsg(returnMsg); + } + // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回(统一下单成功) + String prepayId = result.get("prepay_id"); + Map packageParams = WxPayKit.miniAppPrepayIdCreateSign(wxPayApiConfig.getAppId(), prepayId, + wxPayApiConfig.getPartnerKey(), SignType.HMACSHA256); + String jsonStr = JSON.toJSONString(packageParams); + log.info("小程序支付的参数:" + jsonStr); + return rb.success().setData(packageParams); + } catch (Exception e) { + e.printStackTrace(); + return rb.setMsg(e.getMessage()); + } + } + + @PostMapping(value = "/payNotify") + @ResponseBody + public String payNotify(HttpServletRequest request) { + String xmlMsg = HttpKit.readData(request); + Map params = WxPayKit.xmlToMap(xmlMsg); + log.info("微信支付通知=" + params); + String returnCode = params.get("return_code"); + //订单编号 + String out_trade_no = params.get("out_trade_no"); + // 注意重复通知的情况,同一订单号可能收到多次通知,请注意一定先判断订单状态 + // 注意此处签名方式需与统一下单的签名类型一致 + if (WxPayKit.verifyNotify(params, this.getApiConfig().getPartnerKey(), SignType.HMACSHA256)) { + if (WxPayKit.codeIsOk(returnCode)) { + // 发送通知等 + Map xml = new HashMap(2); + xml.put("return_code", "SUCCESS"); + xml.put("return_msg", "OK"); + return WxPayKit.toXml(xml); + } else { + log.error("订单" + out_trade_no + "支付失败"); + return null; + } + } + return null; + } +} +*/ diff --git a/src/main/java/com/yxt/pay/biz/wxpay/WxPayService.java b/src/main/java/com/yxt/pay/biz/wxpay/WxPayService.java new file mode 100644 index 0000000..cfba914 --- /dev/null +++ b/src/main/java/com/yxt/pay/biz/wxpay/WxPayService.java @@ -0,0 +1,13 @@ +package com.yxt.pay.biz.wxpay; + +import org.springframework.stereotype.Service; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/7 + **/ +@Service +public class WxPayService { + +} diff --git a/src/main/java/com/yxt/pay/utils/ApiBaseAction.java b/src/main/java/com/yxt/pay/utils/ApiBaseAction.java new file mode 100644 index 0000000..1989185 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/ApiBaseAction.java @@ -0,0 +1,126 @@ +package com.yxt.pay.utils; + + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.TypeMismatchException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.propertyeditors.StringTrimmerEditor; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.InitBinder; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.request.WebRequest; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.Map; + + +/** + * @author mallplus + * @ClassName: ApiBaseAction + * @Description: 基础控制类 + * @date 2016年9月2日 + */ +@Slf4j +public class ApiBaseAction { + + /** + * 得到request对象 + */ + @Autowired + protected HttpServletRequest request; + /** + * 得到response对象 + */ + @Resource + protected HttpServletResponse response; + /** + * 获取请求的用户Id + * + * @return 客户端Ip + */ + /*public String getUserId() { + String token = request.getHeader(AuthorizationInterceptor.LOGIN_TOKEN_KEY); + //查询token信息 + TokenEntity tokenEntity = tokenService.queryByToken(token); + if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) { + return null; + } + return tokenEntity.getUserId(); + }*/ + + /** + * @param requestCode + * @param msg + * @param data + * @return Map + * @throws + * @Description:构建统一格式返回对象 + * @date 2016年9月2日 + * @author zhuliyun + */ + public static Map toResponsObject(int requestCode, String msg, Object data) { + Map obj = new HashMap(); + obj.put("code", requestCode); + obj.put("msg", msg); + if (data != null) + obj.put("data", data); + return obj; + } + + public static Map toResponsSuccessForSelect(Object data) { + Map result = new HashMap<>(2); + result.put("list", data); + return toResponsObject(200, "执行成功", result); + } + + /** + * 参数绑定异常 + */ + @ExceptionHandler({BindException.class, MissingServletRequestParameterException.class, TypeMismatchException.class}) + @ResponseBody + public Map bindException(Exception e) { + if (e instanceof BindException) { + return toResponsObject(1, "参数绑定异常", e.getMessage()); + } + return toResponsObject(1, "处理异常", e.getMessage()); + } + + /** + * initBinder 初始化绑定
+ * 这里处理了3种类型
+ * 1、字符串自动 trim 去掉前后空格
+ * 2、java.util.Date 转换为 "yyyy-MM-dd HH:mm:ss" 格式
+ * 3、java.sql.Date 转换为 "yyyy-MM-dd" 格式
+ * 4、java.util.Timestamps 时间转换 + * + * @param binder WebDataBinder 要注册的binder + * @param request 前端请求 + */ + @InitBinder + public void initBinder(WebDataBinder binder, WebRequest request) { + + // 字符串自动Trim + binder.registerCustomEditor(String.class, new StringTrimmerEditor(false)); + } + + /** + * 获取请求方IP + * + * @return 客户端Ip + */ + public String getClientIp() { + String xff = request.getHeader("x-forwarded-for"); + if (xff == null) { + xff = "127.0.0.1"; + } + return xff; + } + + +} diff --git a/src/main/java/com/yxt/pay/utils/CharUtil.java b/src/main/java/com/yxt/pay/utils/CharUtil.java new file mode 100644 index 0000000..162590f --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/CharUtil.java @@ -0,0 +1,80 @@ +package com.yxt.pay.utils; + +import java.util.Random; + +public class CharUtil { + + /** + * 获取随机字符串 + * + * @param num + * @return + */ + public static String getRandomString(Integer num) { + String base = "abcdefghijklmnopqrstuvwxyz0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < num; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } + + /** + * 获取随机字符串 + * + * @param num + * @return + */ + public static String getRandomNum(Integer num) { + String base = "0123456789"; + Random random = new Random(); + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < num; i++) { + int number = random.nextInt(base.length()); + sb.append(base.charAt(number)); + } + return sb.toString(); + } + + /** + * 右补位,左对齐 + * + * @param oriStr 原字符串 + * @param len 目标字符串长度 + * @param fillChar 补位字符 + * @return 目标字符串 + */ + public static String padRight(String oriStr, int len, char fillChar) { + String str = ""; + int strlen = oriStr.length(); + if (strlen < len) { + for (int i = 0; i < len - strlen; i++) { + str = str + fillChar; + } + } + str = str + oriStr; + return str; + } + + /** + * 左补位,右对齐 + * + * @param oriStr 原字符串 + * @param len 目标字符串长度 + * @param fillChar 补位字符 + * @return 目标字符串 + */ + public static String padLeft(String oriStr, int len, char fillChar) { + int strlen = oriStr.length(); + String str = ""; + if (strlen < len) { + for (int i = 0; i < len - strlen; i++) { + str = str + fillChar; + } + } + str = oriStr + str; + return str; + } +} diff --git a/src/main/java/com/yxt/pay/utils/DateUtils.java b/src/main/java/com/yxt/pay/utils/DateUtils.java new file mode 100644 index 0000000..82a1613 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/DateUtils.java @@ -0,0 +1,220 @@ +package com.yxt.pay.utils; + +import org.slf4j.LoggerFactory; +import org.springframework.util.StringUtils; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * 日期处理 + * + * @author zscat + * @email 951449465@qq.com + * @date 2016年12月21日 下午12:53:33 + */ +public class DateUtils { + /** + * 时间格式(yyyy-MM-dd) + */ + public final static String DATE_PATTERN = "yyyy-MM-dd"; + /** + * 时间格式(yyyy-MM-dd HH:mm:ss) + */ + public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + /** + * 无分隔符日期格式 "yyyyMMddHHmmssSSS" + */ + public static String DATE_TIME_PATTERN_YYYY_MM_DD_HH_MM_SS_SSS = "yyyyMMddHHmmssSSS"; + // 日期转换格式数组 + public static String[][] regularExp = new String[][]{ + + // 默认格式 + {"\\d{4}-((([0][1,3-9]|[1][0-2]|[1-9])-([0-2]\\d|[3][0,1]|[1-9]))|((02|2)-(([1-9])|[0-2]\\d)))\\s+([0,1]\\d|[2][0-3]|\\d):([0-5]\\d|\\d):([0-5]\\d|\\d)", + DATE_TIME_PATTERN}, + // 仅日期格式 年月日 + {"\\d{4}-((([0][1,3-9]|[1][0-2]|[1-9])-([0-2]\\d|[3][0,1]|[1-9]))|((02|2)-(([1-9])|[0-2]\\d)))", + DATE_PATTERN}, + // 带毫秒格式 + {"\\d{4}((([0][1,3-9]|[1][0-2]|[1-9])([0-2]\\d|[3][0,1]|[1-9]))|((02|2)(([1-9])|[0-2]\\d)))([0,1]\\d|[2][0-3])([0-5]\\d|\\d)([0-5]\\d|\\d)\\d{1,3}", + DATE_TIME_PATTERN_YYYY_MM_DD_HH_MM_SS_SSS} + }; + // 日志 + private static org.slf4j.Logger logger = LoggerFactory.getLogger(DateUtils.class); + + public static String format(Date date) { + return format(date, DATE_PATTERN); + } + + public static String format(Date date, String pattern) { + if (date != null) { + SimpleDateFormat df = new SimpleDateFormat(pattern); + return df.format(date); + } + return null; + } + + public static String timeToStr(Long time, String pattern) { + SimpleDateFormat dateFormat = new SimpleDateFormat(pattern); + if (time.toString().length() < 13) { + time = time * 1000L; + } + Date date = new Date(time); + String value = dateFormat.format(date); + return value; + } + + public static long strToTime(String timeStr) { + Date time = strToDate(timeStr); + return time.getTime() / 1000; + } + + + /** + * 转换为时间类型格式 + * + * @param strDate 日期 + * @return + */ + public static Date strToDate(String strDate) { + try { + String strType = getDateFormat(strDate); + SimpleDateFormat sf = new SimpleDateFormat(strType); + return new Date((sf.parse(strDate).getTime())); + } catch (Exception e) { + return null; + } + } + + + /** + * 根据传入的日期格式字符串,获取日期的格式 + * + * @return 秒 + */ + public static String getDateFormat(String date_str) { + String style = null; + if (StringUtils.isEmpty(date_str)) { + return null; + } + boolean b = false; + for (int i = 0; i < regularExp.length; i++) { + b = date_str.matches(regularExp[i][0]); + if (b) { + style = regularExp[i][1]; + } + } + if (StringUtils.isEmpty(style)) { + logger.info("date_str:" + date_str); + logger.info("日期格式获取出错,未识别的日期格式"); + } + return style; + } + + /** + * 将字符串类型的转换成Date类型 + * + * @param dateStr 字符串类型的日期 yyyy-MM-dd + * @return Date类型的日期 + * @throws ParseException + */ + public static Date convertStringToDate(String dateStr) { + // 返回的日期 + Date resultDate = null; + try { + // 日期格式转换 + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + resultDate = sdf.parse(dateStr); + } catch (ParseException e) { + e.printStackTrace(); + } + return resultDate; + } + + /** + * 将字符串类型的转换成Date类型 + * + * @param dateStr 字符串类型的日期 yyyy-MM-dd + * @return Date类型的日期 + * @throws ParseException + */ + public static Date convertStringToDate(String dateStr, String formate) { + // 返回的日期 + Date resultDate = null; + try { + // 日期格式转换 + SimpleDateFormat sdf = new SimpleDateFormat(formate); + resultDate = sdf.parse(dateStr); + } catch (ParseException e) { + e.printStackTrace(); + } + return resultDate; + } + + /** + * 添加小时 + * + * @param date + * @param hour + * @return + */ + public static String addHours(Date date, int hour) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.HOUR, hour);// 24小时制 + date = cal.getTime(); + cal = null; + return format.format(date); + + } + + /** + * 添加分钟 + * + * @param date + * @param min + * @return + */ + public static String addMins(Date date, int min) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.MINUTE, min);// 24小时制 + date = cal.getTime(); + cal = null; + return format.format(date); + + } + + public static void main(String[] args) throws ParseException { + System.out.println(DateUtils.addMins(new Date(), 10)); + DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + // 获取当前时间 + Date date = new Date(); + String nowtime = df.format(date); + // System.out.println(nowtime); + Date d2 = df.parse("2019-09-10 10:07:00"); //86400000 1728 + Date d1 = df.parse(nowtime); + + long diff = d1.getTime() - d2.getTime();// 这样得到的差值是微秒级别 + System.out.println(d1.getTime()); + System.out.println(diff); + System.out.println(1000 * 60 * 60 * 24); + long days = diff / (1000 * 60 * 60 * 24);//天 + + long hours = (diff - days * (1000 * 60 * 60 * 24)) + / (1000 * 60 * 60); //小时 + long mins = (diff - days * (1000 * 60 * 60 * 24) - hours * (1000 * 60 * 60)) / (1000 * 60); //小时 + long sc = (diff - days * (1000 * 60 * 60 * 24) - hours + * (1000 * 60 * 60) - mins * (1000 * 60)) / (1000); // 秒 + + System.out.println(days); + System.out.println(hours); + System.out.println(mins); + System.out.println(sc); + } +} diff --git a/src/main/java/com/yxt/pay/utils/DealMapValueHelper.java b/src/main/java/com/yxt/pay/utils/DealMapValueHelper.java new file mode 100644 index 0000000..1bc00d5 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/DealMapValueHelper.java @@ -0,0 +1,7 @@ +package com.yxt.pay.utils; + +import java.util.Map; + +public interface DealMapValueHelper { + void dealValue(String key, Map map); +} diff --git a/src/main/java/com/yxt/pay/utils/MapUtils.java b/src/main/java/com/yxt/pay/utils/MapUtils.java new file mode 100644 index 0000000..ab36bb7 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/MapUtils.java @@ -0,0 +1,306 @@ +package com.yxt.pay.utils; + + +import com.yxt.common.base.utils.StringUtils; +import org.apache.commons.beanutils.PropertyUtilsBean; +import org.apache.commons.lang.ArrayUtils; +import org.springframework.util.Assert; + +import java.beans.PropertyDescriptor; +import java.math.BigDecimal; +import java.sql.Date; +import java.sql.Timestamp; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * 获取map中值的工具类,自动进行类型转换 + * + * @author DT_panda + */ +public class MapUtils { + + public static String getString(String key, Map map) { + if (map == null || key == null) + throw new IllegalArgumentException(); + if (!map.containsKey(key)) + return null; + Object value = map.get(key); + if (value == null) + return null; + return value.toString(); + } + + public static Integer getInteger(String key, Map map) { + if (map == null || key == null) { + throw new IllegalArgumentException(); + } + if (!map.containsKey(key)) { + return null; + } + Object value = map.get(key); + if (value == null) { + return null; + } + if (value instanceof Integer) { + return (Integer) value; + } + if (value instanceof String) + return Integer.valueOf((String) value); + //Date 不支持变成为date类型 + if (value instanceof Date) + throw new ClassCastException(); + if (value instanceof Number) + return ((Number) value).intValue(); + throw new ClassCastException(); + } + + public static Long getLong(String key, Map map) { + if (map == null || key == null) + throw new IllegalArgumentException(); + if (!map.containsKey(key)) + return null; + Object value = map.get(key); + if (value == null) + return null; + if (value instanceof Long) + return (Long) value; + if (value instanceof Number) + return ((Number) value).longValue(); + if (value instanceof String) + return Long.valueOf((String) value); + if (value instanceof Date) { + return (((Date) value).getTime()); + } + if (value instanceof java.sql.Time) { + return ((java.sql.Time) value).getTime(); + } + if (value instanceof Timestamp) { + return ((Timestamp) value).getTime(); + } + + throw new ClassCastException(); + } + + public static Double getDouble(String key, Map map) { + if (map == null || key == null) + throw new IllegalArgumentException(); + if (!map.containsKey(key)) + return null; + Object value = map.get(key); + if (value == null) + return null; + if (value instanceof Double) + return (Double) value; + if (value instanceof Number) + return ((Number) value).doubleValue(); + if (value instanceof String) + return Double.valueOf((String) value); + throw new ClassCastException(); + } + + public static BigDecimal getBigDecimal(String key, Map map) { + if (map == null || key == null) + throw new IllegalArgumentException(); + if (!map.containsKey(key)) + return null; + Object value = map.get(key); + if (value == null) + return null; + if (value instanceof BigDecimal) + return (BigDecimal) value; + if (value instanceof Integer) + return new BigDecimal((Integer) value); + if (value instanceof Short) + return new BigDecimal((Short) value); + if (value instanceof Byte) + return new BigDecimal((Byte) value); + if (value instanceof Long) + return new BigDecimal((Long) value); + if (value instanceof Float) + return new BigDecimal((Float) value); + if (value instanceof Double) + return new BigDecimal((Double) value); + if (value instanceof Date) { + return new BigDecimal(((Date) value).getTime()); + } + if (value instanceof java.sql.Time) { + return new BigDecimal(((java.sql.Time) value).getTime()); + } + if (value instanceof Timestamp) { + return new BigDecimal(((Timestamp) value).getTime()); + } + if (value instanceof String) { + if (!StringUtils.isBlank((String) value)) + return new BigDecimal((String) value); + else + return null; + } + throw new ClassCastException(); + } + + /** + * 将bean转化为map + * + * @param bean + * @return + */ + public static Map getMap(Object bean) { + return beanToMap(bean); + } + + /** + * 将map中key为likeKey的value前后加上字符'%',用于like查询 + * + * @param map + * @param likeKey + */ + public static void toLikeValue(Map map, String... likeKey) { + if (ArrayUtils.isEmpty(likeKey)) + return; + for (String key : likeKey) { + if (map.containsKey(key)) + map.put(key, "%" + map.get(key) + "%"); + } + } + + /** + * 获取日期 + * + * @param key + * @param map + * @return + */ + public static Date getDate(String key, Map map) { + if (map == null || key == null) + throw new IllegalArgumentException(); + if (!map.containsKey(key)) + return null; + Object value = map.get(key); + if (value == null) + return null; + else { + if (value instanceof Date) { + return (Date) value; + } else if (value instanceof Timestamp) { + return new Date(((Timestamp) value).getTime()); + } + } + return null; + } + + /** + * 获取日期 + * + * @param key + * @param map + * @return + */ + public static java.util.Date getTimestamp(String key, Map map) { + if (map == null || key == null) + throw new IllegalArgumentException(); + if (!map.containsKey(key)) + return null; + Object value = map.get(key); + if (value == null) + return null; + else { + if (value instanceof Date) { + return (Date) value; + } else if (value instanceof Timestamp) { + Timestamp ts = (Timestamp) value; + return ts; + } + } + return null; + } + + /** + * 如果value不为空 ,则放到map中 + * + * @param map + * @param key + * @param value + */ + public static void putIfValueNotNull(Map map, String key, Object value) { + Assert.notNull(map); + Assert.hasText(key); + if (value != null) + map.put(key, value); + } + + /** + * 如果value不为空 ,则放到map中 + * + * @param map + * @param key + * @param value + */ + public static void putIfValueNotEmpty(Map map, String key, String value) { + Assert.notNull(map); + Assert.hasText(key); + if (!StringUtils.isBlank(value)) + map.put(key, value); + } + + /** + * 将map中指定的key的value值进行处理 + * + * @param key + * @param map + * @param helper + */ + public static void convertMapValuePattern(String key, Map map, DealMapValueHelper helper) { + Assert.hasText(key); + Assert.notNull(map); + Assert.notNull(helper); + helper.dealValue(key, map); + } + + /** + * 将javabean实体类转为map类型,然后返回一个map类型的值 + * + * @return + */ + public static Map beanToMap(Object beanObj) { + Map params = new HashMap(0); + try { + PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean(); + PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(beanObj); + for (int i = 0; i < descriptors.length; i++) { + String name = descriptors[i].getName(); + if (!"class".equals(name)) { + params.put(name, propertyUtilsBean.getNestedProperty(beanObj, name)); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return params; + } + + public static String convertMap2Xml(Map paraMap) { + StringBuffer xmlStr = new StringBuffer(); + if (paraMap != null) { + xmlStr.append(""); + Set keySet = paraMap.keySet(); + Iterator keyIte = keySet.iterator(); + while (keyIte.hasNext()) { + String key = (String) keyIte.next(); + String val = String.valueOf(paraMap.get(key)); + xmlStr.append("<"); + xmlStr.append(key); + xmlStr.append(">"); + xmlStr.append(val); + xmlStr.append(""); + } + xmlStr.append(""); + } + return xmlStr.toString(); + } +} + diff --git a/src/main/java/com/yxt/pay/utils/UrlComponent.java b/src/main/java/com/yxt/pay/utils/UrlComponent.java new file mode 100644 index 0000000..f78aa63 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/UrlComponent.java @@ -0,0 +1,20 @@ +package com.yxt.pay.utils; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @description: + * @author: dimengzhe + * @date: 2024/1/7 + **/ +@Component +public class UrlComponent { + + @Value("${domain.url:http://127.0.0.1:7777}") + private String doMainUrl; + + public String getDoMainUrl() { + return doMainUrl; + } +} diff --git a/src/main/java/com/yxt/pay/utils/XmlHelper.java b/src/main/java/com/yxt/pay/utils/XmlHelper.java new file mode 100644 index 0000000..8df4f7f --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/XmlHelper.java @@ -0,0 +1,254 @@ +package com.yxt.pay.utils; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; + +/** + * xpath解析xml + *

+ *

+ *     文档地址:
+ *     http://www.w3school.com.cn/xpath/index.asp
+ * 
+ * + * @author L.cm + */ +public class XmlHelper { + private final XPath path; + private final Document doc; + + private XmlHelper(InputSource inputSource) throws ParserConfigurationException, SAXException, IOException { + DocumentBuilderFactory dbf = getDocumentBuilderFactory(); + // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all + // XML entity attacks are prevented + // Xerces 2 only - + // http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + + // If you can't completely disable DTDs, then at least do the following: + // Xerces 1 - + // http://xerces.apache.org/xerces-j/features.html#external-general-entities + // Xerces 2 - + // http://xerces.apache.org/xerces2-j/features.html#external-general-entities + // JDK7+ - http://xml.org/sax/features/external-general-entities + dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); + + // Xerces 1 - + // http://xerces.apache.org/xerces-j/features.html#external-parameter-entities + // Xerces 2 - + // http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities + // JDK7+ - http://xml.org/sax/features/external-parameter-entities + dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + + // Disable external DTDs as well + dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and + // Entity Attacks" + dbf.setXIncludeAware(false); + dbf.setExpandEntityReferences(false); + DocumentBuilder db = dbf.newDocumentBuilder(); + doc = db.parse(inputSource); + path = getXpathFactory().newXPath(); + } + + private static XmlHelper create(InputSource inputSource) { + try { + return new XmlHelper(inputSource); + } catch (ParserConfigurationException e) { + throw new RuntimeException(e); + } catch (SAXException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static XmlHelper of(InputStream is) { + InputSource inputSource = new InputSource(is); + return create(inputSource); + } + + public static XmlHelper of(File file) { + InputSource inputSource = new InputSource(file.toURI().toASCIIString()); + return create(inputSource); + } + + public static XmlHelper of(String xmlStr) { + StringReader sr = new StringReader(xmlStr.trim()); + InputSource inputSource = new InputSource(sr); + XmlHelper xmlHelper = create(inputSource); + sr.close(); + return xmlHelper; + } + + private static DocumentBuilderFactory getDocumentBuilderFactory() { + return XmlHelperHolder.documentBuilderFactory; + } + + private static XPathFactory getXpathFactory() { + return XmlHelperHolder.xPathFactory; + } + + private Object evalXpath(String expression, Object item, QName returnType) { + item = null == item ? doc : item; + try { + return path.evaluate(expression, item, returnType); + } catch (XPathExpressionException e) { + throw new RuntimeException(e); + } + } + + /** + * 获取String + * + * @param expression 路径 + * @return String + */ + public String getString(String expression) { + return (String) evalXpath(expression, null, XPathConstants.STRING); + } + + /** + * 获取Boolean + * + * @param expression 路径 + * @return String + */ + public Boolean getBoolean(String expression) { + return (Boolean) evalXpath(expression, null, XPathConstants.BOOLEAN); + } + + /** + * 获取Number + * + * @param expression 路径 + * @return {Number} + */ + public Number getNumber(String expression) { + return (Number) evalXpath(expression, null, XPathConstants.NUMBER); + } + + /** + * 获取某个节点 + * + * @param expression 路径 + * @return {Node} + */ + public Node getNode(String expression) { + return (Node) evalXpath(expression, null, XPathConstants.NODE); + } + + /** + * 获取子节点 + * + * @param expression 路径 + * @return NodeList + */ + public NodeList getNodeList(String expression) { + return (NodeList) evalXpath(expression, null, XPathConstants.NODESET); + } + + /** + * 获取String + * + * @param node 节点 + * @param expression 相对于node的路径 + * @return String + */ + public String getString(Object node, String expression) { + return (String) evalXpath(expression, node, XPathConstants.STRING); + } + + /** + * 获取 + * + * @param node 节点 + * @param expression 相对于node的路径 + * @return String + */ + public Boolean getBoolean(Object node, String expression) { + return (Boolean) evalXpath(expression, node, XPathConstants.BOOLEAN); + } + + /** + * 获取 + * + * @param node 节点 + * @param expression 相对于node的路径 + * @return {Number} + */ + public Number getNumber(Object node, String expression) { + return (Number) evalXpath(expression, node, XPathConstants.NUMBER); + } + + /** + * 获取某个节点 + * + * @param node 节点 + * @param expression 路径 + * @return {Node} + */ + public Node getNode(Object node, String expression) { + return (Node) evalXpath(expression, node, XPathConstants.NODE); + } + + /** + * 获取子节点 + * + * @param node 节点 + * @param expression 相对于node的路径 + * @return NodeList + */ + public NodeList getNodeList(Object node, String expression) { + return (NodeList) evalXpath(expression, node, XPathConstants.NODESET); + } + + /** + * 针对没有嵌套节点的简单处理 + * + * @return map集合 + */ + public Map toMap() { + Element root = doc.getDocumentElement(); + + // 将节点封装成map形式 + NodeList list = root.getChildNodes(); + Map params = new HashMap(list.getLength()); + for (int i = 0; i < list.getLength(); i++) { + Node node = list.item(i); + params.put(node.getNodeName(), node.getTextContent()); + } + // 含有空白符会生成一个#text参数 + params.remove("#text"); + return params; + } + + /** + * 内部类单例 + */ + private static class XmlHelperHolder { + private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + private static XPathFactory xPathFactory = XPathFactory.newInstance(); + } + +} diff --git a/src/main/java/com/yxt/pay/utils/XmlUtil.java b/src/main/java/com/yxt/pay/utils/XmlUtil.java new file mode 100644 index 0000000..533f62e --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/XmlUtil.java @@ -0,0 +1,201 @@ +package com.yxt.pay.utils; + + +import com.yxt.common.base.utils.StringUtils; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * xml相关的工具类 + * + * @author yang.y + */ +@SuppressWarnings("unchecked") +public class XmlUtil { + + /** + * xml字符串转换成bean对象 + * + * @param xmlStr xml字符串 + * @param clazz 待转换的class + * @return 转换后的对象 + */ + public static Object xmlStrToBean(String xmlStr, Class clazz) { + Object obj = null; + try { + // 将xml格式的数据转换成Map对象 + Map map = xmlStrToMap(xmlStr); + // 将map对象的数据转换成Bean对象 + obj = mapToBean(map, clazz); + } catch (Exception e) { + e.printStackTrace(); + } + return obj; + } + + /** + * 将xml格式的字符串转换成Map对象 + * + * @param xmlStr xml格式的字符串 + * @return Map对象 + * @throws Exception 异常 + */ + public static Map xmlStrToMap(String xmlStr) throws Exception { + if (StringUtils.isBlank(xmlStr)) { + return null; + } + Map map = new HashMap(); + // 将xml格式的字符串转换成Document对象 + Document doc = DocumentHelper.parseText(xmlStr); + // 获取根节点 + Element root = doc.getRootElement(); + // 获取根节点下的所有元素 + List children = root.elements(); + // 循环所有子元素 + if (children != null && children.size() > 0) { + for (int i = 0; i < children.size(); i++) { + Element child = (Element) children.get(i); + map.put(child.getName(), child.getTextTrim()); + } + } + return map; + } + + /** + * 将xml格式字符串转换成Bean对象 + * 多级子节点递归遍历 + * + * @param xmlStr + * @param clazz + * @return + * @throws Exception + */ + public static Object xmlStrToJavaBean(String xmlStr, Class clazz) { + if (StringUtils.isBlank(xmlStr)) { + return null; + } + Object obj = null; + Map map = new HashMap(); + // 将xml格式的字符串转换成Document对象 + Document doc; + try { + doc = DocumentHelper.parseText(xmlStr); + + // 获取根节点 + Element root = doc.getRootElement(); + map = elementToMap(root, map); + // 将map对象的数据转换成Bean对象 + obj = mapToBean(map, clazz); + } catch (Exception e) { + e.printStackTrace(); + } + return obj; + } + + /** + * 递归遍历xml子节点,转换Map + * + * @param element + * @param map + * @return + */ + public static Map elementToMap(Element element, Map map) { + if (element == null || map == null) + return null; + List children = element.elements(); + if (children != null && children.size() > 0) { + for (int i = 0; i < children.size(); i++) { + Element child = (Element) children.get(i); + if (child.elements() != null && child.elements().size() > 0) + elementToMap(child, map); + else + map.put(child.getName(), child.getTextTrim()); + } + } + return map; + } + + /** + * 将Map对象通过反射机制转换成Bean对象 + * + * @param map 存放数据的map对象 + * @param clazz 待转换的class + * @return 转换后的Bean对象 + * @throws Exception 异常 + */ + public static Object mapToBean(Map map, Class clazz) throws Exception { + Object obj = clazz.newInstance(); + if (map != null && map.size() > 0) { + for (Map.Entry entry : map.entrySet()) { + String propertyName = entry.getKey(); + Object value = entry.getValue(); + String setMethodName = "set" + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); + Field field = getClassField(clazz, propertyName); + if (field != null) { + Class fieldTypeClass = field.getType(); + value = convertValType(value, fieldTypeClass); + clazz.getMethod(setMethodName, field.getType()).invoke(obj, value); + } + } + } + return obj; + } + + /** + * 将Object类型的值,转换成bean对象属性里对应的类型值 + * + * @param value Object对象值 + * @param fieldTypeClass 属性的类型 + * @return 转换后的值 + */ + private static Object convertValType(Object value, Class fieldTypeClass) { + Object retVal = null; + if (Long.class.getName().equals(fieldTypeClass.getName()) + || long.class.getName().equals(fieldTypeClass.getName())) { + retVal = Long.parseLong(value.toString()); + } else if (Integer.class.getName().equals(fieldTypeClass.getName()) + || int.class.getName().equals(fieldTypeClass.getName())) { + retVal = Integer.parseInt(value.toString()); + } else if (Float.class.getName().equals(fieldTypeClass.getName()) + || float.class.getName().equals(fieldTypeClass.getName())) { + retVal = Float.parseFloat(value.toString()); + } else if (Double.class.getName().equals(fieldTypeClass.getName()) + || double.class.getName().equals(fieldTypeClass.getName())) { + retVal = Double.parseDouble(value.toString()); + } else { + retVal = value; + } + return retVal; + } + + /** + * 获取指定字段名称查找在class中的对应的Field对象(包括查找父类) + * + * @param clazz 指定的class + * @param fieldName 字段名称 + * @return Field对象 + */ + private static Field getClassField(Class clazz, String fieldName) { + if (Object.class.getName().equals(clazz.getName())) { + return null; + } + Field[] declaredFields = clazz.getDeclaredFields(); + for (Field field : declaredFields) { + if (field.getName().equals(fieldName)) { + return field; + } + } + + Class superClass = clazz.getSuperclass(); + if (superClass != null) {// 简单的递归一下 + return getClassField(superClass, fieldName); + } + return null; + } +} diff --git a/src/main/java/com/yxt/pay/utils/applet/MD5.java b/src/main/java/com/yxt/pay/utils/applet/MD5.java new file mode 100644 index 0000000..7d10192 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/applet/MD5.java @@ -0,0 +1,29 @@ +package com.yxt.pay.utils.applet; + +import java.security.MessageDigest; + +public class MD5 { + private MD5() { + } + + /* * 生成 MD5 + * + * @param data 待处理数据 + * @return MD5结果 + */ + public static String getMessageDigest(String data) { + StringBuilder sb = new StringBuilder(); + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] array = md.digest(data.getBytes("UTF-8")); + + for (byte item : array) { + sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3)); + } + } catch (Exception e) { + return null; + } + return sb.toString().toUpperCase(); + } + +} diff --git a/src/main/java/com/yxt/pay/utils/applet/WechatConfig.java b/src/main/java/com/yxt/pay/utils/applet/WechatConfig.java new file mode 100644 index 0000000..4f39398 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/applet/WechatConfig.java @@ -0,0 +1,39 @@ +package com.yxt.pay.utils.applet; + +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContexts; + +import javax.net.ssl.SSLContext; +import java.io.InputStream; +import java.security.KeyStore; + +@SuppressWarnings("deprecation") +public class WechatConfig { + + private static SSLConnectionSocketFactory sslcsf; + + public static SSLConnectionSocketFactory getSslcsf() { + if (null == sslcsf) { + setSsslcsf(); + } + return sslcsf; + } + + private static void setSsslcsf() { + try { + KeyStore keyStore = KeyStore.getInstance("PKCS12"); + Thread.currentThread().getContextClassLoader(); + InputStream instream = new WechatRefundApiResult().getClass().getResourceAsStream("certName"); + try { + keyStore.load(instream, "mchId".toCharArray()); + } finally { + instream.close(); + } + SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, "mchId".toCharArray()).build(); + sslcsf = new SSLConnectionSocketFactory(sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/yxt/pay/utils/applet/WechatRefundApiResult.java b/src/main/java/com/yxt/pay/utils/applet/WechatRefundApiResult.java new file mode 100644 index 0000000..fd3d399 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/applet/WechatRefundApiResult.java @@ -0,0 +1,214 @@ +package com.yxt.pay.utils.applet; + +public class WechatRefundApiResult { + private String return_code; + private String return_msg; + + private String result_code; + private String err_code; + private String err_code_des; + private String appid; + private String mch_id; + private String device_info; + private String nonce_str; + private String sign; + private String transaction_id; + private String out_trade_no; + private String out_refund_no; + private String refund_id; + private String refund_channel; + private String refund_fee; + private String settlement_refund_fee; + private String total_fee; + private String settlement_total_fee; + private String fee_type; + private String cash_fee; + private String cash_refund_fee; + private String refund_status; + + public String getRefund_status() { + return refund_status; + } + + public void setRefund_status(String refund_status) { + this.refund_status = refund_status; + } + + public String getReturn_code() { + return return_code; + } + + public void setReturn_code(String return_code) { + this.return_code = return_code; + } + + public String getReturn_msg() { + return return_msg; + } + + public void setReturn_msg(String return_msg) { + this.return_msg = return_msg; + } + + public String getResult_code() { + return result_code; + } + + public void setResult_code(String result_code) { + this.result_code = result_code; + } + + public String getErr_code() { + return err_code; + } + + public void setErr_code(String err_code) { + this.err_code = err_code; + } + + public String getErr_code_des() { + return err_code_des; + } + + public void setErr_code_des(String err_code_des) { + this.err_code_des = err_code_des; + } + + public String getAppid() { + return appid; + } + + public void setAppid(String appid) { + this.appid = appid; + } + + public String getMch_id() { + return mch_id; + } + + public void setMch_id(String mch_id) { + this.mch_id = mch_id; + } + + public String getDevice_info() { + return device_info; + } + + public void setDevice_info(String device_info) { + this.device_info = device_info; + } + + public String getNonce_str() { + return nonce_str; + } + + public void setNonce_str(String nonce_str) { + this.nonce_str = nonce_str; + } + + public String getSign() { + return sign; + } + + public void setSign(String sign) { + this.sign = sign; + } + + public String getTransaction_id() { + return transaction_id; + } + + public void setTransaction_id(String transaction_id) { + this.transaction_id = transaction_id; + } + + public String getOut_trade_no() { + return out_trade_no; + } + + public void setOut_trade_no(String out_trade_no) { + this.out_trade_no = out_trade_no; + } + + public String getOut_refund_no() { + return out_refund_no; + } + + public void setOut_refund_no(String out_refund_no) { + this.out_refund_no = out_refund_no; + } + + public String getRefund_id() { + return refund_id; + } + + public void setRefund_id(String refund_id) { + this.refund_id = refund_id; + } + + public String getRefund_channel() { + return refund_channel; + } + + public void setRefund_channel(String refund_channel) { + this.refund_channel = refund_channel; + } + + public String getRefund_fee() { + return refund_fee; + } + + public void setRefund_fee(String refund_fee) { + this.refund_fee = refund_fee; + } + + public String getSettlement_refund_fee() { + return settlement_refund_fee; + } + + public void setSettlement_refund_fee(String settlement_refund_fee) { + this.settlement_refund_fee = settlement_refund_fee; + } + + public String getTotal_fee() { + return total_fee; + } + + public void setTotal_fee(String total_fee) { + this.total_fee = total_fee; + } + + public String getSettlement_total_fee() { + return settlement_total_fee; + } + + public void setSettlement_total_fee(String settlement_total_fee) { + this.settlement_total_fee = settlement_total_fee; + } + + public String getFee_type() { + return fee_type; + } + + public void setFee_type(String fee_type) { + this.fee_type = fee_type; + } + + public String getCash_fee() { + return cash_fee; + } + + public void setCash_fee(String cash_fee) { + this.cash_fee = cash_fee; + } + + public String getCash_refund_fee() { + return cash_refund_fee; + } + + public void setCash_refund_fee(String cash_refund_fee) { + this.cash_refund_fee = cash_refund_fee; + } + + +} diff --git a/src/main/java/com/yxt/pay/utils/applet/WechatUtil.java b/src/main/java/com/yxt/pay/utils/applet/WechatUtil.java new file mode 100644 index 0000000..ed883f7 --- /dev/null +++ b/src/main/java/com/yxt/pay/utils/applet/WechatUtil.java @@ -0,0 +1,296 @@ +package com.yxt.pay.utils.applet; + +import com.yxt.pay.utils.CharUtil; +import com.yxt.pay.utils.MapUtils; +import com.yxt.pay.utils.XmlUtil; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.BasicHttpClientConnectionManager; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + *

Title: 微信退款工具类

+ *

Description: 微信退款工具类,通过充值客户端的不同初始化不同的工具类,得到相应微信退款相关的appid和muchid

+ * + * @author xubo + * @date 2017年6月6日 下午5:05:03 + */ +public class WechatUtil { + /** + * 充值客户端类型--微信公众号 + */ + public static Integer CLIENTTYPE_WX = 2; + /** + * 充值客户端类型--app + */ + public static Integer CLIENTTYPE_APP = 1; + private static Logger logger = LoggerFactory.getLogger(WechatUtil.class); + + /** + * 方法描述:微信退款逻辑 + * 创建时间:2017年4月12日 上午11:04:25 + * 作者: xubo + * + * @param + * @return + */ + /* public static WechatRefundApiResult wxRefund(String out_trade_no, Double orderMoney, Double refundMoney) { + //初始化请求微信服务器的配置信息包括appid密钥等 + //转换金钱格式 + BigDecimal bdOrderMoney = new BigDecimal(orderMoney, MathContext.DECIMAL32); + BigDecimal bdRefundMoney = new BigDecimal(refundMoney, MathContext.DECIMAL32); + //构建请求参数 + Map params = buildRequsetMapParam(out_trade_no, bdOrderMoney, bdRefundMoney); + String mapToXml = MapUtils.convertMap2Xml(params); + //请求微信 + String reponseXml = sendSSLPostToWx(mapToXml, WechatConfig.getSslcsf()); + WechatRefundApiResult result = (WechatRefundApiResult) XmlUtil.xmlStrToBean(reponseXml, WechatRefundApiResult.class); + return result; + }*/ + + /** + * 方法描述:得到请求微信退款请求的参数 + * 创建时间:2017年6月8日 上午11:27:02 + * 作者: xubo + * + * @param + * @return + */ + /* private static Map buildRequsetMapParam(String out_trade_no, BigDecimal bdOrderMoney, BigDecimal bdRefundMoney) { + Map params = new HashMap(); + //微信分配的公众账号ID(企业号corpid即为此appId) + params.put("appid", ResourceUtil.getConfigByName("wx.appId")); + //微信支付分配的商户号 + params.put("mch_id", ResourceUtil.getConfigByName("wx.mchId")); + //随机字符串,不长于32位。推荐随机数生成算法 + params.put("nonce_str", CharUtil.getRandomString(16)); + //商户侧传给微信的订单号 + params.put("out_trade_no", out_trade_no); + //商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔 + params.put("out_refund_no", getBundleId()); + //订单总金额,单位为分,只能为整数 + params.put("total_fee", bdOrderMoney.multiply(new BigDecimal(100)).intValue()); + //退款总金额,订单总金额,单位为分,只能为整数 + params.put("refund_fee", bdRefundMoney.multiply(new BigDecimal(100)).intValue()); + //操作员帐号, 默认为商户号 + params.put("op_user_id", ResourceUtil.getConfigByName("wx.mchId")); + //签名前必须要参数全部写在前面 + params.put("sign", arraySign(params, ResourceUtil.getConfigByName("wx.paySignKey"))); + return params; + }*/ + + /** + * ResourceUtil.getConfigByName("wx.refundUrl") + * 请求微信https + **/ + public static String sendSSLPostToWx(String mapToXml, SSLConnectionSocketFactory sslcsf, String refundUrl) { + logger.info("*******退款(WX Request:" + mapToXml); + HttpPost httPost = new HttpPost(refundUrl); + httPost.addHeader("Connection", "keep-alive"); + httPost.addHeader("Accept", "*/*"); + httPost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + httPost.addHeader("Host", "api.mch.weixin.qq.com"); + httPost.addHeader("X-Requested-With", "XMLHttpRequest"); + httPost.addHeader("Cache-Control", "max-age=0"); + httPost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); + httPost.setEntity(new StringEntity(mapToXml, "UTF-8")); + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(sslcsf).build(); + CloseableHttpResponse response = null; + try { + response = httpClient.execute(httPost); + HttpEntity entity = response.getEntity(); + String xmlStr = EntityUtils.toString(entity, "UTF-8"); + logger.info("*******退款(WX Response:" + xmlStr); + return xmlStr; + } catch (Exception e) { + logger.error(e.getMessage(), e); + return null; + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + } + + /** + * 支付交易ID + */ + public static String getBundleId() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + String tradeno = dateFormat.format(new Date()); + String str = "000000" + (int) (Math.random() * 1000000); + tradeno = tradeno + str.substring(str.length() - 6); + return tradeno; + } + + /** + * 方法描述:根据签名加密请求参数 + * 创建时间:2017年6月8日 上午11:28:52 + * 作者: xubo + * + * @param + * @return + */ + public static String arraySign(Map params, String paySignKey) { + boolean encode = false; + Set keysSet = params.keySet(); + Object[] keys = keysSet.toArray(); + Arrays.sort(keys); + StringBuffer temp = new StringBuffer(); + boolean first = true; + for (Object key : keys) { + if (first) { + first = false; + } else { + temp.append("&"); + } + temp.append(key).append("="); + Object value = params.get(key); + String valueString = ""; + if (null != value) { + valueString = value.toString(); + } + if (encode) { + try { + temp.append(URLEncoder.encode(valueString, "UTF-8")); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + } else { + temp.append(valueString); + } + } + temp.append("&key="); + temp.append(paySignKey); + System.out.println(temp.toString()); + String packageSign = MD5.getMessageDigest(temp.toString()); + return packageSign; + } + + /** + * 请求,只请求一次,不做重试 + * + * @param url + * @param data + * @return + * @throws Exception + */ + public static String requestOnce(final String url, String data) throws Exception { + BasicHttpClientConnectionManager connManager; + connManager = new BasicHttpClientConnectionManager( + RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.getSocketFactory()) + .register("https", SSLConnectionSocketFactory.getSocketFactory()) + .build(), + null, + null, + null + ); + + HttpClient httpClient = HttpClientBuilder.create() + .setConnectionManager(connManager) + .build(); + + HttpPost httpPost = new HttpPost(url); + + RequestConfig requestConfig = RequestConfig.custom() + .setSocketTimeout(5000) + .setConnectTimeout(5000) + .setConnectionRequestTimeout(10000).build(); + + httpPost.setConfig(requestConfig); + + StringEntity postEntity = new StringEntity(data, "UTF-8"); + httpPost.addHeader("Content-Type", "text/xml"); + httpPost.addHeader("User-Agent", "wxpay sdk java v1.0 " + "mchId"); + httpPost.setEntity(postEntity); + + HttpResponse httpResponse = httpClient.execute(httpPost); + HttpEntity httpEntity = httpResponse.getEntity(); + String reusltObj = EntityUtils.toString(httpEntity, "UTF-8"); + logger.info("请求结果:" + reusltObj); + return reusltObj; + + } + + /** + * 方法描述:微信查询退款逻辑 + * 创建时间:2017年4月12日 上午11:04:25 + * 作者: xubo + * + * @param + * @return + */ + + + public Map wxRefundquery(String out_trade_no, String out_refund_no) { + Map params = new HashMap(); + //微信分配的公众账号ID(企业号corpid即为此appId) + params.put("appid", "xx"); + //微信支付分配的商户号 + params.put("mch_id", "xx"); + //随机字符串,不长于32位。推荐随机数生成算法 + params.put("nonce_str", CharUtil.getRandomString(16)); + //商户侧传给微信的订单号 + params.put("out_trade_no", out_trade_no); + //签名前必须要参数全部写在前面 + //签名 + params.put("sign", arraySign(params, "wx.paySignKey")); + String mapToXml = MapUtils.convertMap2Xml(params); + HttpPost httPost = new HttpPost("refundqueryUrl"); + httPost.addHeader("Connection", "keep-alive"); + httPost.addHeader("Accept", "*/*"); + httPost.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + httPost.addHeader("Host", "api.mch.weixin.qq.com"); + httPost.addHeader("X-Requested-With", "XMLHttpRequest"); + httPost.addHeader("Cache-Control", "max-age=0"); + httPost.addHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0) "); + httPost.setEntity(new StringEntity(mapToXml, "UTF-8")); + CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(WechatConfig.getSslcsf()).build(); + CloseableHttpResponse response = null; + try { + response = httpClient.execute(httPost); + HttpEntity entity = response.getEntity(); + String xmlStr = EntityUtils.toString(entity, "UTF-8"); + System.out.println(xmlStr); + Map result = XmlUtil.xmlStrToMap(xmlStr);//.xmlStrToBean(xmlStr, WechatRefundApiResult.class); + return result; + //将信息保存到数据库 + } catch (Exception e) { + logger.error(e.getMessage(), e); + return null; + } finally { + try { + if (response != null) { + response.close(); + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + } +} diff --git a/src/main/resources/application-devv.yml b/src/main/resources/application-devv.yml new file mode 100644 index 0000000..726634a --- /dev/null +++ b/src/main/resources/application-devv.yml @@ -0,0 +1,30 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/yxt_pay?serverTimezone=GMT%2B8&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&nullCatalogMeansCurrent=true + username: root + password: root + cloud: + nacos: + discovery: + server-addr: 127.0.0.1:8848 + redis: + database: 3 # Redis数据库索引(默认为0) + host: 127.0.0.1 + jedis: + pool: + max-active: -1 #连接池最大连接数(使用负值表示没有限制) + max-idle: 8 #连接池中的最大空闲连接 + max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) + min-idle: 0 # 连接池中的最小空闲连接 + password: 123456 + port: 6379 + timeout: 0 # 连接超时时间(毫秒) + +image: + upload: + path: D:\\pay\\upload\\ + url: + prefix: http://192.168.1.120:8111/upload/ +domain: + urlPrex: http://192.168.0.111:7777 \ No newline at end of file diff --git a/src/main/resources/application-pro.yml b/src/main/resources/application-pro.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..e3e1c6d --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,62 @@ +spring: + application: + name: yxt_pay + profiles: + active: devv + messages: + # 国际化资源文件路径 + basename: i18n/messages + servlet: + #上传文件 + multipart: + max-file-size: 50MB + max-request-size: 100MB + devtools: + restart: + # 热部署开关 + enabled: true + mvc: + async: + request-timeout: 20000 + + + +server: + port: 7777 + max-http-header-size: 102400 + tomcat: + max-http-form-post-size: -1 +#mybatis +mybatis-plus: + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapper-locations: classpath*:**Mapper.xml + global-config: + refresh: true + db-config: + #定义生成ID的类型 + id-type: Auto + db-type: mysql + configuration: + map-underscore-to-camel-case: false + cache-enabled: true + call-setters-on-nulls: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +#hystrix的超时时间 +hystrix: + command: + default: + execution: + timeout: + enabled: true + isolation: + thread: + timeoutInMilliseconds: 60000 +#ribbon的超时时间 +ribbon: + ReadTimeout: 60000 + ConnectTimeout: 60000 + + + + diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt new file mode 100644 index 0000000..98e44c5 --- /dev/null +++ b/src/main/resources/banner.txt @@ -0,0 +1,13 @@ + ,----.. ____ + / / \ ,' , `. +| : : ,---. ,-+-,.' _ | ,---. ,---, +. | ;. / ' ,'\ ,-+-. ; , || ' ,'\ ,-+-. / | +. ; /--` / / | ,--.'|' | || ,---. / / | ,--.'|' | +; | ; . ; ,. :| | ,', | |,/ \ . ; ,. :| | ,"' | +| : | ' | |: :| | / | |--'/ / | ' | |: :| | / | | +. | '___' | .; :| : | | , . ' / | ' | .; :| | | | | +' ; : .'| : || : | |/ ' ; /| | : || | | |/ +' | '/ :\ \ / | | |`-' ' | / | \ \ / | | |--' +| : / `----' | ;/ | : | `----' | |/ + \ \ .' '---' \ \ / '---' + `---` `----' diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..4112207 --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,54 @@ + + + + + + + + + + %yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%green(%logger:%line) |%blue(%msg%n) + + + + + + + + + + + + + + + ${log.base}.log + + ${log.base}.%d{yyyyMMdd}.%i.log.zip + + + + 1MB + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} + -%msg%n + + + + + + + + + + + \ No newline at end of file