1个文件已删除
14个文件已修改
27个文件已添加
| | |
| | | // strategy.setTablePrefix(pc.getModuleName() + ""); |
| | | // strategy.setLikeTable(new LikeTable("room")); |
| | | //strategy.setLikeTable(new LikeTable("member")); |
| | | strategy.setLikeTable(new LikeTable("t_sys_ai_config"));// 生成表名 |
| | | strategy.setLikeTable(new LikeTable("t_sys_pay_record"));// 生成表名 |
| | | // strategy.setLikeTable(new LikeTable("t_hotel"));// 生成表名 |
| | | // strategy.setLikeTable(new LikeTable("t_scan_message"));// 生成表名 |
| | | // strategy.setNotLikeTable(new LikeTable("hotel_info"));// 不生成表名 |
| | |
| | | |
| | | /** |
| | | * <p> |
| | | * 系统配置-协议管理-其他设置 前端控制器 |
| | | * 系统配置-协议管理 前端控制器 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-08-20 |
| | | */ |
| | | @Api(tags = "系统配置-协议管理-其他设置") |
| | | @Api(tags = "系统配置-协议管理") |
| | | @RestController |
| | | @RequestMapping("/t-sys-config") |
| | | public class TSysConfigController { |
| | |
| | | this.sysConfigService = sysConfigService; |
| | | } |
| | | /** |
| | | * 修改系统配置-协议管理-其他设置 |
| | | * 修改系统配置-协议管理 |
| | | */ |
| | | @Log(title = "系统配置-协议管理-其他设置-修改系统配置-协议管理-其他设置管理", businessType = BusinessType.UPDATE) |
| | | @ApiOperation(value = "修改系统配置-协议管理-其他设置管理") |
| | | @Log(title = "系统配置-协议管理-修改系统配置-协议管理管理", businessType = BusinessType.UPDATE) |
| | | @ApiOperation(value = "修改系统配置-协议管理管理") |
| | | @PostMapping(value = "/update") |
| | | public R<Boolean> update(@Validated @RequestBody TSysConfig dto) { |
| | | return R.ok(sysConfigService.updateById(dto)); |
| | | } |
| | | |
| | | /** |
| | | * 查看系统配置-协议管理-其他设置详情 |
| | | * 查看系统配置-协议管理详情 |
| | | */ |
| | | @ApiOperation(value = "查看系统配置-协议管理-其他设置详情",notes = "配置类型 1=用户协议 2=隐私协议 3=其他设置") |
| | | @ApiOperation(value = "查看系统配置-协议管理详情",notes = "配置类型 1=用户协议 2=隐私协议") |
| | | @GetMapping(value = "/getDetailById") |
| | | public R<TSysConfig> getDetailById(@RequestParam Integer configType) { |
| | | TSysConfig sysConfig = sysConfigService.getOne(Wrappers.<TSysConfig>lambdaQuery().eq(TSysConfig::getConfigType, configType) |
| | |
| | | @ApiOperation(value = "添加用户检测信息管理") |
| | | @PostMapping(value = "/add") |
| | | public R<Boolean> add(@Validated @RequestBody TSysInspection dto) { |
| | | dto.setIsPay(1); |
| | | return R.ok(sysInspectionService.save(dto)); |
| | | } |
| | | |
New file |
| | |
| | | package com.ruoyi.web.controller.api; |
| | | |
| | | |
| | | import com.ruoyi.common.annotation.Log; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.enums.BusinessType; |
| | | import com.ruoyi.system.model.TSysOtherConfig; |
| | | import com.ruoyi.system.service.TSysOtherConfigService; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | /** |
| | | * <p> |
| | | * 其他设置 前端控制器 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | @Api(tags = "其他设置") |
| | | @RestController |
| | | @RequestMapping("/t-sys-other-config") |
| | | public class TSysOtherConfigController { |
| | | private final TSysOtherConfigService sysOtherConfigService; |
| | | @Autowired |
| | | public TSysOtherConfigController(TSysOtherConfigService sysOtherConfigService) { |
| | | this.sysOtherConfigService = sysOtherConfigService; |
| | | } |
| | | /** |
| | | * 修改其他设置 |
| | | */ |
| | | @Log(title = "其他设置-修改其他设置管理", businessType = BusinessType.UPDATE) |
| | | @ApiOperation(value = "修改其他设置管理") |
| | | @PostMapping(value = "/update") |
| | | public R<Boolean> update(@Validated @RequestBody TSysOtherConfig dto) { |
| | | return R.ok(sysOtherConfigService.updateById(dto)); |
| | | } |
| | | |
| | | /** |
| | | * 查看其他设置详情 |
| | | */ |
| | | @ApiOperation(value = "查看其他设置详情") |
| | | @GetMapping(value = "/getDetailById") |
| | | public R<TSysOtherConfig> getDetailById() { |
| | | TSysOtherConfig sysConfig = sysOtherConfigService.getById(1); |
| | | return R.ok(sysConfig); |
| | | } |
| | | } |
| | | |
| | |
| | | //package com.ruoyi.web.controller.task; |
| | | // |
| | | // |
| | | //import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | //import com.ruoyi.common.utils.SmsUtil; |
| | | //import com.ruoyi.system.mapper.TBillMapper; |
| | | //import com.ruoyi.system.model.TBill; |
| | | //import com.ruoyi.system.model.TContract; |
| | | //import com.ruoyi.system.model.TContractRentType; |
| | | //import com.ruoyi.system.service.TBillService; |
| | | //import com.ruoyi.system.service.TContractRentTypeService; |
| | | //import com.ruoyi.system.service.TContractService; |
| | | //import org.springframework.beans.factory.annotation.Autowired; |
| | | //import org.springframework.scheduling.annotation.Scheduled; |
| | | //import org.springframework.stereotype.Component; |
| | | // |
| | | //import javax.annotation.Resource; |
| | | //import java.math.BigDecimal; |
| | | //import java.time.LocalDate; |
| | | //import java.time.LocalDateTime; |
| | | //import java.time.LocalTime; |
| | | //import java.time.ZoneId; |
| | | //import java.time.temporal.ChronoUnit; |
| | | //import java.time.temporal.TemporalAdjusters; |
| | | //import java.util.ArrayList; |
| | | //import java.util.Date; |
| | | //import java.util.List; |
| | | //import java.util.Random; |
| | | //import java.util.stream.Collectors; |
| | | // |
| | | ///** |
| | | // * @author zhibing.pu |
| | | // * @date 2023/7/11 8:39 |
| | | // */ |
| | | //@Component |
| | | //public class TaskUtil { |
| | | // @Autowired |
| | | // private TContractService contractService; |
| | | // @Autowired |
| | | // private TBillMapper billMapper; |
| | | // // 用于更新违约金账单 |
| | | // // 每分钟执行一次的定时任务 |
| | | // |
| | | // @Scheduled(cron = "0 * * * * ?") |
| | | // public void dayOfProportionBill() { |
| | | // try { |
| | | // // 查询所有未缴费账单 |
| | | // List<TBill> list = billMapper.selectList(new LambdaQueryWrapper<TBill>().eq(TBill::getPayFeesStatus, 1) |
| | | // .le(TBill::getPayableFeesTime,LocalDate.now())); |
| | | // for (TBill tBill : list) { |
| | | // tBill.setPayFeesStatus("4"); |
| | | // TContract contract = contractService.getById(tBill.getContractId()); |
| | | // LocalDate payableFeesTime = tBill.getPayableFeesTime(); |
| | | // // 将LocalDate转化为LocalDateTime |
| | | // LocalDateTime payableFeesTime1 = LocalDateTime.of(payableFeesTime, LocalTime.of(0, 0, 0)); |
| | | // LocalDateTime now = LocalDateTime.now(); |
| | | // // 计算两个时间相差多少个小时 |
| | | // long hours = ChronoUnit.HOURS.between(payableFeesTime1, now); |
| | | // long l = hours / 24; |
| | | // if (l>=3){ |
| | | // // 违约金比例 |
| | | // BigDecimal proportion = contract.getProportion(); |
| | | // // 按每天 待缴费金额 * XX% 增加违约金费用 |
| | | // if (tBill.getOutstandingMoney().compareTo(new BigDecimal("0"))==0){ |
| | | // tBill.setPayFeesStatus("3"); |
| | | // billMapper.updateById(tBill); |
| | | // continue; |
| | | // } |
| | | // BigDecimal money = tBill.getOutstandingMoney().multiply(new BigDecimal(100).add(proportion)).divide(new BigDecimal(100),2, BigDecimal.ROUND_DOWN); |
| | | // tBill.setOverDays((int) l); |
| | | // tBill.setPayableFeesPenalty((tBill.getPayableFeesPenalty()!=null?tBill.getPayableFeesPenalty():BigDecimal.ZERO).add(money)); |
| | | // tBill.setOutstandingMoney(money); |
| | | // billMapper.updateById(tBill); |
| | | // |
| | | // } |
| | | // } |
| | | // } catch (Exception e) { |
| | | // e.printStackTrace(); |
| | | // } |
| | | // } |
| | | // |
| | | // |
| | | //} |
| | | package com.ruoyi.web.controller.task; |
| | | |
| | | import org.springframework.scheduling.annotation.Scheduled; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @date 2023/7/11 8:39 |
| | | */ |
| | | @Component |
| | | public class TaskUtil { |
| | | |
| | | @Scheduled(cron = "0 0 0 * * ?") |
| | | public void dayOfProportionBill() { |
| | | try { |
| | | |
| | | |
| | | |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | } |
| | | } |
| | | |
| | | |
| | | } |
| | |
| | | package com.ruoyi.web.controller.api; |
| | | |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.redis.service.RedisService; |
| | | import com.ruoyi.framework.web.service.SysLoginService; |
| | | import com.ruoyi.framework.web.service.TokenService; |
| | | import com.ruoyi.system.model.TSysAiConfig; |
| | | import com.ruoyi.system.model.TSysInspection; |
| | | import com.ruoyi.system.service.TSysAiConfigService; |
| | | import com.ruoyi.system.service.TSysAppUserService; |
| | | import com.ruoyi.system.service.TSysInspectionService; |
| | | import com.ruoyi.system.utils.util.AesSimpleUtil; |
| | | import com.ruoyi.system.utils.util.RsaSimpleUtil; |
| | | import com.ruoyi.system.utils.wx.model.WeixinProperties; |
| | |
| | | |
| | | @Autowired |
| | | private TSysAiConfigService sysAiConfigService; |
| | | @Autowired |
| | | private TSysInspectionService sysInspectionService; |
| | | |
| | | @PostMapping("/reportReturn") |
| | | public String reportReturn(String encryptedJson, String signEncryptedJson) throws Exception { |
| | |
| | | |
| | | // 1. 先解密 |
| | | String source = AesSimpleUtil.decrypt(encryptedJson, aesKey); |
| | | System.out.println("AI问诊回调数据=====:"+ source); |
| | | JSONObject jsonObject = JSONObject.parseObject(source); |
| | | |
| | | // 2. 再验证签名 |
| | |
| | | return "error"; |
| | | } |
| | | // 3. 验证成功,则仅仅业务保存并返回success字符串 |
| | | /** |
| | | * { |
| | | * "returnType": 1, |
| | | * "name": "张三", |
| | | * "phone": "13344445555", |
| | | * "email": null, |
| | | * "sex": 1, |
| | | * "age": 42, |
| | | * "time": "2023-03-08 08:23:13", |
| | | * "pdf": "https://labelsys-images.oss-cn-hangzhou.aliyuncs.com/tongueFile/check/2023/03/87d76220-085e-4d2e-9fd7-9f5a01da1940.pdf", |
| | | * "healthIndex": 100.0, |
| | | * "constitutionNames": "气虚", |
| | | * "symptomName": "胃气虚证", |
| | | * "thirdId": "13344445555", |
| | | * "tongueFeature": "正常人舌色,舌质淡红润泽。舌苔呈现白色。", |
| | | * "faceFeature": "面部颜色暗沉。" |
| | | * } |
| | | */ |
| | | |
| | | Integer returnType = jsonObject.getInteger("returnType"); |
| | | String name = jsonObject.getString("name"); |
| | | String phone = jsonObject.getString("phone"); |
| | | String email = jsonObject.getString("email"); |
| | | Integer sex = jsonObject.getInteger("sex"); |
| | | Integer age = jsonObject.getInteger("age"); |
| | | String time = jsonObject.getString("time"); |
| | | String pdf = jsonObject.getString("pdf"); |
| | | Double healthIndex = jsonObject.getDouble("healthIndex"); |
| | | String constitutionNames = jsonObject.getString("constitutionNames"); |
| | | String symptomName = jsonObject.getString("symptomName"); |
| | | String thirdId = jsonObject.getString("thirdId"); |
| | | String tongueFeature = jsonObject.getString("tongueFeature"); |
| | | String faceFeature = jsonObject.getString("faceFeature"); |
| | | |
| | | if(returnType == 2){ |
| | | log.info("用户["+phone+"]检测失败"); |
| | | } |
| | | |
| | | long count = sysInspectionService.count(Wrappers.lambdaQuery(TSysInspection.class) |
| | | .eq(TSysInspection::getPersonPhone, phone) |
| | | .eq(TSysInspection::getCheckTime, time)); |
| | | if (count == 0) { |
| | | TSysInspection sysInspection = new TSysInspection(); |
| | | sysInspection.setPersonName(name); |
| | | sysInspection.setPersonPhone(phone); |
| | | sysInspection.setPersonSex(sex); |
| | | sysInspection.setPersonAge(age); |
| | | sysInspection.setCheckTime(time); |
| | | sysInspection.setPdfUrl(pdf); |
| | | sysInspection.setHealthIndex(healthIndex); |
| | | sysInspection.setConstitutionNames(constitutionNames); |
| | | sysInspection.setSymptomName(symptomName); |
| | | sysInspection.setTongueFeature(tongueFeature); |
| | | sysInspection.setFaceFeature(faceFeature); |
| | | sysInspection.setAppUserId(thirdId); |
| | | sysInspection.setInspectionType(2); |
| | | sysInspection.setIsPay(0); |
| | | sysInspectionService.save(sysInspection); |
| | | } |
| | | |
| | | return "success"; |
| | | } |
| | | |
| | |
| | | package com.ruoyi.web.controller.api; |
| | | |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.ruoyi.common.annotation.Log; |
| | | import com.ruoyi.common.basic.PageInfo; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.enums.BusinessType; |
| | | import com.ruoyi.common.utils.DateUtils; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import com.ruoyi.framework.web.service.TokenService; |
| | | import com.ruoyi.system.model.TSysAppUser; |
| | | import com.ruoyi.system.model.TSysInspection; |
| | | import com.ruoyi.system.query.TSysInspectionQuery; |
| | | import com.ruoyi.system.service.TSysAppUserService; |
| | | import com.ruoyi.system.service.TSysInspectionService; |
| | | import com.ruoyi.system.utils.util.AIUtil; |
| | | import com.ruoyi.system.vo.TSysInspectionVO; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.validation.annotation.Validated; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.util.List; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * <p> |
| | |
| | | |
| | | private final TSysInspectionService sysInspectionService; |
| | | private final TokenService tokenService; |
| | | private final TSysAppUserService sysAppUserService; |
| | | private final AIUtil aiUtil; |
| | | @Autowired |
| | | public TSysInspectionController(TSysInspectionService sysInspectionService, TokenService tokenService) { |
| | | public TSysInspectionController(TSysInspectionService sysInspectionService, TokenService tokenService, TSysAppUserService sysAppUserService, AIUtil aiUtil) { |
| | | this.sysInspectionService = sysInspectionService; |
| | | this.tokenService = tokenService; |
| | | this.sysAppUserService = sysAppUserService; |
| | | this.aiUtil = aiUtil; |
| | | } |
| | | |
| | | /** |
| | |
| | | return R.ok(sysInspectionService.getById(id)); |
| | | } |
| | | |
| | | /** |
| | | * 用户检测跳转时单点登录 |
| | | */ |
| | | @ApiOperation(value = "用户检测跳转时单点登录") |
| | | @PostMapping(value = "/ssoLogin") |
| | | public R<String> ssoLogin() { |
| | | String userId = tokenService.getLoginUserApplet().getUserId(); |
| | | TSysAppUser sysAppUser = sysAppUserService.getById(userId); |
| | | if(StringUtils.isEmpty(sysAppUser.getNickName()) || Objects.isNull(sysAppUser.getSex()) || Objects.isNull(sysAppUser.getBirthTime())){ |
| | | return R.fail("请先完善用户信息"); |
| | | } |
| | | int code = aiUtil.ssoLogin().getCode(); |
| | | if(code != 200){ |
| | | return R.fail("单点登录失败"); |
| | | } |
| | | code = aiUtil.initUser(userId, sysAppUser.getNickName(), sysAppUser.getSex(), DateUtils.localDateTimeToString(sysAppUser.getBirthTime())).getCode(); |
| | | if(code != 200){ |
| | | return R.fail("初始化用户失败"); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | } |
| | | |
New file |
| | |
| | | package com.ruoyi.web.controller.api; |
| | | |
| | | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.ruoyi.common.core.domain.AjaxResult; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.utils.CodeGenerateUtils; |
| | | import com.ruoyi.framework.web.service.TokenService; |
| | | import com.ruoyi.system.model.TSysAppUser; |
| | | import com.ruoyi.system.model.TSysInspection; |
| | | import com.ruoyi.system.model.TSysOtherConfig; |
| | | import com.ruoyi.system.model.TSysPayRecord; |
| | | import com.ruoyi.system.service.TSysAppUserService; |
| | | import com.ruoyi.system.service.TSysInspectionService; |
| | | import com.ruoyi.system.service.TSysOtherConfigService; |
| | | import com.ruoyi.system.service.TSysPayRecordService; |
| | | import com.ruoyi.system.wxPay.enums.RefundEnum; |
| | | import com.ruoyi.system.wxPay.enums.TradeStateEnum; |
| | | import com.ruoyi.system.wxPay.model.WeixinPayProperties; |
| | | import com.ruoyi.system.wxPay.model.WxPaymentRefundModel; |
| | | import com.ruoyi.system.wxPay.utils.WxTimeUtils; |
| | | import com.ruoyi.system.wxPay.utils.WxV3Pay; |
| | | import io.swagger.annotations.Api; |
| | | import io.swagger.annotations.ApiOperation; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.io.IOException; |
| | | import java.math.BigDecimal; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | |
| | | /** |
| | | * 微信相关接口 |
| | | */ |
| | | @Slf4j |
| | | @RestController |
| | | @CrossOrigin |
| | | @RequestMapping("/wx/") |
| | | @Api(tags = {"微信支付相关接口"}) |
| | | public class WxPayController { |
| | | @Autowired |
| | | private WxV3Pay wxV3Pay; |
| | | @Autowired |
| | | private TokenService tokenService; |
| | | @Autowired |
| | | private TSysAppUserService sysAppUserService; |
| | | @Autowired |
| | | private TSysOtherConfigService sysOtherConfigService; |
| | | @Autowired |
| | | private TSysPayRecordService sysPayRecordService; |
| | | @Autowired |
| | | private WeixinPayProperties weixinPayProperties; |
| | | @Autowired |
| | | private TSysInspectionService sysInspectionService; |
| | | |
| | | |
| | | |
| | | /** |
| | | * 按实际修改 |
| | | */ |
| | | @ApiOperation("订单支付") |
| | | @PostMapping("orderPay") |
| | | public R<Map<String, Object>> orderPay(@RequestParam(value = "inspectionId") String inspectionId) { |
| | | |
| | | TSysOtherConfig sysOtherConfig = sysOtherConfigService.getById(1); |
| | | if (Objects.isNull(sysOtherConfig.getAiPrice())) { |
| | | return R.fail("系统未配置AI支付价格"); |
| | | } |
| | | |
| | | String userId = tokenService.getLoginUserApplet().getUserId(); |
| | | TSysAppUser sysAppUser = sysAppUserService.getById(userId); |
| | | // 价格 |
| | | Integer totalPrice = sysOtherConfig.getAiPrice().multiply(new BigDecimal(100)).intValue(); |
| | | // 生成订单号 |
| | | String orderNo = CodeGenerateUtils.generateOrderSn(); |
| | | // 查询用户信息 用户openid |
| | | String openId = sysAppUser.getOpenId(); |
| | | // 存储支付记录 |
| | | sysPayRecordService.saveData(orderNo, userId, sysOtherConfig.getAiPrice(), 1); |
| | | // 调用支付方法 |
| | | Map<String, Object> result = wxV3Pay.jsApi(orderNo, totalPrice, openId, weixinPayProperties.getV3().getNotifyPayUrl(),"AI检测报告支付"); |
| | | log.info("支付参数:{}", result); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | /** |
| | | * 微信v3支付-订单退款 |
| | | * |
| | | * @return |
| | | */ |
| | | @ApiOperation("订单退款") |
| | | @PostMapping(value = "refund-order") |
| | | public AjaxResult<String> refundOrder() { |
| | | Map<String, Object> result = wxV3Pay.refund(new WxPaymentRefundModel()); |
| | | log.info("退款结果:{}", result); |
| | | // 微信支付退款单号 |
| | | String refund_id = result.get("refund_id").toString(); |
| | | // 商户退款单号 |
| | | String out_refund_no = result.get("out_refund_no").toString(); |
| | | // 微信支付订单号 |
| | | String transaction_id = result.get("transaction_id").toString(); |
| | | // 商户订单号 tradeNo |
| | | String out_trade_no = result.get("out_trade_no").toString(); |
| | | // 退款成功时间 |
| | | String success_time = Objects.nonNull(result.get("success_time")) ? result.get("success_time").toString() : null; |
| | | // 退款状态 RefundEnum |
| | | String status = result.get("status").toString(); |
| | | // TODO 退款业务处理 |
| | | return AjaxResult.success(); |
| | | } |
| | | /** |
| | | * 支付回调 |
| | | */ |
| | | @PostMapping("pay/notify") |
| | | @ApiOperation("订单回调") |
| | | public void payNotify(HttpServletRequest request) throws Exception { |
| | | try { |
| | | Map<String, Object> params = wxV3Pay.verifyNotify(request, new TypeReference<Map<String, Object>>() {}); |
| | | log.info("支付回调:{}", params); |
| | | // 商户订单号 |
| | | String tradeNo = params.get("out_trade_no").toString(); |
| | | // 交易状态 |
| | | String trade_state = params.get("trade_state").toString(); |
| | | // 交易状态描述 |
| | | String trade_state_desc = params.get("trade_state_desc").toString(); |
| | | // 微信支付订单号 |
| | | String transaction_id = params.get("transaction_id").toString(); |
| | | // 支付完成时间 |
| | | // 时间不对的话,可以调用 WxTimeUtils.toRfc3339Date(success_time)转换一下 |
| | | String success_time = params.get("success_time").toString(); |
| | | // 附加数据 |
| | | Integer attach = Integer.parseInt(params.get("attach").toString()); |
| | | // 查询订单 |
| | | TSysPayRecord sysPayRecord = sysPayRecordService.getOne(Wrappers.lambdaQuery(TSysPayRecord.class) |
| | | .eq(TSysPayRecord::getOrderNo, tradeNo).last("LIMIT 1")); |
| | | // 处理订单 |
| | | if (trade_state.equals(TradeStateEnum.SUCCESS.name())) { |
| | | log.info("回调成功"); |
| | | // 订单号查询订单 |
| | | sysPayRecord.setPayState(2); |
| | | sysPayRecord.setPayTime(WxTimeUtils.dateToLocalDateTime(WxTimeUtils.toRfc3339Date(success_time))); |
| | | sysPayRecord.setTransactionId(transaction_id); |
| | | sysPayRecordService.updateById(sysPayRecord); |
| | | |
| | | // 处理检测报告为可见 |
| | | TSysInspection sysInspection = sysInspectionService.getById(sysPayRecord.getInspectionId()); |
| | | sysInspection.setIsPay(1); |
| | | sysInspectionService.updateById(sysInspection); |
| | | |
| | | wxV3Pay.ack(); |
| | | } |
| | | wxV3Pay.ack(); |
| | | } catch (Exception e) { |
| | | log.error("支付回调异常:{}", e, e); |
| | | wxV3Pay.ack(false, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 支付回调成功后 |
| | | */ |
| | | @PostMapping("pay/ack") |
| | | public void ack(){ |
| | | try { |
| | | wxV3Pay.ack(); |
| | | } catch (IOException e) { |
| | | throw new RuntimeException(e); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * 退款回调 |
| | | */ |
| | | @PostMapping("refund/notify") |
| | | public void refundNotify(HttpServletRequest request) throws IOException { |
| | | try { |
| | | Map<String, Object> params = wxV3Pay.verifyNotify(request, new TypeReference<Map<String, Object>>() { |
| | | }); |
| | | // 商户订单号 |
| | | String out_trade_no = params.get("out_trade_no").toString(); |
| | | // 商户退款单号 |
| | | String out_refund_no = params.get("out_refund_no").toString(); |
| | | // 微信支付订单号 |
| | | String transaction_id = params.get("transaction_id").toString(); |
| | | // 微信支付退款单号 |
| | | String refund_id = params.get("refund_id").toString(); |
| | | // 退款状态 |
| | | String tradeState = params.get("refund_status").toString(); |
| | | // 退款成功时间 |
| | | // 时间不对的话,可以调用 WxTimeUtils.toRfc3339Date(success_time)转换一下 |
| | | String success_time = params.get("success_time").toString(); |
| | | if (tradeState.equals(RefundEnum.SUCCESS.name())) { |
| | | wxV3Pay.ack(); |
| | | } else { |
| | | wxV3Pay.ack(false, "不是成功的退款状态"); |
| | | } |
| | | } catch (Exception e) { |
| | | e.printStackTrace(); |
| | | wxV3Pay.ack(false, e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | } |
| | |
| | | #OSS及短信配置 |
| | | code: |
| | | config: |
| | | templateCodeTest: "SMS_154950909" |
| | | signNameTest: "阿里云短信测试" |
| | | accessKeyId: LTAI5tAdba8HtT1C6UqtSxBt |
| | | accessKeySecret: 0SRb6XGkciQDPWn2rYqbJtq2qRMDY8 |
| | | signName: "四川金达通信工程" |
| | | templateCode: "SMS_293985284" |
| | | cos: |
| | | client: |
| | | accessKey: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x |
| | | secretKey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU |
| | | bucket: xzgt-1305134071 |
| | | bucketAddr: ap-chengdu |
| | | rootSrc: https://xzgt-1305134071.cos.ap-chengdu.myqcloud.com |
| | | location: /xizang |
| | | sms: |
| | | enable: true |
| | | appId: 1400957506 |
| | | secretid: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x |
| | | secretkey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU |
| | | sign: 畅云出行 |
| | | accessKeyId: LTAI5t99NH2Wwoq48ho72u8M |
| | | accessKeySecret: wAGHKUEBrrTgDcbfjnBKTEt5858Ru5 |
| | | signName: "海诊通" |
| | | payment: |
| | | wx: |
| | | # 微信appid |
| | | appId: wxa17e8d1331e50934 |
| | | # 微信商户号 |
| | | mchId: 1721757915 |
| | | # 秘钥 |
| | | secretId: 79c234527fd3b6553679d52be5e29b19 |
| | | v3: |
| | | # 加签串 |
| | | apiKey: V7mKp9qL2Rs4jU6tX8wZ0bC3eF5hN1yD4gA |
| | | # 加签证书地址 |
| | | privateKeyPath: D:/app/cert/weixin/apiclient_key.pem |
| | | # 证书序列号 |
| | | mchSerialNo: 52C3F27D42CB31E70F93C2E9A3FF4F0BD845EA6B |
| | | # 支付成功回调地址 |
| | | notifyPayUrl: http://221.182.45.100/wx/pay/notify |
| | | # 支付退款回调地址 |
| | | notifyRefundUrl: http://221.182.45.100/wx/refund/notify |
| | |
| | | prefix: https://xzgt.test.591taxi.cn:${server.port}${server.servlet.context-path} |
| | | wx: |
| | | conf: |
| | | appId: wxe91f1af7638aa5dd |
| | | appId: wxa17e8d1331e50934 |
| | | secretId: a787e1a462715604e0c9528b6d8960d1 |
| | | sms: |
| | | enable: true |
| | | appId: 1400957506 |
| | | secretid: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x |
| | | secretkey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU |
| | | sign: 畅云出行 |
| | | code: |
| | | config: |
| | | accessKeyId: LTAI5t99NH2Wwoq48ho72u8M |
| | | accessKeySecret: wAGHKUEBrrTgDcbfjnBKTEt5858Ru5 |
| | | signName: "海诊通" |
| | | payment: |
| | | wx: |
| | | # 微信appid |
| | | appId: wxa17e8d1331e50934 |
| | | # 微信商户号 |
| | | mchId: 1721757915 |
| | | # 秘钥 |
| | | secretId: 79c234527fd3b6553679d52be5e29b19 |
| | | v3: |
| | | # 加签串 |
| | | apiKey: V7mKp9qL2Rs4jU6tX8wZ0bC3eF5hN1yD4gA |
| | | # 加签证书地址 |
| | | privateKeyPath: D:/app/cert/weixin/apiclient_key.pem |
| | | # 证书序列号 |
| | | mchSerialNo: 52C3F27D42CB31E70F93C2E9A3FF4F0BD845EA6B |
| | | # 支付成功回调地址 |
| | | notifyPayUrl: http://221.182.45.100/wx/pay/notify |
| | | # 支付退款回调地址 |
| | | notifyRefundUrl: http://221.182.45.100/wx/refund/notify |
| | |
| | | return result.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 向指定 URL 发送POST方法的请求 |
| | | * |
| | | * @param url 发送请求的 URL |
| | | * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 |
| | | * @return 所代表远程资源的响应结果 |
| | | */ |
| | | public static String sendPost(String url, String param,String token) |
| | | { |
| | | PrintWriter out = null; |
| | | BufferedReader in = null; |
| | | StringBuilder result = new StringBuilder(); |
| | | try |
| | | { |
| | | log.info("sendPost - {}", url); |
| | | URL realUrl = new URL(url); |
| | | URLConnection conn = realUrl.openConnection(); |
| | | conn.setRequestProperty("accept", "*/*"); |
| | | conn.setRequestProperty("connection", "Keep-Alive"); |
| | | conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); |
| | | conn.setRequestProperty("Accept-Charset", "utf-8"); |
| | | conn.setRequestProperty("contentType", "utf-8"); |
| | | conn.setRequestProperty("Authorization","bearer "+ token); |
| | | conn.setDoOutput(true); |
| | | conn.setDoInput(true); |
| | | out = new PrintWriter(conn.getOutputStream()); |
| | | out.print(param); |
| | | out.flush(); |
| | | in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); |
| | | String line; |
| | | while ((line = in.readLine()) != null) |
| | | { |
| | | result.append(line); |
| | | } |
| | | log.info("recv - {}", result); |
| | | } |
| | | catch (ConnectException e) |
| | | { |
| | | log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); |
| | | } |
| | | catch (SocketTimeoutException e) |
| | | { |
| | | log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); |
| | | } |
| | | catch (IOException e) |
| | | { |
| | | log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); |
| | | } |
| | | catch (Exception e) |
| | | { |
| | | log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); |
| | | } |
| | | finally |
| | | { |
| | | try |
| | | { |
| | | if (out != null) |
| | | { |
| | | out.close(); |
| | | } |
| | | if (in != null) |
| | | { |
| | | in.close(); |
| | | } |
| | | } |
| | | catch (IOException ex) |
| | | { |
| | | log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); |
| | | } |
| | | } |
| | | return result.toString(); |
| | | } |
| | | |
| | | public static String sendSSLPost(String url, String param) |
| | | { |
| | | StringBuilder result = new StringBuilder(); |
| | |
| | | <groupId>com.fasterxml.jackson.datatype</groupId> |
| | | <artifactId>jackson-datatype-jsr310</artifactId> |
| | | </dependency> |
| | | <dependency> |
| | | <groupId>com.github.wechatpay-apiv3</groupId> |
| | | <artifactId>wechatpay-apache-httpclient</artifactId> |
| | | <version>0.4.3</version> |
| | | </dependency> |
| | | </dependencies> |
| | | |
| | | </project> |
New file |
| | |
| | | package com.ruoyi.system.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.system.model.TSysOtherConfig; |
| | | |
| | | /** |
| | | * <p> |
| | | * 其他设置 Mapper 接口 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | public interface TSysOtherConfigMapper extends BaseMapper<TSysOtherConfig> { |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.mapper; |
| | | |
| | | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
| | | import com.ruoyi.system.model.TSysPayRecord; |
| | | |
| | | /** |
| | | * <p> |
| | | * 微信支付记录 Mapper 接口 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | public interface TSysPayRecordMapper extends BaseMapper<TSysPayRecord> { |
| | | |
| | | } |
| | |
| | | |
| | | /** |
| | | * <p> |
| | | * 系统配置-协议管理-其他设置 |
| | | * 系统配置-协议管理 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = false) |
| | | @TableName("t_sys_config") |
| | | @ApiModel(value="TSysConfig对象", description="系统配置-协议管理-其他设置") |
| | | @ApiModel(value="TSysConfig对象", description="系统配置-协议管理") |
| | | public class TSysConfig extends BaseModel { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | |
| | | @TableId(value = "id", type = IdType.ASSIGN_ID) |
| | | private String id; |
| | | |
| | | @ApiModelProperty(value = "配置类型 1=用户协议 2=隐私协议 3=其他设置") |
| | | @ApiModelProperty(value = "配置类型 1=用户协议 2=隐私协议") |
| | | @TableField("config_type") |
| | | private Integer configType; |
| | | |
| | |
| | | @TableField("tongue_feature") |
| | | private String tongueFeature; |
| | | |
| | | @ApiModelProperty(value = "面象特征分析") |
| | | @TableField("face_feature") |
| | | private String faceFeature; |
| | | |
| | | @ApiModelProperty(value = "风险疾病名称") |
| | | @TableField("disease_risks") |
| | | private String diseaseRisks; |
New file |
| | |
| | | package com.ruoyi.system.model; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.*; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * 其他设置 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = false) |
| | | @TableName("t_sys_other_config") |
| | | @ApiModel(value="TSysOtherConfig对象", description="其他设置") |
| | | public class TSysOtherConfig implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.ASSIGN_ID) |
| | | private String id; |
| | | |
| | | @ApiModelProperty(value = "采购满多少元获得1积分") |
| | | @TableField("procurement_conditions") |
| | | private Double procurementConditions; |
| | | |
| | | @ApiModelProperty(value = "积分有效期") |
| | | @TableField("points_expiration") |
| | | private Integer pointsExpiration; |
| | | |
| | | @ApiModelProperty(value = "有效期单位 1=天 2=月(31) 3=年(365)") |
| | | @TableField("expire_unit") |
| | | private Integer expireUnit; |
| | | |
| | | @ApiModelProperty(value = "诊所每天可发送短信数") |
| | | @TableField("msg_count") |
| | | private Integer msgCount; |
| | | |
| | | @ApiModelProperty(value = "ai检测费用") |
| | | @TableField("ai_price") |
| | | private BigDecimal aiPrice; |
| | | |
| | | @ApiModelProperty(value = "自动收货时间") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | @TableField("delivery_time") |
| | | private LocalDateTime deliveryTime; |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.model; |
| | | |
| | | import com.baomidou.mybatisplus.annotation.IdType; |
| | | import com.baomidou.mybatisplus.annotation.TableField; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import com.fasterxml.jackson.annotation.JsonFormat; |
| | | import io.swagger.annotations.ApiModel; |
| | | import io.swagger.annotations.ApiModelProperty; |
| | | import lombok.Data; |
| | | import lombok.EqualsAndHashCode; |
| | | |
| | | import java.io.Serializable; |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDateTime; |
| | | |
| | | /** |
| | | * <p> |
| | | * 微信支付记录 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | @Data |
| | | @EqualsAndHashCode(callSuper = false) |
| | | @TableName("t_sys_pay_record") |
| | | @ApiModel(value="TSysPayRecord对象", description="微信支付记录") |
| | | public class TSysPayRecord implements Serializable { |
| | | |
| | | private static final long serialVersionUID = 1L; |
| | | |
| | | @TableId(value = "id", type = IdType.ASSIGN_ID) |
| | | private String id; |
| | | |
| | | @ApiModelProperty(value = "用户id") |
| | | @TableField("app_user_id") |
| | | private String appUserId; |
| | | |
| | | @ApiModelProperty(value = "检测id") |
| | | @TableField("inspection_id") |
| | | private String inspectionId; |
| | | |
| | | @ApiModelProperty(value = "支付价格") |
| | | @TableField("pay_amount") |
| | | private BigDecimal payAmount; |
| | | |
| | | @ApiModelProperty(value = "支付状态 1=待支付 2=已支付") |
| | | @TableField("pay_state") |
| | | private Integer payState; |
| | | |
| | | @ApiModelProperty(value = "商户流水号") |
| | | @TableField("order_no") |
| | | private String orderNo; |
| | | |
| | | @ApiModelProperty(value = "支付流水号") |
| | | @TableField("transaction_id") |
| | | private String transactionId; |
| | | |
| | | @ApiModelProperty(value = "支付时间") |
| | | @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
| | | @TableField("pay_time") |
| | | private LocalDateTime payTime; |
| | | |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.system.model.TSysOtherConfig; |
| | | |
| | | /** |
| | | * <p> |
| | | * 其他设置 服务类 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | public interface TSysOtherConfigService extends IService<TSysOtherConfig> { |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.ruoyi.system.model.TSysPayRecord; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** |
| | | * <p> |
| | | * 微信支付记录 服务类 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | public interface TSysPayRecordService extends IService<TSysPayRecord> { |
| | | |
| | | /** |
| | | * 保存数据 |
| | | * @param orderNo 支付单号 |
| | | * @param userId 用户id |
| | | * @param aiPrice ai价格 |
| | | * @param state 支付状态 1=待支付 2=已支付 |
| | | */ |
| | | void saveData(String orderNo, String userId, BigDecimal aiPrice, Integer state); |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.system.mapper.TSysOtherConfigMapper; |
| | | import com.ruoyi.system.model.TSysOtherConfig; |
| | | import com.ruoyi.system.service.TSysOtherConfigService; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | /** |
| | | * <p> |
| | | * 其他设置 服务实现类 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | @Service |
| | | public class TSysOtherConfigServiceImpl extends ServiceImpl<TSysOtherConfigMapper, TSysOtherConfig> implements TSysOtherConfigService { |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.service.impl; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.ruoyi.system.mapper.TSysPayRecordMapper; |
| | | import com.ruoyi.system.model.TSysPayRecord; |
| | | import com.ruoyi.system.service.TSysPayRecordService; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import java.math.BigDecimal; |
| | | |
| | | /** |
| | | * <p> |
| | | * 微信支付记录 服务实现类 |
| | | * </p> |
| | | * |
| | | * @author xiaochen |
| | | * @since 2025-09-18 |
| | | */ |
| | | @Service |
| | | public class TSysPayRecordServiceImpl extends ServiceImpl<TSysPayRecordMapper, TSysPayRecord> implements TSysPayRecordService { |
| | | |
| | | @Override |
| | | public void saveData(String orderNo, String userId, BigDecimal aiPrice, Integer state) { |
| | | TSysPayRecord sysPayRecord = new TSysPayRecord(); |
| | | sysPayRecord.setOrderNo(orderNo); |
| | | sysPayRecord.setAppUserId(userId); |
| | | sysPayRecord.setPayAmount(aiPrice); |
| | | sysPayRecord.setPayState(state); |
| | | this.save(sysPayRecord); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.utils.util; |
| | | |
| | | import com.alibaba.fastjson2.JSONObject; |
| | | import com.ruoyi.common.constant.Constants; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.exception.ServiceException; |
| | | import com.ruoyi.common.core.redis.RedisCache; |
| | | import com.ruoyi.common.utils.http.HttpUtils; |
| | | import com.ruoyi.system.model.TSysAiConfig; |
| | | import com.ruoyi.system.service.TSysAiConfigService; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | @Slf4j |
| | | @Component |
| | | public class AIUtil { |
| | | |
| | | @Autowired |
| | | private TSysAiConfigService sysAiConfigService; |
| | | @Autowired |
| | | private RedisCache redisCache; |
| | | |
| | | /** |
| | | * 获取企业应用access_token |
| | | */ |
| | | public static final String ACCESS_TOKEN_URL = "https://www.ai-tongue.com/backend/auth/invoker/pwd/signin"; |
| | | /** |
| | | * 会员单点登录 |
| | | */ |
| | | public static final String SSO_URL = "https://www.ai-tongue.com/h5/sso"; |
| | | /** |
| | | * 初始化会员信息 |
| | | */ |
| | | public static final String INIT_USER_URL = "https://www.ai-tongue.com/backend/check/i/secret/thirdUser/init"; |
| | | |
| | | /** |
| | | * 获取企业应用access_token |
| | | * @return |
| | | */ |
| | | public String getAccessToken() { |
| | | String accessToken = redisCache.getCacheObject(Constants.H5AI_ACCESS_TOKEN); |
| | | if(StringUtils.hasLength(accessToken)){ |
| | | return accessToken; |
| | | } |
| | | TSysAiConfig sysAiConfig = sysAiConfigService.getById(1); |
| | | String result = HttpUtils.sendPost(ACCESS_TOKEN_URL, "devid="+sysAiConfig.getDevId()+"&devsecret="+sysAiConfig.getDevSecret()); |
| | | log.info("获取access_token:{}", result); |
| | | JSONObject object = JSONObject.parseObject(result); |
| | | if(object.getInteger("code") != 0){ |
| | | throw new ServiceException(object.getString("msg")); |
| | | } |
| | | JSONObject data = object.getJSONObject("data"); |
| | | accessToken = data.getString("access_token"); |
| | | Integer expiresIn = data.getInteger("expires_in"); |
| | | redisCache.setCacheObject(Constants.H5AI_ACCESS_TOKEN, accessToken, expiresIn-20, TimeUnit.SECONDS); |
| | | return accessToken; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 会员单点登录 |
| | | * @return |
| | | */ |
| | | public R ssoLogin() { |
| | | String accessToken = redisCache.getCacheObject(Constants.H5AI_ACCESS_TOKEN); |
| | | if(!StringUtils.hasLength(accessToken)){ |
| | | accessToken = getAccessToken(); |
| | | } |
| | | TSysAiConfig sysAiConfig = sysAiConfigService.getById(1); |
| | | String result = HttpUtils.sendGet(SSO_URL, "access_token="+accessToken |
| | | +"&encryptedThirdId="+sysAiConfig.getRsaPublicKey() |
| | | +"&signEncryptedThirdId="+sysAiConfig.getDevRsaPublicKey() |
| | | +"&capture=all&diseaseCode=C00.D00"); |
| | | log.info("会员单点登录结果:{}", result); |
| | | JSONObject object = JSONObject.parseObject(result); |
| | | if(object.getInteger("code") != 0){ |
| | | throw new ServiceException(object.getString("msg")); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 会员创建接口 |
| | | * @param nickName |
| | | * @param sex |
| | | * @param birthday |
| | | * @return |
| | | */ |
| | | public R initUser(String userId,String nickName, Integer sex, String birthday) { |
| | | String accessToken = redisCache.getCacheObject(Constants.H5AI_ACCESS_TOKEN); |
| | | if(!StringUtils.hasLength(accessToken)){ |
| | | accessToken = getAccessToken(); |
| | | } |
| | | TSysAiConfig sysAiConfig = sysAiConfigService.getById(1); |
| | | String result = HttpUtils.sendPost(INIT_USER_URL, "devId="+sysAiConfig.getDevId() |
| | | +"&thirdId="+userId |
| | | +"&signEncryptedThirdId="+sysAiConfig.getDevRsaPublicKey() |
| | | +"&thirdName="+nickName |
| | | +"&sex="+sex |
| | | +"&birthday="+birthday,accessToken); |
| | | log.info("会员创建初始化结果:{}", result); |
| | | JSONObject object = JSONObject.parseObject(result); |
| | | if(object.getInteger("code") != 0){ |
| | | throw new ServiceException(object.getString("msg")); |
| | | } |
| | | return R.ok(); |
| | | } |
| | | |
| | | |
| | | |
| | | } |
| | |
| | | import javax.crypto.Cipher; |
| | | import javax.crypto.spec.IvParameterSpec; |
| | | import javax.crypto.spec.SecretKeySpec; |
| | | import javax.servlet.ServletInputStream; |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.io.BufferedReader; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | import java.io.InputStreamReader; |
| | | import java.security.AlgorithmParameters; |
| | | import java.security.SecureRandom; |
| | | import java.security.Security; |
| | | import java.util.Arrays; |
| | | import java.util.Random; |
| | | |
| | | /** |
| | | * @Description 获取用户信息工具类 |
| | |
| | | */ |
| | | @Slf4j |
| | | public class WxUtils { |
| | | /** |
| | | * 随机字符 |
| | | */ |
| | | private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; |
| | | |
| | | private static final Random RANDOM = new SecureRandom(); |
| | | /** |
| | | * 微信小程序API 用户数据的解密 |
| | | * |
| | |
| | | * @return |
| | | */ |
| | | public static String streamBodyByReceive(HttpServletRequest request) throws IOException { |
| | | log.info("微信异步回调地址:{}", request.getRequestURL()); |
| | | StringBuffer buffer = new StringBuffer(); |
| | | InputStream inputStream = request.getInputStream(); |
| | | InputStreamReader reader = new InputStreamReader(inputStream); |
| | | BufferedReader bufferedReader = new BufferedReader(reader); |
| | | String body = null; |
| | | while ((body = bufferedReader.readLine()) != null) { |
| | | buffer.append(body); |
| | | BufferedReader reader = null; |
| | | StringBuffer sb = new StringBuffer(); |
| | | try { |
| | | ServletInputStream stream = request.getInputStream(); |
| | | // 获取响应 |
| | | reader = new BufferedReader(new InputStreamReader(stream)); |
| | | String line; |
| | | while ((line = reader.readLine()) != null) { |
| | | sb.append(line); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new RuntimeException("读取微信支付接口数据流出现异常!"); |
| | | } finally { |
| | | reader.close(); |
| | | WxUtils.info(sb.toString()); |
| | | } |
| | | String data = buffer.toString(); |
| | | reader.close(); |
| | | inputStream.close(); |
| | | log.info("微信异步回调数据:{}", data); |
| | | return data; |
| | | return sb.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 获取随机字符串 Nonce Str |
| | | * |
| | | * @return String 随机字符串 |
| | | */ |
| | | public static String generateNonceStr() { |
| | | char[] nonceChars = new char[32]; |
| | | for (int index = 0; index < nonceChars.length; ++index) { |
| | | nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length())); |
| | | } |
| | | return new String(nonceChars); |
| | | } |
| | | |
| | | /** |
New file |
| | |
| | | package com.ruoyi.system.wxPay.config; |
| | | |
| | | import com.ruoyi.system.utils.wx.model.WeixinProperties; |
| | | import com.ruoyi.system.wxPay.model.WeixinPayProperties; |
| | | import com.ruoyi.system.wxPay.utils.WxV3Pay; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; |
| | | import org.springframework.context.annotation.Bean; |
| | | import org.springframework.context.annotation.Configuration; |
| | | |
| | | /** |
| | | * 项目中需继承此类 |
| | | * |
| | | * @author lihen |
| | | */ |
| | | @Configuration |
| | | public class WxConfig { |
| | | |
| | | private final WeixinPayProperties weixinPayProperties; |
| | | |
| | | @Autowired |
| | | public WxConfig(WeixinPayProperties weixinPayProperties) { |
| | | this.weixinPayProperties = weixinPayProperties; |
| | | } |
| | | |
| | | @Bean |
| | | @ConditionalOnMissingBean(name = "wxV3Pay") |
| | | public WxV3Pay wxSpV3Pay() { |
| | | return new WxV3Pay(weixinPayProperties); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.enums; |
| | | |
| | | import lombok.Getter; |
| | | |
| | | import java.util.stream.Stream; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName ProfitSharingEnum |
| | | * @Description |
| | | * @date 2021-11-21 11:15 |
| | | */ |
| | | public enum RefundEnum { |
| | | /** |
| | | * 退款成功 |
| | | */ |
| | | SUCCESS("SUCCESS", "退款成功"), |
| | | /** |
| | | * 退款关闭 |
| | | */ |
| | | CLOSED("CLOSED", "退款关闭"), |
| | | /** |
| | | * 退款处理中 |
| | | */ |
| | | PROCESSING("PROCESSING", "退款处理中"), |
| | | /** |
| | | * 退款异常 |
| | | */ |
| | | ABNORMAL("ABNORMAL", "退款异常"), |
| | | ; |
| | | |
| | | @Getter |
| | | private String code; |
| | | @Getter |
| | | private String desc; |
| | | |
| | | RefundEnum(String code, String desc) { |
| | | this.code = code; |
| | | this.desc = desc; |
| | | } |
| | | |
| | | /** |
| | | * 通过交易类型执行具体的交易方法 |
| | | * |
| | | * @param code |
| | | * @return |
| | | */ |
| | | public static RefundEnum fromValue(String code) { |
| | | return Stream.of(RefundEnum.values()).filter(fileType -> |
| | | fileType.getCode().toLowerCase().equals(code.toLowerCase()) |
| | | ).findFirst().orElse(null); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.enums; |
| | | |
| | | import lombok.Getter; |
| | | |
| | | import java.util.stream.Stream; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName AliTradeStateEnum |
| | | * @Description |
| | | * @date 2022-01-07 11:56 |
| | | */ |
| | | public enum TradeStateEnum { |
| | | SUCCESS("支付成功"), |
| | | RETURN("已分账回退"), |
| | | FAIL("已失败"), |
| | | PROCESSING("处理中"), |
| | | FINISHED("分账完成"), |
| | | REFUND("转入退款"), |
| | | PAYERROR("支付失败(其他原因,如银行返回失败)"), |
| | | USERPAYING("用户支付中"), |
| | | CLOSED("已关闭"), |
| | | NOTPAY("未支付"), |
| | | UNKNOWN("未知"), |
| | | DONE("服务订单完成"), |
| | | // ... |
| | | ; |
| | | @Getter |
| | | private String desc; |
| | | |
| | | TradeStateEnum(String desc) { |
| | | this.desc = desc; |
| | | } |
| | | |
| | | public static TradeStateEnum tradeState(String code) { |
| | | return Stream.of(TradeStateEnum.values()).filter(fileType -> |
| | | fileType.name().equals(code) |
| | | ).findFirst().orElse(UNKNOWN); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.exception; |
| | | |
| | | /** |
| | | * @author lihen |
| | | */ |
| | | public class WxException extends RuntimeException { |
| | | |
| | | private final static int OK = 0; |
| | | private final static int ValidateSignatureError = -40001; |
| | | private final static int ParseXmlError = -40002; |
| | | public final static int ComputeSignatureError = -40003; |
| | | private final static int IllegalAesKey = -40004; |
| | | private final static int ValidateAppidError = -40005; |
| | | private final static int EncryptAESError = -40006; |
| | | private final static int DecryptAESError = -40007; |
| | | private final static int IllegalBuffer = -40008; |
| | | |
| | | private int code; |
| | | |
| | | private static String getMessage(int code) { |
| | | switch (code) { |
| | | case ValidateSignatureError: |
| | | return "签名验证错误"; |
| | | case ParseXmlError: |
| | | return "xml解析失败"; |
| | | case ComputeSignatureError: |
| | | return "sha加密生成签名失败"; |
| | | case IllegalAesKey: |
| | | return "SymmetricKey非法"; |
| | | case ValidateAppidError: |
| | | return "appid校验失败"; |
| | | case EncryptAESError: |
| | | return "aes加密失败"; |
| | | case DecryptAESError: |
| | | return "aes解密失败"; |
| | | case IllegalBuffer: |
| | | return "解密后得到的buffer非法"; |
| | | default: |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | public int getCode() { |
| | | return code; |
| | | } |
| | | |
| | | public WxException(int code) { |
| | | super(getMessage(code)); |
| | | this.code = code; |
| | | } |
| | | |
| | | public WxException(String message) { |
| | | super(message); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.model; |
| | | |
| | | import lombok.Data; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.poi.util.IOUtils; |
| | | |
| | | import java.io.ByteArrayInputStream; |
| | | import java.io.FileInputStream; |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName V3 |
| | | * @Description |
| | | */ |
| | | @Slf4j |
| | | @Data |
| | | public class V3 { |
| | | /** |
| | | * 获取 API 密钥 |
| | | * |
| | | * @return API密钥 |
| | | */ |
| | | private String apiKey; |
| | | /** |
| | | * 秘钥路径,apiclient_key.pem |
| | | */ |
| | | private String privateKeyPath; |
| | | /** |
| | | * 商户证书序列号 |
| | | */ |
| | | private String mchSerialNo; |
| | | |
| | | /** |
| | | * 支付回调地址 |
| | | * |
| | | * @return |
| | | */ |
| | | private String notifyPayUrl; |
| | | |
| | | /** |
| | | * 退款回调地址 |
| | | * |
| | | * @return |
| | | */ |
| | | private String notifyRefundUrl; |
| | | |
| | | /** |
| | | * 退款回调地址 |
| | | */ |
| | | private String notifyTravelRefundUrl; |
| | | |
| | | |
| | | public InputStream getPrivateKeyStream() { |
| | | // 需要证书释放 |
| | | byte[] certData; |
| | | // InputStream certStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(this.privateKeyPath); |
| | | InputStream certStream = null; |
| | | try { |
| | | certStream = new FileInputStream(this.privateKeyPath); |
| | | certData = IOUtils.toByteArray(certStream); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("私钥文件未找到"); |
| | | }finally { |
| | | if(null != certStream){ |
| | | try { |
| | | certStream.close(); |
| | | } catch (IOException e) { |
| | | log.error("私钥流关闭异常"); |
| | | } |
| | | } |
| | | } |
| | | return new ByteArrayInputStream(certData); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.model; |
| | | |
| | | import lombok.ToString; |
| | | import org.springframework.boot.context.properties.ConfigurationProperties; |
| | | import org.springframework.boot.context.properties.NestedConfigurationProperty; |
| | | import org.springframework.stereotype.Component; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName WeixinProperties |
| | | * @Description |
| | | */ |
| | | @ToString |
| | | @Component |
| | | @ConfigurationProperties(prefix = "payment.wx") |
| | | public class WeixinPayProperties { |
| | | /** |
| | | * 默认开启 |
| | | */ |
| | | private boolean enabled = true; |
| | | /** |
| | | * 获取 App ID |
| | | * |
| | | * @return App ID |
| | | */ |
| | | private String appId; |
| | | /** |
| | | * 获取 Mch ID |
| | | * |
| | | * @return Mch ID |
| | | */ |
| | | private String mchId; |
| | | |
| | | /** |
| | | * 获取 secret ID |
| | | * |
| | | * @return secret ID |
| | | */ |
| | | private String secretId; |
| | | /** |
| | | * 回调地址 |
| | | */ |
| | | private String callBackUrl; |
| | | |
| | | public String getSecretId() { |
| | | return secretId; |
| | | } |
| | | |
| | | public void setSecretId(String secretId) { |
| | | this.secretId = secretId; |
| | | } |
| | | |
| | | /** |
| | | * v3 |
| | | */ |
| | | @NestedConfigurationProperty |
| | | private V3 v3; |
| | | |
| | | public boolean isEnabled() { |
| | | return enabled; |
| | | } |
| | | |
| | | public void setEnabled(boolean enabled) { |
| | | this.enabled = enabled; |
| | | } |
| | | |
| | | public V3 getV3() { |
| | | return v3; |
| | | } |
| | | |
| | | public void setV3(V3 v3) { |
| | | this.v3 = v3; |
| | | } |
| | | |
| | | /** |
| | | * HTTP(S) 连接超时时间,单位毫秒 |
| | | * |
| | | */ |
| | | public int getHttpConnectTimeoutMs() { |
| | | return 6 * 1000; |
| | | } |
| | | |
| | | /** |
| | | * HTTP(S) 读数据超时时间,单位毫秒 |
| | | */ |
| | | public int getHttpReadTimeoutMs() { |
| | | return 8 * 1000; |
| | | } |
| | | |
| | | public String getAppId() { |
| | | return appId; |
| | | } |
| | | |
| | | public void setAppId(String appId) { |
| | | this.appId = appId; |
| | | } |
| | | |
| | | public String getMchId() { |
| | | return mchId; |
| | | } |
| | | |
| | | public void setMchId(String mchId) { |
| | | this.mchId = mchId; |
| | | } |
| | | |
| | | public String getCallBackUrl() { |
| | | return callBackUrl; |
| | | } |
| | | |
| | | public void setCallBackUrl(String callBackUrl) { |
| | | this.callBackUrl = callBackUrl; |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.model; |
| | | |
| | | import lombok.*; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName WxPaymentRefundModel |
| | | * @Description |
| | | */ |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public class WxCloseOrderModel { |
| | | private String mchid; |
| | | |
| | | private String out_trade_no; |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.model; |
| | | |
| | | import lombok.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName WxPaymentInfoModel |
| | | * @Description |
| | | */ |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public class WxPaymentInfoModel { |
| | | /** |
| | | * 合单商户appid |
| | | */ |
| | | private String combine_appid; |
| | | /** |
| | | * 合单商户号 |
| | | */ |
| | | private String combine_mchid; |
| | | /** |
| | | * 合单商户订单号 |
| | | */ |
| | | private String combine_out_trade_no; |
| | | /** |
| | | * 合单--子单信息 |
| | | */ |
| | | private List<SubOrders> sub_orders; |
| | | /** |
| | | * 合单--支付者 |
| | | */ |
| | | private CombinePayerInfo combine_payer_info; |
| | | |
| | | private String appid; |
| | | private String sp_appid; |
| | | private String mchid; |
| | | private String sp_mchid; |
| | | private String sub_appid; |
| | | private String sub_mchid; |
| | | private String description; |
| | | private String out_trade_no; |
| | | private String time_expire; |
| | | private String attach; |
| | | private String notify_url; |
| | | private String goods_tag; |
| | | private SettleInfo settle_info; |
| | | private Amount amount; |
| | | private Payer payer; |
| | | private Detail detail; |
| | | private SceneInfo scene_info; |
| | | |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class SettleInfo { |
| | | private Boolean profit_sharing; |
| | | private Integer subsidy_amount; |
| | | } |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class Amount { |
| | | private Integer total; |
| | | /** |
| | | * 合单支付时需要 |
| | | */ |
| | | private Integer total_amount; |
| | | @Builder.Default |
| | | private String currency = "CNY"; |
| | | } |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class Payer { |
| | | private String openid; |
| | | private String sp_openid; |
| | | private String sub_openid; |
| | | } |
| | | |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class Detail { |
| | | private int cost_price; |
| | | private String invoice_id; |
| | | private List<GoodsDetail> goods_detail; |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class GoodsDetail { |
| | | private String merchant_goods_id; |
| | | private String wechatpay_goods_id; |
| | | private String goods_name; |
| | | private int quantity; |
| | | private int unit_price; |
| | | } |
| | | } |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class SceneInfo { |
| | | private String payer_client_ip; |
| | | private String device_id; |
| | | private StoreInfo store_info; |
| | | private H5Info h5_info; |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class StoreInfo { |
| | | private String id; |
| | | private String name; |
| | | private String area_code; |
| | | private String address; |
| | | } |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class H5Info { |
| | | private String type; |
| | | private String app_name; |
| | | private String app_url; |
| | | private String bundle_id; |
| | | private String package_name; |
| | | } |
| | | } |
| | | |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class SubOrders { |
| | | |
| | | private String out_trade_no; |
| | | |
| | | |
| | | private Amount amount; |
| | | |
| | | private String mchid; |
| | | |
| | | private String sub_mchid; |
| | | |
| | | private String attach; |
| | | |
| | | private String description; |
| | | private String goods_tag; |
| | | |
| | | private SettleInfo settle_info; |
| | | } |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class CombinePayerInfo { |
| | | private String openid; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.model; |
| | | |
| | | import lombok.*; |
| | | |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName WxPaymentRefundModel |
| | | * @Description |
| | | */ |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public class WxPaymentRefundModel { |
| | | /** |
| | | * 子商户,二级商户号 |
| | | */ |
| | | private String sub_mchid; |
| | | /** |
| | | * 电商平台APPID |
| | | */ |
| | | private String sp_appid; |
| | | private String transaction_id; |
| | | private String out_trade_no; |
| | | /** |
| | | * 商户退款单号 |
| | | */ |
| | | private String out_refund_no; |
| | | /** |
| | | * 退款原因 |
| | | */ |
| | | private String reason; |
| | | private String notify_url; |
| | | /** |
| | | * 资金账户,否 |
| | | */ |
| | | private String funds_account; |
| | | /** |
| | | * 退款金额信息 |
| | | */ |
| | | private RefundAmount amount; |
| | | /** |
| | | * 退款商品 |
| | | */ |
| | | private List<RefundGoodsDetail> goods_detail; |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class RefundAmount { |
| | | /** |
| | | * 原订单金额 |
| | | */ |
| | | private int total; |
| | | @Builder.Default |
| | | private String currency = "CNY"; |
| | | /** |
| | | * 退款金额 |
| | | */ |
| | | private int refund; |
| | | } |
| | | |
| | | @Builder |
| | | @AllArgsConstructor |
| | | @NoArgsConstructor |
| | | @Getter |
| | | @Setter |
| | | @ToString |
| | | public static class RefundGoodsDetail { |
| | | private String merchant_goods_id; |
| | | private String wechatpay_goods_id; |
| | | private String goods_name; |
| | | private int unit_price; |
| | | private int refund_amount; |
| | | private int refund_quantity; |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.pojo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName AppletUserDecodeData |
| | | * @Description |
| | | * 用户主体信息部分 |
| | | * { |
| | | * "openId": "OPENID", |
| | | * "nickName": "NICKNAME", |
| | | * "gender": GENDER, |
| | | * "city": "CITY", |
| | | * "province": "PROVINCE", |
| | | * "country": "COUNTRY", |
| | | * "avatarUrl": "AVATARURL", |
| | | * "unionId": "UNIONID", |
| | | * "watermark": |
| | | * { |
| | | * "appid":"APPID", |
| | | * "timestamp":TIMESTAMP |
| | | * } |
| | | * } |
| | | * 电话部分 |
| | | * { |
| | | * "phoneNumber": "13580006666", |
| | | * "purePhoneNumber": "13580006666", |
| | | * "countryCode": "86", |
| | | * "watermark": |
| | | * { |
| | | * "appid":"APPID", |
| | | * "timestamp": TIMESTAMP |
| | | * } |
| | | * } |
| | | * |
| | | */ |
| | | @Data |
| | | public class AppletUserDecodeData { |
| | | private String openId; |
| | | private String unionId; |
| | | private String nickName; |
| | | private int gender; |
| | | private String city; |
| | | private String province; |
| | | private String country; |
| | | private String avatarUrl; |
| | | private Watermark watermark; |
| | | private String phoneNumber; |
| | | private String purePhoneNumber; |
| | | private String countryCode; |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.pojo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | public class Watermark { |
| | | private String appid; |
| | | private String timestamp; |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.resp; |
| | | |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | |
| | | import java.io.Serializable; |
| | | import java.util.List; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName FacilV3PayNotifyRespBody |
| | | * @Description |
| | | */ |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @Data |
| | | public class NotifyV3PayDecodeRespBody implements Serializable { |
| | | // 合单--开始 |
| | | private String combine_appid; |
| | | private String combine_mchid; |
| | | private String combine_out_trade_no; |
| | | private List<SubOrders> sub_orders; |
| | | // 合单--结束 |
| | | /** |
| | | * 服务商应用ID |
| | | */ |
| | | private String sp_appid; |
| | | /** |
| | | * 服务商户号 |
| | | */ |
| | | private String sp_mchid; |
| | | /** |
| | | * 商户号 |
| | | */ |
| | | private String mchid; |
| | | /** |
| | | * 子商户应用ID |
| | | */ |
| | | private String sub_appid; |
| | | /** |
| | | * 子商户号 |
| | | */ |
| | | private String sub_mchid; |
| | | /** |
| | | * 商户订单号 |
| | | */ |
| | | private String out_trade_no; |
| | | /** |
| | | * 交易状态描述 |
| | | */ |
| | | private String trade_state_desc; |
| | | /** |
| | | * 交易类型,枚举值: |
| | | * JSAPI:公众号支付 |
| | | * NATIVE:扫码支付 |
| | | * APP:APP支付 |
| | | * MICROPAY:付款码支付 |
| | | * MWEB:H5支付 |
| | | * FACEPAY:刷脸支付 |
| | | */ |
| | | private String trade_type; |
| | | /** |
| | | * 附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用 |
| | | */ |
| | | private String attach; |
| | | /** |
| | | * 微信支付订单号 |
| | | */ |
| | | private String transaction_id; |
| | | /** |
| | | * 交易状态,枚举值: |
| | | * SUCCESS:支付成功 |
| | | * REFUND:转入退款 |
| | | * NOTPAY:未支付 |
| | | * CLOSED:已关闭 |
| | | * REVOKED:已撤销(付款码支付) |
| | | * USERPAYING:用户支付中(付款码支付) |
| | | * PAYERROR:支付失败(其他原因,如银行返回失败) |
| | | */ |
| | | private String trade_state; |
| | | /** |
| | | * 银行类型,采用字符串类型的银行标识。银行标识请参考《银行类型对照表》 |
| | | * https://pay.weixin.qq.com/wiki/doc/apiv3_partner/terms_definition/chapter1_1_3.shtml#part-6 |
| | | */ |
| | | private String bank_type; |
| | | /** |
| | | * 支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE, |
| | | * YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒, |
| | | * TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。 |
| | | * 例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。 |
| | | * 示例值:2018-06-08T10:34:56+08:00 |
| | | */ |
| | | private String success_time; |
| | | /** |
| | | * 支付者信息 |
| | | */ |
| | | private Payer payer; |
| | | |
| | | /** |
| | | * 支付者 |
| | | */ |
| | | private Payer combine_payer_info; |
| | | |
| | | /** |
| | | * 订单金额信息 |
| | | */ |
| | | private Amount amount; |
| | | /** |
| | | * 场景信息 |
| | | */ |
| | | private SceneInfo scene_info; |
| | | /** |
| | | * 优惠功能,享受优惠时返回该字段 |
| | | */ |
| | | private List<PromotionDetail> promotion_detail; |
| | | |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @Data |
| | | public static class Amount implements Serializable{ |
| | | /** |
| | | * 用户支付金额 |
| | | */ |
| | | private int payer_total; |
| | | /** |
| | | * 总金额 |
| | | */ |
| | | private int total; |
| | | /** |
| | | * 标价金额 |
| | | */ |
| | | private int total_amount; |
| | | /** |
| | | * 现金支付金额 |
| | | */ |
| | | private int payer_amount; |
| | | private String currency; |
| | | private String payer_currency; |
| | | } |
| | | |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @Data |
| | | public static class GoodsDetail implements Serializable{ |
| | | private String goods_id; |
| | | private int quantity; |
| | | private int unit_price; |
| | | private int discount_amount; |
| | | private String goods_remark; |
| | | |
| | | } |
| | | |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @Data |
| | | public static class Payer implements Serializable{ |
| | | private String openid; |
| | | private String sp_openid; |
| | | private String sub_openid; |
| | | } |
| | | |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @Data |
| | | public static class PromotionDetail implements Serializable{ |
| | | private String coupon_id; |
| | | private String name; |
| | | private String scope; |
| | | private String type; |
| | | private int amount; |
| | | private String stock_id; |
| | | private int wechatpay_contribute; |
| | | |
| | | private int merchant_contribute; |
| | | private int other_contribute; |
| | | private String currency; |
| | | |
| | | private List<GoodsDetail> goods_detail; |
| | | |
| | | } |
| | | |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @Data |
| | | public static class SceneInfo implements Serializable{ |
| | | /** |
| | | * 商户端设备号 |
| | | */ |
| | | private String device_id; |
| | | } |
| | | |
| | | |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | @Data |
| | | public static class SubOrders implements Serializable{ |
| | | private String mchid; |
| | | private String trade_type; |
| | | private String trade_state; |
| | | private String trade_state_desc; |
| | | private String bank_type; |
| | | private String attach; |
| | | private String success_time; |
| | | private String transaction_id; |
| | | private String out_trade_no; |
| | | private String sub_mchid; |
| | | private Amount amount; |
| | | /** |
| | | * 优惠功能,享受优惠时返回该字段 |
| | | */ |
| | | private List<PromotionDetail> promotion_detail; |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.utils; |
| | | |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.ruoyi.system.utils.wx.tools.WebUtils; |
| | | import com.ruoyi.system.utils.wx.tools.WxJsonUtils; |
| | | import com.ruoyi.system.utils.wx.tools.WxUtils; |
| | | import com.ruoyi.system.wxPay.model.WxCloseOrderModel; |
| | | import com.ruoyi.system.wxPay.model.WxPaymentInfoModel; |
| | | import com.ruoyi.system.wxPay.model.WxPaymentRefundModel; |
| | | import com.ruoyi.system.wxPay.resp.NotifyV3PayDecodeRespBody; |
| | | import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; |
| | | import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; |
| | | import com.wechat.pay.contrib.apache.httpclient.notification.Notification; |
| | | import com.wechat.pay.contrib.apache.httpclient.notification.NotificationHandler; |
| | | import com.wechat.pay.contrib.apache.httpclient.notification.NotificationRequest; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.http.client.config.RequestConfig; |
| | | import org.apache.http.client.methods.*; |
| | | import org.apache.http.conn.ConnectTimeoutException; |
| | | import org.apache.http.entity.StringEntity; |
| | | import org.apache.http.impl.client.CloseableHttpClient; |
| | | import org.apache.http.util.EntityUtils; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import javax.servlet.http.HttpServletResponse; |
| | | import java.io.IOException; |
| | | import java.io.PrintWriter; |
| | | import java.net.SocketTimeoutException; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | import java.util.stream.Stream; |
| | | |
| | | import static com.wechat.pay.contrib.apache.httpclient.constant.WechatPayHttpHeaders.*; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName WxWifiV3Pay |
| | | * @Description |
| | | * @date 2021-11-13 21:10 |
| | | */ |
| | | @Slf4j |
| | | public abstract class WxAbstractPay { |
| | | /** |
| | | * 请求成功相应码 |
| | | */ |
| | | private static final int STATUS_CODE = 200; |
| | | /** |
| | | * 请求成功相应码 |
| | | */ |
| | | private static final int OTHER_STATUS_CODE = 204; |
| | | /** |
| | | * 请求根地址 |
| | | */ |
| | | private static final String HOST = "https://api.mch.weixin.qq.com"; |
| | | |
| | | private static RuntimeException parameterError(String message, Object... args) { |
| | | message = String.format(message, args); |
| | | return new IllegalArgumentException("parameter error: " + message); |
| | | } |
| | | |
| | | /** |
| | | * 封装基础数据 |
| | | * |
| | | * @param requestBody |
| | | * @param notifyUrl |
| | | * @return |
| | | */ |
| | | protected String buildBaseParam(WxPaymentInfoModel requestBody, String notifyUrl) { |
| | | // 封装基础数据 |
| | | requestBody.setNotify_url(notifyUrl + requestBody.getNotify_url()); |
| | | String reqBody = WxJsonUtils.toJsonString(requestBody); |
| | | return reqBody; |
| | | } |
| | | |
| | | /** |
| | | * 微信调起支付参数 |
| | | * 返回参数如有不理解 请访问微信官方文档 |
| | | * https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter4_1_4.shtml |
| | | * |
| | | * @param prepayId 微信下单返回的prepay_id |
| | | * @param appId 应用ID(appid) |
| | | * @return 当前调起支付所需的参数 |
| | | * @throws Exception |
| | | */ |
| | | protected Map<String, Object> wxTuneUp(PrivateKeySigner privateKeySigner, String appId, String prepayId) { |
| | | String timeStamp = String.valueOf(System.currentTimeMillis() / 1000); |
| | | String nonceStr = WxUtils.generateNonceStr(); |
| | | String packageStr = "prepay_id=" + prepayId; |
| | | //加载签名 |
| | | String signStr = Stream.of(appId, timeStamp, nonceStr, packageStr).collect(Collectors.joining("\n", "", "\n")); |
| | | String packageSign = privateKeySigner.sign(signStr.getBytes(StandardCharsets.UTF_8)).getSign(); |
| | | Map<String, Object> map = new HashMap<>(6); |
| | | map.put("appId", appId); |
| | | map.put("timeStamp", timeStamp); |
| | | map.put("nonceStr", nonceStr); |
| | | map.put("package", packageStr); |
| | | map.put("signType", "RSA"); |
| | | map.put("paySign", packageSign); |
| | | return map; |
| | | } |
| | | |
| | | /** |
| | | * 构建方法请求 |
| | | * |
| | | * @param uri |
| | | * @param socketTimeout |
| | | * @param connectTimeout |
| | | * @return |
| | | */ |
| | | protected HttpGet requestGet(String uri, int socketTimeout, int connectTimeout) { |
| | | //请求URL |
| | | HttpGet httpGet = new HttpGet(HOST + uri); |
| | | RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout) |
| | | .setConnectTimeout(connectTimeout).build(); |
| | | httpGet.setConfig(requestConfig); |
| | | httpGet.setHeader("Content-type", "application/json"); |
| | | httpGet.setHeader("Accept", "application/json"); |
| | | return httpGet; |
| | | } |
| | | |
| | | /** |
| | | * 构建方法请求 |
| | | * |
| | | * @param uri |
| | | * @param socketTimeout |
| | | * @param connectTimeout |
| | | * @param reqdata |
| | | * @return |
| | | */ |
| | | protected HttpPost requestPost(String uri, int socketTimeout, int connectTimeout, String reqdata) { |
| | | //请求URL |
| | | HttpPost httpPost = new HttpPost(HOST + uri); |
| | | RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(socketTimeout) |
| | | .setConnectTimeout(connectTimeout).build(); |
| | | httpPost.setConfig(requestConfig); |
| | | StringEntity entity = new StringEntity(reqdata, StandardCharsets.UTF_8); |
| | | entity.setContentType("application/json"); |
| | | httpPost.setEntity(entity); |
| | | httpPost.setHeader("Accept", "application/json"); |
| | | return httpPost; |
| | | } |
| | | |
| | | public abstract <T> T verifyNotify(HttpServletRequest request, TypeReference<T> valueTypeRef) throws Exception; |
| | | |
| | | /** |
| | | * 接收回调 |
| | | * |
| | | * @param request |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | public <T> T verifyNotify(HttpServletRequest request, Verifier verifier, String apiKey, TypeReference<T> valueTypeRef) throws Exception { |
| | | String body = WxUtils.streamBodyByReceive(request); |
| | | String requestId = request.getHeader(REQUEST_ID); |
| | | String[] headers = {WECHAT_PAY_SERIAL, WECHAT_PAY_SIGNATURE, WECHAT_PAY_NONCE, WECHAT_PAY_TIMESTAMP}; |
| | | String value; |
| | | // 验签必须参数检验 |
| | | for (String headerName : headers) { |
| | | value = request.getHeader(headerName); |
| | | if (value == null || "".equals(value)) { |
| | | throw parameterError("empty [%s], request-id=[%s]", headerName, requestId); |
| | | } |
| | | } |
| | | String serial = request.getHeader(WECHAT_PAY_SERIAL); |
| | | String signature = request.getHeader(WECHAT_PAY_SIGNATURE); |
| | | String timestamp = request.getHeader(WECHAT_PAY_TIMESTAMP); |
| | | String nonce = request.getHeader(WECHAT_PAY_NONCE); |
| | | // 构建request,传入必要参数 |
| | | NotificationRequest notificationRequest = new NotificationRequest.Builder().withSerialNumber(serial) |
| | | .withNonce(nonce) |
| | | .withTimestamp(timestamp) |
| | | .withSignature(signature) |
| | | .withBody(body) |
| | | .build(); |
| | | NotificationHandler handler = new NotificationHandler(verifier, apiKey.getBytes(StandardCharsets.UTF_8)); |
| | | // 验签和解析请求体 |
| | | Notification notification = handler.parse(notificationRequest); |
| | | assert notification != null; |
| | | T respBody = WxJsonUtils.parseObject(notification.getDecryptData(), valueTypeRef); |
| | | return respBody; |
| | | } |
| | | |
| | | /** |
| | | * 订单查询 |
| | | * |
| | | * @param httpClient |
| | | * @param socketTimeout |
| | | * @param connectTimeout |
| | | * @param url |
| | | * @return com.abl.biz.center.payment.wx.v3.NotifyV3PayDecodeRespBody |
| | | * @author xiaochen |
| | | * @date 2021-12-20 17:12 |
| | | */ |
| | | protected NotifyV3PayDecodeRespBody query(CloseableHttpClient httpClient, int socketTimeout, int connectTimeout, String url) { |
| | | //请求URL |
| | | HttpGet httpGet = requestGet( |
| | | url |
| | | , socketTimeout |
| | | , connectTimeout); |
| | | CloseableHttpResponse response = null; |
| | | try { |
| | | response = httpClient.execute(httpGet); |
| | | int statusCode = response.getStatusLine().getStatusCode(); |
| | | String respBodyStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); |
| | | if (WxUtils.getLogger().isDebugEnabled()) { |
| | | WxUtils.debug("请求成功:{}", respBodyStr); |
| | | } |
| | | if(404 == statusCode){ |
| | | return null; |
| | | } |
| | | // 成功相应 |
| | | if (STATUS_CODE == statusCode || OTHER_STATUS_CODE == statusCode ) { |
| | | NotifyV3PayDecodeRespBody body = WxJsonUtils.parseObject(respBodyStr, NotifyV3PayDecodeRespBody.class); |
| | | return body; |
| | | } else { |
| | | WxUtils.error("failed,resp code = {},return body = {}", statusCode, respBodyStr); |
| | | throw new RuntimeException(respBodyStr); |
| | | } |
| | | } catch (ConnectTimeoutException e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("接口超时"); |
| | | } catch (SocketTimeoutException e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("读取接口数据超时"); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("接口请求失败,请尝试检查网络环境或请求接口是否能正常访问"); |
| | | } finally { |
| | | // 关闭响应 |
| | | try { |
| | | if (response != null) { |
| | | //关闭结果集 |
| | | response.getEntity().getContent().close(); |
| | | response.close(); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new RuntimeException("关闭流异常"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 子级实现 |
| | | * |
| | | * @param out_trade_no |
| | | * @return |
| | | */ |
| | | public abstract NotifyV3PayDecodeRespBody query(String out_trade_no); |
| | | |
| | | |
| | | /** |
| | | * 订单退款 |
| | | * |
| | | * @param refundModel |
| | | * @return |
| | | */ |
| | | public abstract Map<String, Object> refund(WxPaymentRefundModel refundModel); |
| | | |
| | | |
| | | |
| | | public abstract String close(String out_trade_no); |
| | | |
| | | /** |
| | | * 订单退款 |
| | | * |
| | | * @param httpClient |
| | | * @param uri |
| | | * @param httpReadTimeoutMs |
| | | * @param httpConnectTimeoutMs |
| | | * @param refundModel |
| | | * @return |
| | | */ |
| | | public Map<String, Object> refund(CloseableHttpClient httpClient, |
| | | String uri, |
| | | int httpReadTimeoutMs, |
| | | int httpConnectTimeoutMs, |
| | | WxPaymentRefundModel refundModel) { |
| | | String reqBody = WxJsonUtils.toJsonString(refundModel); |
| | | //请求URL |
| | | HttpEntityEnclosingRequestBase httpPost = requestPost( |
| | | uri |
| | | , httpReadTimeoutMs |
| | | , httpConnectTimeoutMs, reqBody); |
| | | String repBody = result(httpClient, httpPost); |
| | | Map<String, Object> body = WxJsonUtils.parseObject(repBody, Map.class); |
| | | return body; |
| | | } |
| | | |
| | | /** |
| | | * 请求结果 |
| | | * |
| | | * @param request |
| | | * @return |
| | | */ |
| | | protected String result(CloseableHttpClient httpClient, HttpRequestBase request) { |
| | | CloseableHttpResponse response = null; |
| | | try { |
| | | response = httpClient.execute(request); |
| | | int statusCode = response.getStatusLine().getStatusCode(); |
| | | String respBodyStr = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); |
| | | if (WxUtils.getLogger().isDebugEnabled()) { |
| | | WxUtils.debug("请求成功:{}", respBodyStr); |
| | | } |
| | | // 成功相应 |
| | | if (STATUS_CODE == statusCode || OTHER_STATUS_CODE == statusCode) { |
| | | return respBodyStr; |
| | | } else { |
| | | WxUtils.error("failed,resp code = {},return body = {}", statusCode, respBodyStr); |
| | | throw new RuntimeException(respBodyStr); |
| | | } |
| | | } catch (ConnectTimeoutException e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("接口超时"); |
| | | } catch (SocketTimeoutException e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("读取接口数据超时"); |
| | | } catch (IOException e) { |
| | | e.printStackTrace(); |
| | | throw new RuntimeException("接口请求失败,请尝试检查网络环境或请求接口是否能正常访问"); |
| | | } finally { |
| | | // 关闭响应 |
| | | try { |
| | | if (response != null) { |
| | | //关闭结果集 |
| | | response.getEntity().getContent().close(); |
| | | response.close(); |
| | | } |
| | | } catch (IOException e) { |
| | | throw new RuntimeException("关闭流异常"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 微信结果确认应答 |
| | | * 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。 |
| | | * |
| | | * @param |
| | | * @throws IOException |
| | | */ |
| | | public void ack() throws IOException { |
| | | ack(true, null); |
| | | } |
| | | |
| | | /** |
| | | * 微信结果确认应答 |
| | | * 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx。 |
| | | * |
| | | * @param |
| | | * @throws IOException |
| | | */ |
| | | public void ack(boolean ackSucc, String erroMsg) throws IOException { |
| | | HttpServletResponse response = WebUtils.response(); |
| | | PrintWriter writer = response.getWriter(); |
| | | if (ackSucc) { |
| | | log.info("响应微信回调成功!"); |
| | | response.setStatus(200); |
| | | writer.write("{\"code\": \"SUCCESS\",\"message\": \"成功\"}"); |
| | | |
| | | } else { |
| | | log.info("响应微信回调失败:{}!", erroMsg); |
| | | response.setStatus(500); |
| | | writer.write("{\"code\": \"FAIL\",\"message\": " + (StringUtils.hasLength(erroMsg) ? erroMsg : "业务处理失败") + "}"); |
| | | } |
| | | // 关闭流 |
| | | if (Objects.nonNull(writer)) { |
| | | writer.close(); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 关闭订单 |
| | | * @param httpClient |
| | | * @param uri |
| | | * @param httpReadTimeoutMs |
| | | * @param httpConnectTimeoutMs |
| | | * @param closeModel |
| | | * @return |
| | | */ |
| | | public String close(CloseableHttpClient httpClient, |
| | | String uri, |
| | | int httpReadTimeoutMs, |
| | | int httpConnectTimeoutMs, |
| | | WxCloseOrderModel closeModel) { |
| | | String reqBody = WxJsonUtils.toJsonString(closeModel); |
| | | //请求URL |
| | | HttpEntityEnclosingRequestBase httpPost = requestPost( |
| | | uri |
| | | , httpReadTimeoutMs |
| | | , httpConnectTimeoutMs, reqBody); |
| | | String repBody = result(httpClient, httpPost); |
| | | return repBody; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.utils; |
| | | |
| | | import org.joda.time.DateTime; |
| | | import org.joda.time.DateTimeZone; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.time.Instant; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.time.ZonedDateTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.util.Date; |
| | | import java.util.Objects; |
| | | import java.util.TimeZone; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName WxTimeUtils |
| | | * @Description |
| | | * @date 2021-12-16 16:07 |
| | | */ |
| | | public class WxTimeUtils { |
| | | /** |
| | | * 系统默认时区 |
| | | */ |
| | | private static final ZoneId ZONE = ZoneId.systemDefault(); |
| | | /** |
| | | * yyyy-MM-dd'T'HH:mm:ssxxx 比如:2020-05-23T17:06:30+08:00 0时区时末尾 为+00:00 |
| | | */ |
| | | public static final DateTimeFormatter YYYY_MM_DD_T_HH_MM_SS_XXX_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssxxx"); |
| | | /** |
| | | * yyyy-MM-dd HH:mm:ss 比如:2020-05-23 17:06:30 |
| | | */ |
| | | public static final DateTimeFormatter YYYY_MM_DD_HH_MM_SS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(ZONE); |
| | | |
| | | /** |
| | | * 时间转 TimeZone |
| | | * |
| | | * @param date |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | public static String dateToTimeZone(Date date) throws Exception { |
| | | String time; |
| | | if (date == null) { |
| | | throw new Exception("date is not null"); |
| | | } |
| | | ZonedDateTime zonedDateTime = toZonedDateTime(date); |
| | | time = format(zonedDateTime, YYYY_MM_DD_T_HH_MM_SS_XXX_FMT); |
| | | return time; |
| | | } |
| | | |
| | | /** |
| | | * Date转ZonedDateTime,时区为系统默认时区 |
| | | * |
| | | * @param date Date |
| | | * @return ZonedDateTime |
| | | */ |
| | | public static ZonedDateTime toZonedDateTime(Date date) { |
| | | Objects.requireNonNull(date, "date"); |
| | | return Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()); |
| | | } |
| | | |
| | | /** |
| | | * 根据 formatter格式化 zonedDateTime |
| | | * |
| | | * @param zonedDateTime ZonedDateTime |
| | | * @param formatter DateTimeFormatter |
| | | * @return String |
| | | */ |
| | | public static String format(ZonedDateTime zonedDateTime, DateTimeFormatter formatter) { |
| | | Objects.requireNonNull(zonedDateTime, "zonedDateTime"); |
| | | Objects.requireNonNull(formatter, "formatter"); |
| | | return zonedDateTime.format(formatter); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * TimeZone 时间转标准时间 |
| | | * |
| | | * @param date |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | public static String toTimeZoneStr(String date) { |
| | | String time; |
| | | if (!StringUtils.hasLength(date)) { |
| | | throw new RuntimeException("str is not null"); |
| | | } |
| | | ZonedDateTime zonedDateTime = parseToZonedDateTime(date, YYYY_MM_DD_T_HH_MM_SS_XXX_FMT); |
| | | if (zonedDateTime == null) { |
| | | throw new RuntimeException("str to zonedDateTime fail"); |
| | | } |
| | | time = zonedDateTime.format(YYYY_MM_DD_HH_MM_SS_FMT); |
| | | return time; |
| | | } |
| | | |
| | | /** |
| | | * 转date |
| | | * |
| | | * @param date |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | public static Date toDate(String date) { |
| | | String time; |
| | | if (!StringUtils.hasLength(date)) { |
| | | throw new RuntimeException("str is not null"); |
| | | } |
| | | ZonedDateTime zonedDateTime = parseToZonedDateTime(date, YYYY_MM_DD_T_HH_MM_SS_XXX_FMT); |
| | | if (zonedDateTime == null) { |
| | | throw new RuntimeException("str to zonedDateTime fail"); |
| | | } |
| | | return Date.from(zonedDateTime.toInstant()); |
| | | } |
| | | |
| | | /** |
| | | * str --> Date |
| | | * |
| | | * @param date |
| | | * @return java.util.Date |
| | | * @author xiaochen |
| | | * @date 2022-01-20 18:20 |
| | | */ |
| | | public static Date toRfc3339Date(String date) { |
| | | DateTime dt2 = new DateTime(date); |
| | | return dt2.toDate(); |
| | | } |
| | | |
| | | /** |
| | | * 将 Date 转为 LocalDateTime |
| | | * |
| | | * @param date |
| | | * @return java.time.LocalDateTime; |
| | | */ |
| | | public static LocalDateTime dateToLocalDateTime(Date date) { |
| | | return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * str --> Date |
| | | * |
| | | * @param date |
| | | * @return java.util.Date |
| | | * @author xiaochen |
| | | * @date 2022-01-20 18:20 |
| | | */ |
| | | public static String toRfc3339Str(Date date) { |
| | | DateTime dt1 = new DateTime(new Date(), DateTimeZone.forTimeZone(TimeZone.getTimeZone("Asia/Shanghai"))); |
| | | return dt1.toString(); |
| | | } |
| | | |
| | | /** |
| | | * 根据 formatter解析为 ZonedDateTime |
| | | * |
| | | * @param text 待解析字符串 |
| | | * @param formatter DateTimeFormatter |
| | | * @return ZonedDateTime |
| | | */ |
| | | public static ZonedDateTime parseToZonedDateTime(String text, DateTimeFormatter formatter) { |
| | | return ZonedDateTime.parse(text, formatter); |
| | | } |
| | | } |
New file |
| | |
| | | package com.ruoyi.system.wxPay.utils; |
| | | |
| | | import com.fasterxml.jackson.core.JsonProcessingException; |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.JsonNode; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.ruoyi.system.utils.wx.tools.WxUtils; |
| | | import com.ruoyi.system.wxPay.model.WeixinPayProperties; |
| | | import com.ruoyi.system.wxPay.model.WxCloseOrderModel; |
| | | import com.ruoyi.system.wxPay.model.WxPaymentInfoModel; |
| | | import com.ruoyi.system.wxPay.model.WxPaymentRefundModel; |
| | | import com.ruoyi.system.wxPay.resp.NotifyV3PayDecodeRespBody; |
| | | import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; |
| | | import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; |
| | | import com.wechat.pay.contrib.apache.httpclient.auth.Verifier; |
| | | import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials; |
| | | import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; |
| | | import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager; |
| | | import com.wechat.pay.contrib.apache.httpclient.util.PemUtil; |
| | | import lombok.Getter; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; |
| | | import org.apache.http.impl.client.CloseableHttpClient; |
| | | |
| | | import javax.servlet.http.HttpServletRequest; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.PrivateKey; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * @author xiaochen |
| | | * @ClassName WxWifiV3Pay |
| | | * @Description |
| | | * @date 2021-11-13 21:10 |
| | | */ |
| | | @Slf4j |
| | | public class WxV3Pay extends WxAbstractPay { |
| | | @Getter |
| | | private WeixinPayProperties config; |
| | | @Getter |
| | | private Verifier verifier; |
| | | private WechatPayHttpClientBuilder builder; |
| | | @Getter |
| | | private CloseableHttpClient httpClient; |
| | | private PrivateKeySigner privateKeySigner; |
| | | private WechatPay2Validator validator; |
| | | @Getter |
| | | private PrivateKey privateKey; |
| | | |
| | | /** |
| | | * 初始化 |
| | | * |
| | | * @param config |
| | | */ |
| | | public WxV3Pay(WeixinPayProperties config) { |
| | | // 检查v3支付配置信息 |
| | | if (WxUtils.getLogger().isDebugEnabled()) { |
| | | WxUtils.debug("开始检查v3支付配置信息...."); |
| | | } |
| | | try { |
| | | this.config = config; |
| | | checkWxConfig(); |
| | | this.privateKey = PemUtil.loadPrivateKey(config.getV3().getPrivateKeyStream()); |
| | | this.privateKeySigner = new PrivateKeySigner(config.getV3().getMchSerialNo(), privateKey); |
| | | // 获取证书管理器实例 |
| | | CertificatesManager certificatesManager = CertificatesManager.getInstance(); |
| | | // 向证书管理器增加需要自动更新平台证书的商户信息 |
| | | certificatesManager.putMerchant(config.getMchId(), new WechatPay2Credentials(config.getMchId(), |
| | | this.privateKeySigner), config.getV3().getApiKey().getBytes(StandardCharsets.UTF_8)); |
| | | // 从证书管理器中获取verifier |
| | | this.verifier = certificatesManager.getVerifier(config.getMchId()); |
| | | this.validator = new WechatPay2Validator(verifier); |
| | | this.builder = WechatPayHttpClientBuilder.create() |
| | | .withMerchant(config.getMchId(), config.getV3().getMchSerialNo(), this.privateKey) |
| | | .withValidator(this.validator); |
| | | this.httpClient = this.builder.build(); |
| | | } catch (Exception e) { |
| | | // 打印异常信息 |
| | | e.printStackTrace(); |
| | | WxUtils.warn("检查v3支付配置信息出现错误,直连商户商户号:{},{}", config.getMchId(), e.getMessage()); |
| | | return; |
| | | } |
| | | if (WxUtils.getLogger().isDebugEnabled()) { |
| | | WxUtils.debug("检查v3支付配置信息完成,未出现异常...."); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查支付配置信息 |
| | | * |
| | | * @throws Exception |
| | | */ |
| | | private void checkWxConfig() throws Exception { |
| | | if (this.config == null) { |
| | | throw new Exception("config is null"); |
| | | } |
| | | if (config.getMchId() == null || config.getMchId().trim().length() == 0) { |
| | | throw new Exception("MchID in config is empty"); |
| | | } |
| | | if (config.getV3().getMchSerialNo() == null) { |
| | | throw new Exception("mchSerialNo in config is empty"); |
| | | } |
| | | if (config.getV3().getPrivateKeyStream() == null) { |
| | | throw new Exception("cert stream in config is empty"); |
| | | } |
| | | if (this.config.getHttpConnectTimeoutMs() < 10) { |
| | | throw new Exception("http connect timeout is too small"); |
| | | } |
| | | if (this.config.getHttpReadTimeoutMs() < 10) { |
| | | throw new Exception("http read timeout is too small"); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * jsApi下单 |
| | | * |
| | | * @param tradeNo 订单号 |
| | | * @param amount 金额 分 |
| | | * @param openid openid |
| | | * @param description 订单描述 |
| | | * @return java.util.Map<java.lang.String, java.lang.Object> |
| | | * @author xiaochen |
| | | * @date 2022-03-22 12:47 |
| | | */ |
| | | public Map<String, Object> jsApi(String tradeNo, Integer amount, String openid, String notify_url, String description) { |
| | | WxPaymentInfoModel requestBody = WxPaymentInfoModel.builder() |
| | | .mchid(this.config.getMchId()) |
| | | .appid(this.config.getAppId()) |
| | | .description(description) |
| | | .out_trade_no(tradeNo) |
| | | .notify_url(notify_url) |
| | | // .attach("") |
| | | .amount(WxPaymentInfoModel.Amount.builder().total(amount).build()) |
| | | .payer(WxPaymentInfoModel.Payer.builder().openid(openid).build()) |
| | | // 分不分账 |
| | | // .settle_info(WxPaymentInfoModel.SettleInfo.builder().profit_sharing(true).build()) |
| | | .build(); |
| | | // 封装基础数据 |
| | | String reqBody = buildBaseParam(requestBody |
| | | , this.config.getV3().getNotifyPayUrl()); |
| | | //请求URL |
| | | HttpEntityEnclosingRequestBase httpPost = requestPost( |
| | | "/v3/pay/transactions/jsapi" |
| | | , this.config.getHttpReadTimeoutMs() |
| | | , this.config.getHttpConnectTimeoutMs() |
| | | , reqBody); |
| | | String repBody = result(httpClient, httpPost); |
| | | ObjectMapper om = new ObjectMapper(); |
| | | try { |
| | | JsonNode rootNode = om.readTree(repBody); |
| | | String prepayId = rootNode.path("prepay_id").asText(); |
| | | return wxTuneUp(this.privateKeySigner, requestBody.getAppid(), prepayId); |
| | | } catch (JsonProcessingException e) { |
| | | throw new RuntimeException("获取支付数据错误!"); |
| | | } |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 接收回调 |
| | | * |
| | | * @param request |
| | | * @return |
| | | * @throws Exception |
| | | */ |
| | | @Override |
| | | public <T> T verifyNotify(HttpServletRequest request, TypeReference<T> valueTypeRef) throws Exception { |
| | | return verifyNotify(request, this.verifier, this.config.getV3().getApiKey(), valueTypeRef); |
| | | } |
| | | |
| | | /** |
| | | * 订单查询 |
| | | * |
| | | * @param out_trade_no |
| | | * @return com.abl.biz.center.payment.wx.v3.NotifyV3PayDecodeRespBody |
| | | * @author xiaochen |
| | | * @date 2021-12-20 16:47 |
| | | */ |
| | | @Override |
| | | public NotifyV3PayDecodeRespBody query(String out_trade_no) { |
| | | String url = |
| | | String.format("/v3/pay/transactions/out-trade-no/%s", out_trade_no) + String.format("?mchid=%s", this.getConfig().getMchId()); |
| | | return query(this.httpClient, this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), url); |
| | | } |
| | | |
| | | /** |
| | | * 退款 |
| | | * |
| | | * @param refundModel |
| | | * @return java.util.Map<java.lang.String, java.lang.Object> |
| | | * @author xiaochen |
| | | */ |
| | | @Override |
| | | public Map<String, Object> refund(WxPaymentRefundModel refundModel) { |
| | | refundModel.setNotify_url(this.config.getV3().getNotifyRefundUrl() + refundModel.getNotify_url()); |
| | | return refund(this.httpClient, "/v3/refund/domestic/refunds", this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), refundModel); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 关闭订单 |
| | | * @param out_trade_no |
| | | * @return |
| | | */ |
| | | @Override |
| | | public String close(String out_trade_no) { |
| | | String uri = String.format("/v3/pay/transactions/out-trade-no/%s/close", out_trade_no); |
| | | WxCloseOrderModel wxCloseOrderModel = new WxCloseOrderModel(); |
| | | wxCloseOrderModel.setMchid(this.config.getMchId()); |
| | | return close(this.httpClient, uri, this.config.getHttpReadTimeoutMs(), this.config.getHttpConnectTimeoutMs(), wxCloseOrderModel); |
| | | } |
| | | } |
| | |
| | | <result column="constitution_names" property="constitutionNames" /> |
| | | <result column="symptom_name" property="symptomName" /> |
| | | <result column="tongue_feature" property="tongueFeature" /> |
| | | <result column="face_feature" property="faceFeature" /> |
| | | <result column="disease_risks" property="diseaseRisks" /> |
| | | <result column="is_pay" property="isPay" /> |
| | | <result column="create_time" property="createTime" /> |
| | |
| | | <sql id="Base_Column_List"> |
| | | id, app_user_id,clinic_id, inspection_info, team_code, team_name, device_code, device_name, person_name, |
| | | person_sex, person_age, person_phone, person_height, person_weight, check_time, pdf_url, health_index, |
| | | constitution_names, symptom_name, tongue_feature, disease_risks,is_pay, create_time, update_time, |
| | | constitution_names, symptom_name, tongue_feature,face_feature, disease_risks,is_pay, create_time, update_time, |
| | | create_by, update_by, disabled, create_id |
| | | </sql> |
| | | <select id="pageList" resultType="com.ruoyi.system.vo.TSysInspectionVO"> |
New file |
| | |
| | | <?xml version="1.0" encoding="UTF-8"?> |
| | | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
| | | <mapper namespace="com.ruoyi.system.mapper.TSysOtherConfigMapper"> |
| | | |
| | | <!-- 通用查询映射结果 --> |
| | | <resultMap id="BaseResultMap" type="com.ruoyi.system.model.TSysOtherConfig"> |
| | | <id column="id" property="id" /> |
| | | <result column="procurement_conditions" property="procurementConditions" /> |
| | | <result column="points_expiration" property="pointsExpiration" /> |
| | | <result column="expira_unit" property="expiraUnit" /> |
| | | <result column="msg_count" property="msgCount" /> |
| | | <result column="ai_price" property="aiPrice" /> |
| | | <result column="delivery_time" property="deliveryTime" /> |
| | | <result column="create_time" property="createTime" /> |
| | | <result column="update_time" property="updateTime" /> |
| | | <result column="create_by" property="createBy" /> |
| | | <result column="update_by" property="updateBy" /> |
| | | <result column="disabled" property="disabled" /> |
| | | <result column="create_id" property="createId" /> |
| | | </resultMap> |
| | | |
| | | <!-- 通用查询结果列 --> |
| | | <sql id="Base_Column_List"> |
| | | id, procurement_conditions, points_expiration, expira_unit, msg_count, ai_price, delivery_time, create_time, update_time, create_by, update_by, disabled, create_id |
| | | </sql> |
| | | |
| | | </mapper> |