diff --git a/backend/lpg-user/pom.xml b/backend/lpg-user/pom.xml index c7cb5e5..c185be7 100644 --- a/backend/lpg-user/pom.xml +++ b/backend/lpg-user/pom.xml @@ -67,6 +67,37 @@ ${parent.version} + + + com.github.binarywang + weixin-java-pay + 4.1.0 + + + + com.github.binarywang + weixin-java-common + 4.1.0 + + + + com.github.binarywang + weixin-java-miniapp + 4.1.0 + + + + com.github.binarywang + weixin-java-mp + 4.1.0 + + + + com.github.binarywang + weixin-java-open + 4.1.0 + + diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/controller/PaymentController.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/controller/PaymentController.java new file mode 100644 index 0000000..b3d3140 --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/controller/PaymentController.java @@ -0,0 +1,41 @@ +package com.hotent.lpg.user.controller; + +import com.hotent.lpg.user.enums.PayTypeEnum; +import com.hotent.lpg.user.manager.PaymentManage; +import com.hotent.lpg.user.vo.ResultData; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/payment/v1/") +@AllArgsConstructor +@Validated +@Slf4j +public class PaymentController { + private final PaymentManage paymentManage; + + /** + * 预支付 + * @param ddid + * @return + */ + @PostMapping("/{ddid}") + public ResultData pay(@PathVariable("ddid") String ddid) { + String payType = PayTypeEnum.WX.getName(); // 默认微信 + return paymentManage.pay(ddid, payType); + } + + /** + * 支付回调 + * @return + */ + @PostMapping("/notify-order") + public String notifyOrder(@RequestBody String xmlData) { + log.warn("支付回调:{}", xmlData); + String res = paymentManage.notifyOrder(xmlData); + log.warn("处理状态:{}", res); + return res; + } +} diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/enums/PayTypeEnum.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/enums/PayTypeEnum.java new file mode 100644 index 0000000..62ef183 --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/enums/PayTypeEnum.java @@ -0,0 +1,11 @@ +package com.hotent.lpg.user.enums; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum PayTypeEnum { + WX("微信"); + + private String name; +} diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/enums/TradeTypeEnum.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/enums/TradeTypeEnum.java new file mode 100644 index 0000000..a1c9f11 --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/enums/TradeTypeEnum.java @@ -0,0 +1,15 @@ +package com.hotent.lpg.user.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum TradeTypeEnum { + APP("APP"), + NATIVE("NATIVE"), + JSAPI("JSAPI"), + MWEB("MWEB"); + + private String code; +} diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/manager/PaymentManage.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/manager/PaymentManage.java new file mode 100644 index 0000000..1ca6e56 --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/manager/PaymentManage.java @@ -0,0 +1,10 @@ +package com.hotent.lpg.user.manager; + + +import com.hotent.lpg.user.vo.ResultData; + +public interface PaymentManage { + ResultData pay(String ddid, String payType); + + String notifyOrder(String xmlData); +} diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/manager/impl/PaymentManageImpl.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/manager/impl/PaymentManageImpl.java new file mode 100644 index 0000000..bc147ae --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/manager/impl/PaymentManageImpl.java @@ -0,0 +1,138 @@ +package com.hotent.lpg.user.manager.impl; + +import cn.hutool.core.util.RandomUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse; +import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; +import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.github.binarywang.wxpay.service.WxPayService; + +import com.hotent.base.util.BeanUtils; +import com.hotent.lpg.common.model.WDd; +import com.hotent.lpg.user.dao.DdDao; +import com.hotent.lpg.user.enums.PayTypeEnum; +import com.hotent.lpg.user.manager.PaymentManage; +import com.hotent.lpg.user.util.LocalDateTimeUtils; +import com.hotent.lpg.user.util.WxPayConfiguration; +import com.hotent.lpg.user.vo.ResultData; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Service +@Transactional +@AllArgsConstructor +@Slf4j +public class PaymentManageImpl implements PaymentManage { + private final DdDao ddDao; + + @Override + public ResultData pay(String ddid, String payType) { + + WDd wDd = ddDao.selectById(ddid); + if (BeanUtils.isEmpty(wDd)) { + return ResultData.error("订单不存在"); + } + if (payType.equals(PayTypeEnum.WX.getName())) { + wDd.setFZffs(PayTypeEnum.WX.getName()); + return wxPay(wDd); + } + return ResultData.error("支付方式错误"); + } + + + private ResultData wxPay(WDd wDd) { + try { + String tradeType = wDd.getFZffs(); + if (StrUtil.isBlank(tradeType)) { + return ResultData.error("支付方式错误"); + } + + // 防止重新启动支付时报“商户订单号重复”,前面增加8位随机数,product_id最大32位 + String outTradeNo = RandomUtil.randomString(8) + "_" + wDd.getId(); + String body = wDd.getFDddh(); + body = body.length() > 40 ? body.substring(0,39) : body; + + WxPayUnifiedOrderRequest wxPayUnifiedOrderRequest = new WxPayUnifiedOrderRequest(); + wxPayUnifiedOrderRequest.setAppid(WxPayConfiguration.appId); + wxPayUnifiedOrderRequest.setOpenid(null); + wxPayUnifiedOrderRequest.setBody(body); + wxPayUnifiedOrderRequest.setOutTradeNo(outTradeNo); + wxPayUnifiedOrderRequest.setTotalFee(wDd.getFDdje().multiply(new BigDecimal(100)).intValue()); + wxPayUnifiedOrderRequest.setTradeType(tradeType); + + JSONObject scene_info = new JSONObject(); + scene_info.put("id", "LPG"); + scene_info.put("name", "燃气"); + wxPayUnifiedOrderRequest.setSceneInfo(scene_info.toString()); + wxPayUnifiedOrderRequest.setNotifyUrl("https://zaoyin.peony.cn/server/hzys/payment/v1/notify-order"); // 支付回调地址,开放不用登录 + wxPayUnifiedOrderRequest.setSpbillCreateIp("127.0.0.1"); + + // trade_type=APP时 + if("APP".equals(wxPayUnifiedOrderRequest.getTradeType())){ + + } + // trade_type=NATIVE时 + if("NATIVE".equals(wxPayUnifiedOrderRequest.getTradeType())){ + wxPayUnifiedOrderRequest.setProductId(wxPayUnifiedOrderRequest.getOutTradeNo()); + } + if ("JSAPI".equals(wxPayUnifiedOrderRequest.getTradeType())) { + + } + if ("MWEB".equals(wxPayUnifiedOrderRequest.getTradeType())) { + + } + WxPayService wxPayService = WxPayConfiguration.getPayService(); + ddDao.updateById(wDd); + return ResultData.success(wxPayService.createOrder(wxPayUnifiedOrderRequest)); + } catch (WxPayException e) { + if("INVALID_REQUEST".equals(e.getErrCode())){ + return ResultData.error("订单号重复,请重新下单"); + } + e.printStackTrace(); + return ResultData.error(e.getReturnMsg() + "" + e.getCustomErrorMsg() + "" + e.getErrCodeDes()); + } + } + + + @Override + public String notifyOrder(String xmlData) { + WxPayService wxPayService = WxPayConfiguration.getPayService(); + try { + WxPayOrderNotifyResult notifyResult = wxPayService.parseOrderNotifyResult(xmlData); + String ddid = notifyResult.getOutTradeNo().split("_")[1]; + WDd wDd = ddDao.selectById(ddid); + if (null == wDd) { + return WxPayNotifyResponse.fail("订单不存在"); + } + BigDecimal payPrice = wDd.getFDdje(); + if (payPrice.multiply(new BigDecimal(100)).intValue() != notifyResult.getTotalFee()) { + return WxPayNotifyResponse.fail("付款金额与订单金额不等"); + } + + String timeEnd = notifyResult.getTimeEnd(); + LocalDateTime paymentTime = LocalDateTimeUtils.parse(timeEnd); +// wDdb.setfFksj(paymentTime); +// wDdb.setfZfdh(notifyResult.getTransactionId()); +// wDdb.setfSffk("是"); +// wDdb.setfDdzt("已付款"); +// wDdb.setfPtddzt("待审批"); +// wDdbDao.updateById(wDdb); +// +// if (StrUtil.isNotBlank(wDdb.getfZpid())) { +// WZp wZp = wZpDao.selectById(wDdb.getfZpid()); +// wZp.setfZt("定制中"); +// wZpDao.updateById(wZp); +// } + return WxPayNotifyResponse.success("成功"); + } catch (WxPayException e) { + return WxPayNotifyResponse.fail(e.getErrCodeDes()); + } + } +} diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/util/LocalDateTimeUtils.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/util/LocalDateTimeUtils.java new file mode 100644 index 0000000..78132da --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/util/LocalDateTimeUtils.java @@ -0,0 +1,92 @@ +package com.hotent.lpg.user.util; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.temporal.ChronoField; + +/** + * LocalDateTime时间工具 + */ +public class LocalDateTimeUtils { + + public static final String YYYY = "yyyy"; + public static final String YYYYMM = "yyyyMM"; + public static final String YYYYMMDD = "yyyyMMdd"; + public static final String YYYYMMDDHH = "yyyyMMddHH"; + public static final String YYYYMMDDHHMM = "yyyyMMddHHmm"; + public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + public static final String YYYY_MM = "yyyy-MM"; + public static final String YYYY_MM_DD = "yyyy-MM-dd"; + public static final String YYYY_MM_DD_HH = "yyyy-MM-dd HH"; + public static final String YYYY_MM_DD_HH_MM = "yyyy-MM-dd HH:mm"; + public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static final String BASE_TIME_FORMAT = "[yyyyMMddHHmmss][yyyyMMddHHmm][yyyyMMddHH][yyyyMMdd][yyyyMM][yyyy][[-][/][.]MM][[-][/][.]dd][ ][HH][[:][.]mm][[:][.]ss][[:][.]SSS]"; + /** + * 【推荐】解析常用时间字符串,支持,并不局限于以下形式: + * [yyyy][yyyy-MM][yyyy-MM-dd][yyyy-MM-dd HH][yyyy-MM-dd HH:mm][yyyy-MM-dd HH:mm:ss][yyyy-MM-dd HH:mm:ss:SSS] + * [yyyy][yyyy/MM][yyyy/MM/dd][yyyy/MM/dd HH][yyyy/MM/dd HH:mm][yyyy/MM/dd HH:mm:ss][yyyy/MM/dd HH:mm:ss:SSS] + * [yyyy][yyyy.MM][yyyy.MM.dd][yyyy.MM.dd HH][yyyy.MM.dd HH.mm][yyyy.MM.dd HH.mm.ss][yyyy.MM.dd HH.mm.ss.SSS] + * [yyyy][yyyyMM][yyyyMMdd][yyyyMMddHH][yyyyMMddHHmm][yyyyMMddHHmmss] + * [MM-dd] + * 不支持yyyyMMddHHmmssSSS,因为本身DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS")就不支持这个形式 + * + * @param timeString + * @return + */ + public static LocalDateTime parse(String timeString) { + return LocalDateTime.parse(timeString, getDateTimeFormatterByPattern(BASE_TIME_FORMAT)); + } + + /** + * 根据传进来的pattern返回LocalDateTime,自动补齐 + * + * @param timeString + * @param pattern + * @return + */ + public static LocalDateTime parseByPattern(String timeString, String pattern) { + return LocalDateTime.parse(timeString, getDateTimeFormatterByPattern(pattern)); + } + + private static DateTimeFormatter getDateTimeFormatterByPattern(String pattern) { + return new DateTimeFormatterBuilder() + .appendPattern(pattern) + .parseDefaulting(ChronoField.YEAR_OF_ERA, LocalDateTime.now().getYear()) + .parseDefaulting(ChronoField.MONTH_OF_YEAR, LocalDateTime.now().getMonthValue()) + .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .parseDefaulting(ChronoField.NANO_OF_SECOND, 0) + .toFormatter(); + } + + /** + * 将timestamp转为LocalDateTime + * @param timestamp + * @return + */ + public static LocalDateTime timestamToDatetime(long timestamp){ + Instant instant = Instant.ofEpochMilli(timestamp); + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } + + /** + * 将LocalDataTime转为timestamp + * @param ldt + * @return + */ + public static long datatimeToTimestamp(LocalDateTime ldt){ + ZoneId zone = ZoneId.systemDefault(); + return ldt.atZone(zone).toInstant().toEpochMilli(); + } + + public static void main(String[] args) { + long timeStamp = 1382694957000l; + System.out.println(timestamToDatetime(timeStamp)); + } +} diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/util/WxPayConfiguration.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/util/WxPayConfiguration.java new file mode 100644 index 0000000..6c9f220 --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/util/WxPayConfiguration.java @@ -0,0 +1,63 @@ +package com.hotent.lpg.user.util; + +import com.github.binarywang.wxpay.config.WxPayConfig; +import com.github.binarywang.wxpay.service.WxPayService; +import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; + +public class WxPayConfiguration { +// public static final String appId = "wxf41ff374261f39cc"; // 该配置用的是景易游的微信支付信息 + public static final String appId = "wxde6f31c15afb357d"; + + public static WxPayService getPayService() { + // 该配置用的是景易游的微信支付信息 +// WxPayConfig wxPayConfig = new WxPayConfig(); +// wxPayConfig.setAppId("wxf41ff374261f39cc"); +// wxPayConfig.setMchId("1634368190"); +// wxPayConfig.setMchKey("Peony8228Peony8228Peony8228Peony"); +//// wxPayConfig.setKeyPath("/mnt/install/joolun-file/cert/1/apiclient_cert8e55f27b-6d67-4b91-a16e-e9f6ef96ca5b11027923844080776299.p12"); +//// wxPayConfig.setPrivateKeyPath("/mnt/install/joolun-file/cert/1/apiclient_key571e2a52-a9f8-46c5-9d4d-ed4b08d0749c4331609001879503591.pem"); +//// wxPayConfig.setPrivateCertPath("/mnt/install/joolun-file/cert/1/apiclient_cert13b610c6-4005-4189-8a61-85fab73fa42017841178972810569411.pem"); +// wxPayConfig.setCertSerialNo("55ED43DE3CC79DD045B92577A8FB2E95AACE8BEF"); +// wxPayConfig.setApiV3Key("Peony8228Peony8228Peony8228Peony"); +// // 可以指定是否使用沙箱环境 +// wxPayConfig.setUseSandboxEnv(false); + + + /** + * 商户简称: + * 牡丹集团宁安智慧工程中心 + * 企业名称: + * 北京牡丹电子集团有限责任公司宁安智慧工程中心 + * 登录账号 + * 1556251121 (本账号) + * + * 账号角色 + * Administrator + * 创建时间 + * 2019年9月19日 09:29:32 + * 绑定微信号 + * c*****21更换绑定 + * 姓 名 + * *际 + * 联系手机 + * 158******80 + * 联系邮箱 + * b*********n + */ + WxPayConfig wxPayConfig = new WxPayConfig(); + wxPayConfig.setAppId("wxde6f31c15afb357d"); // appId(应用id)(微信支付商户平台获取) + wxPayConfig.setMchId("1556251121"); // 商户号(微信支付商户平台获取) + // 这里的密钥应该使用APIv2密码,v3密钥此jar包不支持签名方式 + wxPayConfig.setMchKey("Peony8228Peony8228Peony8228Peony"); // 商户密钥(APIv2密钥)(微信支付商户平台-》账户中心-》API安全中获取APIv3密钥) +// wxPayConfig.setKeyPath("/mnt/install/joolun-file/cert/1/apiclient_cert8e55f27b-6d67-4b91-a16e-e9f6ef96ca5b11027923844080776299.p12"); +// wxPayConfig.setPrivateKeyPath("/mnt/install/joolun-file/cert/1/apiclient_key571e2a52-a9f8-46c5-9d4d-ed4b08d0749c4331609001879503591.pem"); +// wxPayConfig.setPrivateCertPath("/mnt/install/joolun-file/cert/1/apiclient_cert13b610c6-4005-4189-8a61-85fab73fa42017841178972810569411.pem"); + wxPayConfig.setCertSerialNo("736846A3CE4CBAC36DD5E81EB08F89240E4A05E1"); // 商户证书序列号(微信支付商户平台-》账户中心-》API安全中申请API证书) + wxPayConfig.setApiV3Key("Peony8228Peony8228Peony8228Peony"); // 商户密钥(APIv2密钥)(微信支付商户平台-》账户中心-》API安全中获取APIv3密钥) + // 可以指定是否使用沙箱环境 + wxPayConfig.setUseSandboxEnv(false); + WxPayService wxPayService = new WxPayServiceImpl(); + wxPayService.setConfig(wxPayConfig); + return wxPayService; + } +} diff --git a/backend/lpg-user/src/main/java/com/hotent/lpg/user/vo/ResultData.java b/backend/lpg-user/src/main/java/com/hotent/lpg/user/vo/ResultData.java new file mode 100644 index 0000000..93b14bb --- /dev/null +++ b/backend/lpg-user/src/main/java/com/hotent/lpg/user/vo/ResultData.java @@ -0,0 +1,42 @@ +package com.hotent.lpg.user.vo; + +import lombok.Data; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.util.HashMap; + +/** + * @projectName: service + * @package: com.hotent.common + * @className: ResultData + * @author: HuangLang + * @description: 接口统一返回对象 + * @date: 2021-12-15 13:40 + */ +@Data +@Accessors(chain = true) +public class ResultData implements Serializable { + + private String code ="200"; + private String msg ="success"; + private Object data =new HashMap<>(); + + public static ResultData error(Exception e){ + return new ResultData().setCode("500").setMsg(e.getMessage()); + } + + public static ResultData error(String message){ + return new ResultData().setCode("500").setMsg(message); + } + + public static ResultData success(){ + return new ResultData(); + } + + public static ResultData success(Object o){ + return new ResultData().setData(o); + } + + +} -- libgit2 0.21.2