From e7e7a3a2e5cd9aefa3e71dd05bbc5f6f4b88a1c0 Mon Sep 17 00:00:00 2001 From: 无关风月 <443237572@qq.com> Date: 星期一, 28 四月 2025 09:57:36 +0800 Subject: [PATCH] bug修改 --- cloud-server-activity/src/main/resources/mapper/HuiminCardMapper.xml | 2 cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java | 298 ++++++++ cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardVO.java | 8 cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java | 56 + cloud-server-activity/src/main/resources/mapper/PayHuiminMapper.xml | 1 cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java | 623 +++++++++++++++-- cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java | 12 cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java | 76 ++ cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java | 10 cloud-server-communityWorldCup/src/main/resources/sharding-jdbc.properties | 30 cloud-server-activity/src/main/java/com/dsh/activity/model/response/MyHuiminCardDetailVO.java | 2 cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java | 177 +++++ cloud-server-activity/pom.xml | 17 cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WechatPaymentService.java | 56 + cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatPaymentServiceImpl.java | 315 +++++++++ cloud-server-activity/src/main/resources/sharding-jdbc.properties | 30 cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardStudentVO.java | 23 cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java | 32 cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java | 221 ++++++ cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java | 57 + 20 files changed, 1,911 insertions(+), 135 deletions(-) diff --git a/cloud-server-activity/pom.xml b/cloud-server-activity/pom.xml index a9ab517..7fd6c3c 100644 --- a/cloud-server-activity/pom.xml +++ b/cloud-server-activity/pom.xml @@ -14,6 +14,17 @@ <name>福利</name> <description>福利</description> <dependencies> + <!-- 微信支付V3 目前新版本--> + <dependency> + <groupId>com.github.wechatpay-apiv3</groupId> + <artifactId>wechatpay-apache-httpclient</artifactId> + <version>0.4.9</version> + </dependency> + <dependency> + <groupId>joda-time</groupId> + <artifactId>joda-time</artifactId> + <version>2.10.10</version> + </dependency> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> @@ -94,6 +105,12 @@ <artifactId>geodesy</artifactId> <version>1.1.3</version> </dependency> + <dependency> + <groupId>com.github.wechatpay-apiv3</groupId> + <artifactId>wechatpay-java-core</artifactId> + <version>0.2.12</version> + <scope>compile</scope> + </dependency> </dependencies> <build> diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java b/cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java index ba0b5ff..37ce23c 100644 --- a/cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java +++ b/cloud-server-activity/src/main/java/com/dsh/activity/controller/HuiminController.java @@ -1,5 +1,6 @@ package com.dsh.activity.controller; +import cn.hutool.core.collection.CollUtil; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; @@ -37,8 +38,11 @@ import javax.annotation.Resource; import java.math.BigDecimal; import java.text.SimpleDateFormat; +import java.time.DayOfWeek; +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.format.TextStyle; import java.util.*; import java.util.stream.Collectors; @@ -94,35 +98,37 @@ @ApiImplicitParam(value = "绑定学员ids多,个逗号拼接", name = "studentIds", dataType = "String", required = true), @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") }) - public ResultUtil<THuiminCard> payHuiminCard(Integer id,Integer payType,String studentIds) { + public ResultUtil<THuiminCard> payHuiminCard(Integer id, Integer payType, String studentIds) { try { Integer uid = tokenUtil.getUserIdFormRedis(); if (null == uid) { return ResultUtil.tokenErr(); } THuiminCard huiminCard = huiminCardService.getById(id); - if (huiminCard.getEndTime()!=null && huiminCard.getEndTime().before(new Date())){ + if (huiminCard.getEndTime() != null && huiminCard.getEndTime().before(new Date())) { return ResultUtil.error("该惠民卡已过期"); } - if (huiminCard.getGrantCount()!=null){ - if (huiminCard.getGrantCount()<=payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId()) - .eq(TPayHuimin::getStatus,2).count()){ + if (huiminCard.getGrantCount() != null) { + if (huiminCard.getGrantCount() <= payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId()) + .eq(TPayHuimin::getStatus, 2).count()) { return ResultUtil.error("该惠民卡已售完"); } } - if (huiminCard.getLimitCount()!=null){ - if (huiminCard.getLimitCount()<=payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId()) - .eq(TPayHuimin::getStatus,2) + if (huiminCard.getLimitCount() != null) { + if (huiminCard.getLimitCount() <= payHuiminService.lambdaQuery().eq(TPayHuimin::getCardId, huiminCard.getId()) + .eq(TPayHuimin::getStatus, 2) .eq(TPayHuimin::getAppUserId, uid) - .count()){ + .count()) { return ResultUtil.error("该惠民卡购买次数已达上限"); } } TPayHuimin tPayHuimin = new TPayHuimin(); - tPayHuimin.setSalesMoney(huiminCard.getSalesMoney()); + String[] split = studentIds.split(","); + + tPayHuimin.setSalesMoney(huiminCard.getSalesMoney().multiply(new BigDecimal(split.length)).setScale(2)); tPayHuimin.setAppUserId(uid); tPayHuimin.setStudentId(studentIds); - switch (huiminCard.getHuiMinType()){ + switch (huiminCard.getHuiMinType()) { case 1: // 年度卡 LocalDateTime localDateTime = LocalDateTime.now().plusDays(365); @@ -148,16 +154,16 @@ tPayHuimin.setCardId(huiminCard.getId()); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); tPayHuimin.setCode(sdf.format(new Date()) + UUIDUtil.getNumberRandom(5)); - System.err.println("支付数据"+tPayHuimin); + System.err.println("支付数据" + tPayHuimin); payHuiminService.save(tPayHuimin); BigDecimal bigDecimal = new BigDecimal(studentIds.split(",").length); - switch (payType){ + switch (payType) { case 1: - return payMoneyUtil.weixinpay("购买惠民卡"+"-"+0, "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().multiply(bigDecimal).toString(), + return payMoneyUtil.weixinpay("购买惠民卡" + "-" + 0, "", tPayHuimin.getCode(), tPayHuimin.getSalesMoney().toString(), "/base/huimin/callBack/weixinPayHuiminCallback", "APP", ""); case 2: - String string = tPayHuimin.getSalesMoney().multiply(bigDecimal).setScale(2).toString(); - return payMoneyUtil.alipay(smid,"购买惠民卡", "购买惠民卡", "", tPayHuimin.getCode(), string, + String string = tPayHuimin.getSalesMoney().toString(); + return payMoneyUtil.alipay(smid, "购买惠民卡", "购买惠民卡", "", tPayHuimin.getCode(), string, "/base/huimin/callBack/aliPayHuiminCallback"); } @@ -180,7 +186,7 @@ @ApiImplicitParam(value = "门店id", name = "storeId", dataType = "int", required = true), @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") }) - public ResultUtil<THuiminCard> getHuiminCardDetail(Integer id,Integer storeId) { + public ResultUtil<THuiminCard> getHuiminCardDetail(Integer id, Integer storeId) { try { Integer uid = tokenUtil.getUserIdFormRedis(); if (null == uid) { @@ -189,54 +195,54 @@ THuiminCard huiminCard = huiminCardService.getById(id); List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>() .eq(TPayHuimin::getAppUserId, uid) - .ge(TPayHuimin::getEndTime,new Date()) + .ge(TPayHuimin::getEndTime, new Date()) .eq(TPayHuimin::getStatus, 1)); List<TPayHuimin> collect = payHuimins.stream().filter(e -> e.getCardId().equals(huiminCard.getId())).collect(Collectors.toList()); - if (!collect.isEmpty()){ + if (!collect.isEmpty()) { huiminCard.setIsBuy(1); - }else{ + } else { huiminCard.setIsBuy(0); } - List<Store> stores = storeClient.queryStoreByIds(Collections.singletonList(storeId)); - if (!stores.isEmpty()){ - Store store = stores.get(0); - Integer operatorId = store.getOperatorId(); - if (operatorId==null||operatorId==0){ - // 平台门店 - THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() + List<Store> stores = storeClient.queryStoreByIds(Collections.singletonList(storeId)); + if (!stores.isEmpty()) { + Store store = stores.get(0); + Integer operatorId = store.getOperatorId(); + if (operatorId == null || operatorId == 0) { + // 平台门店 + THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() + .isNull(THuiminAgreement::getOperatorId)); + if (huiminAgreement != null) { + List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>() + .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId())); + huiminCard.setAgreementSettings(list); + } else { + huiminCard.setAgreementSettings(new ArrayList<>()); + + } + } else { + // 运营商门店 + THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() + .eq(THuiminAgreement::getOperatorId, operatorId)); + if (huiminAgreement != null) { + List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>() + .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId())); + huiminCard.setAgreementSettings(list); + } else { + THuiminAgreement huiminAgreement1 = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() .isNull(THuiminAgreement::getOperatorId)); - if (huiminAgreement!=null){ + if (huiminAgreement1 != null) { List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>() - .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId())); + .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement1.getId())); huiminCard.setAgreementSettings(list); - }else { + } else { huiminCard.setAgreementSettings(new ArrayList<>()); } - }else{ - // 运营商门店 - THuiminAgreement huiminAgreement = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() - .eq(THuiminAgreement::getOperatorId,operatorId)); - if (huiminAgreement!=null){ - List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>() - .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement.getId())); - huiminCard.setAgreementSettings(list); - }else { - THuiminAgreement huiminAgreement1 = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() - .isNull(THuiminAgreement::getOperatorId)); - if (huiminAgreement1!=null){ - List<THuiminAgreementSetting> list = huiminAgreementSettingService.list(new LambdaQueryWrapper<THuiminAgreementSetting>() - .eq(THuiminAgreementSetting::getAgreementId, huiminAgreement1.getId())); - huiminCard.setAgreementSettings(list); - }else { - huiminCard.setAgreementSettings(new ArrayList<>()); - - } - } } - }else{ - huiminCard.setAgreementSettings(new ArrayList<>()); } + } else { + huiminCard.setAgreementSettings(new ArrayList<>()); + } return ResultUtil.success(huiminCard); } catch (Exception e) { @@ -244,6 +250,7 @@ return ResultUtil.success(); } } + /** * 惠民卡富文本内容-惠民卡列表 */ @@ -256,39 +263,40 @@ @ApiImplicitParam(value = "门店id", name = "storeId", dataType = "int", required = true), @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") }) - public ResultUtil<HuiminAgreementVO> getHuiminAgreementAndList(Integer pageSize, Integer pageNo,Integer storeId) { + public ResultUtil<HuiminAgreementVO> getHuiminAgreementAndList(Integer pageSize, Integer pageNo, Integer storeId) { try { Integer uid = tokenUtil.getUserIdFormRedis(); if (null == uid) { return ResultUtil.tokenErr(); } - pageNo = (pageNo - 1) * pageSize; HuiminAgreementVO huiminAgreementVO = new HuiminAgreementVO(); - List<THuiminCard> cardList = huiminCardService.getHuiminAgreementAndList(pageNo,pageSize,storeId); + List<THuiminCard> cardList = huiminCardService.getHuiminAgreementAndList(pageNo, pageSize, storeId); + List<THuiminCard> list = huiminCardService.list(); + List<THuiminCard> cardListNolimit = huiminCardService.getHuiminAgreementAndListNolimit(storeId); - if (cardListNolimit.isEmpty()){ + if (cardListNolimit.isEmpty()) { // 没有配置惠民卡 展示富文本内容 huiminAgreementVO.setShowType(1); - }else{ + } else { huiminAgreementVO.setShowType(2); } List<Store> stores = storeClient.queryStoreByIds(Collections.singletonList(storeId)); Store store = stores.get(0); - if (store.getOperatorId()==null || store.getOperatorId()==0){ + if (store.getOperatorId() == null || store.getOperatorId() == 0) { // 平台 THuiminAgreement one = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() .isNull(THuiminAgreement::getOperatorId).last("limit 1")); huiminAgreementVO.setIntroduce(one.getStoreNoHuiminCardIntro()); - }else{ + } else { THuiminAgreement one = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() - .eq(THuiminAgreement::getOperatorId,store.getOperatorId()).last("limit 1")); - if (one == null){ + .eq(THuiminAgreement::getOperatorId, store.getOperatorId()).last("limit 1")); + if (one == null) { THuiminAgreement two = huiminAgreementService.getOne(new LambdaQueryWrapper<THuiminAgreement>() .isNull(THuiminAgreement::getOperatorId).last("limit 1")); huiminAgreementVO.setIntroduce(two.getStoreNoHuiminCardIntro()); - }else{ + } else { huiminAgreementVO.setIntroduce(one.getStoreNoHuiminCardIntro()); } @@ -297,37 +305,354 @@ List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>() .eq(TPayHuimin::getAppUserId, uid) - .ge(TPayHuimin::getEndTime,new Date()) - .in(TPayHuimin::getStatus, Arrays.asList(2,3))); + .ge(TPayHuimin::getEndTime, new Date()) + .eq(TPayHuimin::getStatus, 2)); + + List<HuiminCardVO> huiminCardVOS = new ArrayList<>(); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + List<TStudent> tStudents = studentClient.queryStudentList(uid); + List<Integer> integers = new ArrayList<>(); + + for (TPayHuimin payHuimin : payHuimins) { + if (integers.contains(payHuimin.getCardId())){ + continue; + } + THuiminCard huiminCard = list.stream().filter(e -> e.getId().equals(payHuimin.getCardId())).findFirst().orElse(null); + if (huiminCard == null) continue; + + HuiminCardVO huiminCardVO1 = new HuiminCardVO(); + huiminCardVO1.setId(huiminCard.getId()); + huiminCardVO1.setHuiminName(huiminCard.getHuiMinName()); + huiminCardVO1.setCover(huiminCard.getBuyCover()); + huiminCardVO1.setIsBuy(1); + String siteIds = ""; + if (huiminCard.getUseScope() == 2) { + siteIds = huiminCard.getUseIds(); + } else { + siteIds = siteClient.querySiteByStoreIds(huiminCard.getStoreIds()) + .stream() + .map(Site::getId) + .map(String::valueOf) + .collect(Collectors.joining(",")); + } + String storeIds = huiminCard.getStoreIds(); + if (storeIds.contains("2024")) { + siteIds = siteIds + ",32"; + } + if (storeIds.contains("1001")) { + siteIds = siteIds + ",3"; + } + // 门店ids + JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(","))); + // 场地ids + JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(","))); + // 可用时间 + LocalDate today = LocalDate.now(); + + int week = today.getDayOfWeek().getValue(); + + JSONArray time = JSONArray.parseArray(huiminCard.getUseWeeks()); + JSONArray jsonArray = new JSONArray(); + String string = LocalDate.now().toString(); + Date date = new Date(); + if (date.before(huiminCard.getEndTime()) && date.after(huiminCard.getStartTime())) { + for (Object o : time) { + JSONObject jsonObject = (JSONObject) o; + for (Object days : jsonObject.getJSONArray("days")) { + JSONObject jsonObject1 = (JSONObject) days; + if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) { + JSONObject jsonObject2 = new JSONObject(); + String s1 = string + " " + jsonObject.getString("startTime") + ":00"; + String e1 = string + " " + jsonObject.getString("endTime") + ":00"; + // 转化为Date类型 + Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss"); + Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss"); + if (new Date().after(start) && new Date().before(end)) { +// jsonObject2.put("start_time", start.getTime() / 1000); + Date date1 = new Date(); + jsonObject2.put("start_time", date1.getTime() / 1000); + date1.setTime(date1.getTime() + 30 * 1000); + if (start.before(end)) { + jsonObject2.put("end_time", date1.getTime() / 1000); + jsonArray.add(jsonObject2); + } + } + } + } + } + } + if (!jsonArray.isEmpty()) { + huiminCardVO1.setEndTime(simpleDateFormat.format(payHuimin.getEndTime())); + TStudent tStudent = tStudents.stream().filter(e -> e.getId().equals(Integer.valueOf(payHuimin.getStudentId().split(",")[0]))).findFirst().orElse(null); + if (tStudent!=null){ + huiminCardVO1.setStudentName(tStudent.getName()); + } + String temp = "{\"sid\":" + sid.toJSONString() + "," + "\"rid\":" + rid + "," + "\"uid\":\"" + payHuimin.getStudentId().split(",")[0] + "\"," + "\"time\":" + jsonArray.toJSONString() + ",\"type\":" + 2 + "}"; + huiminCardVO1.setQrCode(temp); + huiminCardVOS.add(huiminCardVO1); + integers.add(payHuimin.getCardId()); + + } + } for (THuiminCard huiminCardVO : cardList) { HuiminCardVO huiminCardVO1 = new HuiminCardVO(); huiminCardVO1.setId(huiminCardVO.getId()); List<TPayHuimin> collect = payHuimins.stream().filter(e -> e.getCardId().equals(huiminCardVO.getId())).collect(Collectors.toList()); - if (!collect.isEmpty()){ + if (!collect.isEmpty()) { for (TPayHuimin tPayHuimin : collect) { - if (tPayHuimin.getStatus().equals(2)){ + if (tPayHuimin.getStatus().equals(2)) { huiminCardVO1.setCover(huiminCardVO.getBuyCover()); huiminCardVO1.setIsBuy(1); - break; - }else{ + } else { huiminCardVO1.setCover(huiminCardVO.getUnBuyCover()); huiminCardVO1.setIsBuy(0); } } - }else{ + } else { huiminCardVO1.setCover(huiminCardVO.getUnBuyCover()); huiminCardVO1.setIsBuy(0); } huiminCardVOS.add(huiminCardVO1); } - huiminAgreementVO.setCardList(huiminCardVOS); + // 手动对huiminCardVOS进行分页 + if (pageNo < 1) { + pageNo = 1; + } + if (pageSize < 1) { + pageSize = 10; // 默认每页10条 + } + + int startIndex = (pageNo - 1) * pageSize; + int endIndex = Math.min(startIndex + pageSize, huiminCardVOS.size()); +// 防止 startIndex 超过 huiminCardVOS.size() + if (startIndex > huiminCardVOS.size()) { + startIndex = huiminCardVOS.size(); + } + List<HuiminCardVO> paginatedHuiminCardVOS = huiminCardVOS.subList(startIndex, endIndex); +// 将分页后的数据设置到 huiminAgreementVO 中 + huiminAgreementVO.setCardList(paginatedHuiminCardVOS); +// 将分页后的数据设置到 huiminAgreementVO 中 + huiminAgreementVO.setCardList(paginatedHuiminCardVOS); +// huiminAgreementVO.setCardList(huiminCardVOS); return ResultUtil.success(huiminAgreementVO); } catch (Exception e) { e.printStackTrace(); return ResultUtil.success(new HuiminAgreementVO()); } } + + /** + * 惠民卡列表-根据惠民卡获取已购买绑定惠民卡的学员列表 + */ + @ResponseBody + @PostMapping("/getStudentListByCarId") + @ApiOperation(value = "惠民卡列表-根据惠民卡获取已购买绑定惠民卡的学员列表") + @ApiImplicitParams({ + @ApiImplicitParam(value = "惠民卡id", name = "id", dataType = "int", required = true), + @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") + }) + public ResultUtil<List<TStudent>> getStudentListByCarId(Integer id) { + try { + Integer uid = tokenUtil.getUserIdFormRedis(); + if (null == uid) { + return ResultUtil.tokenErr(); + } + List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>() + .eq(TPayHuimin::getAppUserId, uid) + .eq(TPayHuimin::getCardId, id) + .ge(TPayHuimin::getEndTime, new Date()) + .eq(TPayHuimin::getStatus, 2)); + List<THuiminCard> huiminCards = huiminCardService.list(); + List<Integer> studentIds = new ArrayList<>(); + for (TPayHuimin payHuimin : payHuimins) { + THuiminCard huiminCard = huiminCards.stream().filter(e -> e.getId().equals(payHuimin.getCardId())).findFirst().orElse(null); + if (huiminCard != null) { + String siteIds = ""; + if (huiminCard.getUseScope() == 2) { + siteIds = huiminCard.getUseIds(); + } else { + siteIds = siteClient.querySiteByStoreIds(huiminCard.getStoreIds()) + .stream() + .map(Site::getId) + .map(String::valueOf) + .collect(Collectors.joining(",")); + } + String storeIds = huiminCard.getStoreIds(); + if (storeIds.contains("2024")) { + siteIds = siteIds + ",32"; + } + if (storeIds.contains("1001")) { + siteIds = siteIds + ",3"; + } + // 门店ids + JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(","))); + // 场地ids + JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(","))); + // 可用时间 + LocalDate today = LocalDate.now(); + + int week = today.getDayOfWeek().getValue(); + + JSONArray time = JSONArray.parseArray(huiminCard.getUseWeeks()); + JSONArray jsonArray = new JSONArray(); + String string = LocalDate.now().toString(); + + Date date = new Date(); + + if (date.before(huiminCard.getEndTime()) && date.after(huiminCard.getStartTime())) { + for (Object o : time) { + JSONObject jsonObject = (JSONObject) o; + for (Object days : jsonObject.getJSONArray("days")) { + JSONObject jsonObject1 = (JSONObject) days; + if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) { + JSONObject jsonObject2 = new JSONObject(); + String s1 = string + " " + jsonObject.getString("startTime") + ":00"; + String e1 = string + " " + jsonObject.getString("endTime") + ":00"; + // 转化为Date类型 + Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss"); + Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss"); + if (new Date().after(start) && new Date().before(end)) { +// jsonObject2.put("start_time", start.getTime() / 1000); + Date date1 = new Date(); + jsonObject2.put("start_time", date1.getTime() / 1000); + date1.setTime(date1.getTime() + 30 * 1000); + if (start.before(end)) { + jsonObject2.put("end_time", date1.getTime() / 1000); + jsonArray.add(jsonObject2); + } + } + } + } + } + } + if (!jsonArray.isEmpty()) { + // 将学员加入 + String[] split = payHuimin.getStudentId().split(","); + for (String s : split) { + studentIds.add(Integer.valueOf(s)); + } + } + } + } + String collect = studentIds.stream().map(String::valueOf).collect(Collectors.joining(",")); + List<TStudent> studentByIds = studentClient.getStudentByIds(collect); + return ResultUtil.success(studentByIds); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 惠民卡列表-选择学员后返回对应的二维码生成规则和有效期结束时间 + */ + @ResponseBody + @PostMapping("/getQrCodeByStudentId") + @ApiOperation(value = "惠民卡列表-选择学员后返回对应的二维码生成规则和有效期结束时间") + @ApiImplicitParams({ + @ApiImplicitParam(value = "惠民卡id", name = "cardId", dataType = "int", required = true), + @ApiImplicitParam(value = "学员id", name = "studentId", dataType = "int", required = true), + @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") + }) + public ResultUtil<HuiminCardStudentVO> getQrCodeByStudentId(Integer cardId, Integer studentId) { + try { + Integer uid = tokenUtil.getUserIdFormRedis(); + if (null == uid) { + return ResultUtil.tokenErr(); + } + List<TStudent> tStudents = studentClient.queryStudentList(uid); + + List<TPayHuimin> payHuimins = payHuiminService.list(new LambdaQueryWrapper<TPayHuimin>() + .apply("FIND_IN_SET(" + studentId + ", studentId) > 0") +// .eq(TPayHuimin::getStudentId, studentId) + .eq(TPayHuimin::getCardId, cardId) + .ge(TPayHuimin::getEndTime, new Date()) + .eq(TPayHuimin::getStatus, 2) + + .orderByDesc(TPayHuimin::getEndTime)); + HuiminCardStudentVO huiminCardStudentVO = new HuiminCardStudentVO(); + + if (!payHuimins.isEmpty()) { + TPayHuimin tPayHuimin = payHuimins.get(0); + THuiminCard huiminCard = huiminCardService.getById(cardId); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + huiminCardStudentVO.setEndTime(simpleDateFormat.format(tPayHuimin.getEndTime())); + String siteIds = ""; + if (huiminCard.getUseScope() == 2) { + siteIds = huiminCard.getUseIds(); + } else { + siteIds = siteClient.querySiteByStoreIds(huiminCard.getStoreIds()) + .stream() + .map(Site::getId) + .map(String::valueOf) + .collect(Collectors.joining(",")); + } + String storeIds = huiminCard.getStoreIds(); + if (storeIds.contains("2024")) { + siteIds = siteIds + ",32"; + } + if (storeIds.contains("1001")) { + siteIds = siteIds + ",3"; + } + // 门店ids + JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(","))); + // 场地ids + JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(","))); + // 可用时间 + LocalDate today = LocalDate.now(); + + int week = today.getDayOfWeek().getValue(); + + JSONArray time = JSONArray.parseArray(huiminCard.getUseWeeks()); + JSONArray jsonArray = new JSONArray(); + String string = LocalDate.now().toString(); + + Date date = new Date(); + + if (date.before(huiminCard.getEndTime()) && date.after(huiminCard.getStartTime())) { + for (Object o : time) { + JSONObject jsonObject = (JSONObject) o; + for (Object days : jsonObject.getJSONArray("days")) { + JSONObject jsonObject1 = (JSONObject) days; + if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) { + JSONObject jsonObject2 = new JSONObject(); + String s1 = string + " " + jsonObject.getString("startTime") + ":00"; + String e1 = string + " " + jsonObject.getString("endTime") + ":00"; + // 转化为Date类型 + Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss"); + Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss"); + jsonObject2.put("start_time", start.getTime() / 1000); + start.setTime(start.getTime() + 30 * 1000); + if (new Date().after(start) && new Date().before(end)) { +// jsonObject2.put("start_time", start.getTime() / 1000); + Date date1 = new Date(); + jsonObject2.put("start_time", date1.getTime() / 1000); + date1.setTime(date1.getTime() + 30 * 1000); + if (start.before(end)) { + jsonObject2.put("end_time", date1.getTime() / 1000); + jsonArray.add(jsonObject2); + } + } + } + } + } + } + + String temp = "{\"sid\":" + sid.toJSONString() + "," + "\"rid\":" + rid + "," + "\"uid\":\"" + studentId + "\"," + "\"time\":" + jsonArray.toJSONString() + ",\"type\":" + 2 + "}"; + huiminCardStudentVO.setQrCode(temp); + TStudent tStudent = tStudents.stream().filter(e -> e.getId().equals(studentId)).findFirst().orElse(null); + if (tStudent!=null){ + huiminCardStudentVO.setStudentName(tStudent.getName()); + } + } + + + return ResultUtil.success(huiminCardStudentVO); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * 惠民卡富文本内容-惠民卡列表 */ @@ -346,36 +671,34 @@ return ResultUtil.tokenErr(); } pageNo = (pageNo - 1) * pageSize; - List<MyHuiminCardVO> cardList = huiminCardService.getMyHuiminCardList(pageNo,pageSize,uid); + List<MyHuiminCardVO> cardList = huiminCardService.getMyHuiminCardList(pageNo, pageSize, uid); List<THuiminCard> list = huiminCardService.list(); for (MyHuiminCardVO myHuiminCardVO : cardList) { TPayHuimin byId = payHuiminService.getById(myHuiminCardVO.getId()); myHuiminCardVO.setEndTime(byId.getEndTime()); - myHuiminCardVO.setSalesMoney(byId.getSalesMoney()); - int count = huiminRecordService.count(new LambdaQueryWrapper<THuiminRecord>() .eq(THuiminRecord::getHuiminCardId, myHuiminCardVO.getCardId())); // 购卡7日内没有使用记录可退款。超过7日不管有没有使用记录都不能退款 - if (new Date().after(DateUtil.addDay(myHuiminCardVO.getPaymentTime(),7))){ + if (new Date().after(DateUtil.addDay(myHuiminCardVO.getPaymentTime(), 7))) { // 超过七天不可退款 myHuiminCardVO.setIsRefund(0); - }else if (count>=7){ + } else if (count >= 7) { myHuiminCardVO.setIsRefund(0); - }else{ + } else { myHuiminCardVO.setIsRefund(1); } THuiminCard huiminCard = list.stream().filter(e -> e.getId().equals(myHuiminCardVO.getCardId())).findFirst().orElse(null); - if (null!=huiminCard){ + if (null != huiminCard) { myHuiminCardVO.setCover(huiminCard.getBuyCover()); } - if (myHuiminCardVO.getEndTime().before(new Date())){ + if (myHuiminCardVO.getEndTime().before(new Date())) { // 已过期 myHuiminCardVO.setIsExpire(1); - if (null!=huiminCard){ + if (null != huiminCard) { myHuiminCardVO.setCover(huiminCard.getUnBuyCover()); } - }else{ + } else { myHuiminCardVO.setIsExpire(0); } // 查询绑定人员列表 @@ -388,6 +711,7 @@ return ResultUtil.success(new ArrayList<>()); } } + /** * 个人中心-我的惠民卡-退款 */ @@ -405,35 +729,35 @@ return ResultUtil.tokenErr(); } TPayHuimin tPayHuimin = payHuiminService.getById(id); - if (tPayHuimin==null){ + if (tPayHuimin == null) { return ResultUtil.error("订单不存在"); } - if (tPayHuimin.getStatus()==3 && tPayHuimin.getRefundStatus()!=1){ + if (tPayHuimin.getStatus() == 3 && tPayHuimin.getRefundStatus() != 1) { return ResultUtil.error("不可重复退款"); } - if (tPayHuimin.getEndTime().before(new Date())){ + if (tPayHuimin.getEndTime().before(new Date())) { // 已过期 return ResultUtil.error("惠民卡已过期,不可退款"); } int count = huiminRecordService.count(new LambdaQueryWrapper<THuiminRecord>() .eq(THuiminRecord::getHuiminCardId, tPayHuimin.getCardId())); // 购卡7日内没有使用记录可退款。超过7日不管有没有使用记录都不能退款 - if (new Date().after(DateUtil.addDay(tPayHuimin.getPaymentTime(),7))){ + if (new Date().after(DateUtil.addDay(tPayHuimin.getPaymentTime(), 7))) { // 超过七天不可退款 return ResultUtil.error("惠民卡购买超过七天,不可退款"); - }else if (count>=7){ + } else if (count >= 7) { // 使用记录大于等于7次不可退款 return ResultUtil.error("惠民卡使用记录大于等于7次,不可退款"); } - if (tPayHuimin.getPaymentType()==1){ + if (tPayHuimin.getPaymentType() == 1) { Map<String, String> map = payMoneyUtil.wxRefund(tPayHuimin.getOrderNumber(), tPayHuimin.getCode(), tPayHuimin.getSalesMoney().toString(), tPayHuimin.getSalesMoney().toString(), "/base/huimin/callBack/wxRefundHuiminCallback"); - if(!"SUCCESS".equals(map.get("return_code"))){ + if (!"SUCCESS".equals(map.get("return_code"))) { System.err.println("-------------微信退款失败---------"); System.err.println(map.get("return_msg")); return ResultUtil.error("微信退款失败"); } - }else{ + } else { Map<String, String> map = payMoneyUtil.aliRefund(tPayHuimin.getOrderNumber(), tPayHuimin.getSalesMoney().toString()); String return_code = map.get("code"); if (!"10000".equals(return_code)) { @@ -462,10 +786,61 @@ // String format = simpleDateFormat.format(date); // System.err.println(format); - String storeIds = "1001,1002"; - JSONArray jsonArray = JSONArray.parseArray(Arrays.toString(storeIds.split(","))); - System.err.println(jsonArray); +// String storeIds = "1001,1002"; +// JSONArray jsonArray = JSONArray.parseArray(Arrays.toString(storeIds.split(","))); +// System.err.println(jsonArray); +// +// // 获取当前日期 +// LocalDate today = LocalDate.now(); +// +// // 获取当前日期是周几 +// DayOfWeek dayOfWeek = today.getDayOfWeek(); +// +// // 输出当前是周几,使用中文显示 +// System.out.println("今天是: " + dayOfWeek.getValue()); + Date start = new Date(); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.err.println(simpleDateFormat.format(start)); + start.setTime(start.getTime() + 30 * 1000); + System.err.println(simpleDateFormat.format(start)); +// String temp = "[{\"days\":[{\"value\":\"1\",\"label\":\"星期一\",\"checked\":true},{\"value\":\"2\",\"label\":\"星期二\",\"checked\":true},{\"value\":\"3\",\"label\":\"星期三\",\"checked\":true},{\"value\":\"4\",\"label\":\"星期四\",\"checked\":true},{\"value\":\"5\",\"label\":\"星期五\",\"checked\":true},{\"value\":\"6\",\"label\":\"星期六\",\"checked\":false},{\"value\":\"7\",\"label\":\"星期日\",\"checked\":false}],\"startTime\":\"00:00\",\"endTime\":\"23:59\"}]"; +// // 可用时间 +// LocalDate today = LocalDate.now(); +// +// int week = today.getDayOfWeek().getValue(); +// +// JSONArray time = JSONArray.parseArray(temp); +// int i = 0; +// JSONArray jsonArray = new JSONArray(); +// +// StringBuilder startTime = new StringBuilder(); +// StringBuilder endTime = new StringBuilder(); +// +// String string = LocalDate.now().toString(); +// +// for (Object o : time) { +// JSONObject jsonObject = (JSONObject) o; +// for (Object days : jsonObject.getJSONArray("days")) { +// JSONObject jsonObject1 = (JSONObject) days; +// if (jsonObject1.getString("value").equals(String.valueOf(week))&&jsonObject1.getBoolean("checked")){ +// JSONObject jsonObject2 = new JSONObject(); +// String s1 = string+" "+jsonObject.getString("startTime")+":00"; +// String e1 = string+" "+jsonObject.getString("endTime")+":00"; +// // 转化为Date类型 +// Date start = DateUtil.parse(s1,"yyyy-MM-dd HH:mm:ss"); +// Date end = DateUtil.parse(e1,"yyyy-MM-dd HH:mm:ss"); +// jsonObject2.put("startTime",start.getTime()); +// jsonObject2.put("endTime",end.getTime()); +// jsonArray.add(jsonObject2); +// } +// +// +// } +// i++; +// } +// System.err.println(jsonArray); } + /** * 个人中心-我的惠民卡-查看详情 */ @@ -483,15 +858,19 @@ return ResultUtil.tokenErr(); } TPayHuimin tPayHuimin = payHuiminService.getById(id); - if (tPayHuimin==null){ + if (tPayHuimin == null) { return ResultUtil.error("惠民卡不存在"); } THuiminCard byId = huiminCardService.getById(tPayHuimin.getCardId()); MyHuiminCardDetailVO myHuiminCardDetailVO = new MyHuiminCardDetailVO(); + List<TStudent> studentByIds1 = studentClient.getStudentByIds(tPayHuimin.getStudentId()); + if (CollUtil.isNotEmpty(studentByIds1)) { + myHuiminCardDetailVO.setStudentName(studentByIds1.get(0).getName()); + } String siteIds = ""; - if (byId.getUseScope()==2){ + if (byId.getUseScope() == 2) { siteIds = byId.getUseIds(); - }else{ + } else { siteIds = siteClient.querySiteByStoreIds(byId.getStoreIds()) .stream() .map(Site::getId) @@ -499,8 +878,57 @@ .collect(Collectors.joining(",")); } String storeIds = byId.getStoreIds(); - JSONArray jsonArray = JSONArray.parseArray(Arrays.toString(storeIds.split(","))); - String temp ="{\"sid\":\"" +siteIds+"\","+"\"useTimes:\"\""+byId.getUseWeeks()+"\","+"\"unUseTimes:\""+byId.getUnUseTimes()+"\"}"; + if (storeIds.contains("2024")) { + siteIds = siteIds + ",32"; + } + if (storeIds.contains("1001")) { + siteIds = siteIds + ",3"; + } + // 门店ids + JSONArray sid = JSONArray.parseArray(Arrays.toString(storeIds.split(","))); + // 场地ids + JSONArray rid = JSONArray.parseArray(Arrays.toString(siteIds.split(","))); + // 可用时间 + LocalDate today = LocalDate.now(); + + int week = today.getDayOfWeek().getValue(); + + JSONArray time = JSONArray.parseArray(byId.getUseWeeks()); + JSONArray jsonArray = new JSONArray(); + String string = LocalDate.now().toString(); + + Date date = new Date(); + + if (date.before(byId.getEndTime()) && date.after(byId.getStartTime())) { + for (Object o : time) { + JSONObject jsonObject = (JSONObject) o; + for (Object days : jsonObject.getJSONArray("days")) { + JSONObject jsonObject1 = (JSONObject) days; + if (jsonObject1.getString("value").equals(String.valueOf(week)) && jsonObject1.getBoolean("checked")) { + JSONObject jsonObject2 = new JSONObject(); + String s1 = string + " " + jsonObject.getString("startTime") + ":00"; + String e1 = string + " " + jsonObject.getString("endTime") + ":00"; + // 转化为Date类型 + Date start = DateUtil.parse(s1, "yyyy-MM-dd HH:mm:ss"); + Date end = DateUtil.parse(e1, "yyyy-MM-dd HH:mm:ss"); + jsonObject2.put("start_time", start.getTime() / 1000); + start.setTime(start.getTime() + 30 * 1000); + if (new Date().after(start) && new Date().before(end)) { +// jsonObject2.put("start_time", start.getTime() / 1000); + Date date1 = new Date(); + jsonObject2.put("start_time", date1.getTime() / 1000); + date1.setTime(date1.getTime() + 30 * 1000); + if (start.before(end)) { + jsonObject2.put("end_time", date1.getTime() / 1000); + jsonArray.add(jsonObject2); + } + } + } + } + } + } + + String temp = "{\"sid\":" + sid.toJSONString() + "," + "\"rid\":" + rid + "," + "\"uid\":\"" + tPayHuimin.getStudentId() + "\"," + "\"time\":" + jsonArray.toJSONString() + ",\"type\":" + 2 + "}"; myHuiminCardDetailVO.setQrCode(temp); myHuiminCardDetailVO.setCardId(tPayHuimin.getCardId()); myHuiminCardDetailVO.setHuiminCard(byId); @@ -515,6 +943,7 @@ return ResultUtil.success(new MyHuiminCardDetailVO()); } } + /** * 获取添加人员、选择人员说明文案 */ @@ -532,7 +961,7 @@ } THuiminAgreement one = huiminAgreementService.lambdaQuery() .isNull(THuiminAgreement::getOperatorId).last("limit 1").one(); - return ResultUtil.success(one); + return ResultUtil.success(one); } catch (Exception e) { e.printStackTrace(); return ResultUtil.success(new THuiminAgreement()); diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java b/cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java new file mode 100644 index 0000000..772b83a --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/controller/WeiXinV3Controller.java @@ -0,0 +1,76 @@ +package com.dsh.activity.controller; + +import cn.hutool.core.collection.CollUtil; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.dsh.activity.entity.*; +import com.dsh.activity.feignclient.account.StudentClient; +import com.dsh.activity.feignclient.other.SiteClient; +import com.dsh.activity.feignclient.other.StoreClient; +import com.dsh.activity.feignclient.other.model.Site; +import com.dsh.activity.feignclient.other.model.Store; +import com.dsh.activity.model.response.*; +import com.dsh.activity.service.*; +import com.dsh.activity.util.*; +//import com.dsh.activity.util.wx.WechatPaymentService; +import com.dsh.activity.util.wx.WxAppPayService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.*; +import java.util.stream.Collectors; + +/** + * @author zhibing.pu + * @date 2023/6/24 11:27 + */ +@RestController +@RequestMapping("/base") +public class WeiXinV3Controller { + +// @Resource +// private WechatPaymentService wechatPaymentService; + @Autowired + private WxAppPayService wxAppPayService; + @ResponseBody + @PostMapping("/testWeiXinV3") + @ApiOperation(value = "获取添加人员、选择人员说明文案") + @ApiImplicitParams({ + @ApiImplicitParam(name = "Authorization", value = "用户token(Bearer +token)", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9.....") + }) + public ResultUtil getContentForStudent() throws Exception { +// Map<String, Object> stringObjectMap = wechatPaymentService.weChatDoUnifiedOrder(); + // ... 在你的下单方法中调用 ... + try { + String description = "商品描述"; + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); + String outTradeNo =sdf.format(new Date()) + UUIDUtil.getNumberRandom(5); + BigDecimal amount = new BigDecimal("0.01"); // 支付金额,例如1分钱 + Map<String, String> payParams = wxAppPayService.createOrder(description, outTradeNo, amount); + return ResultUtil.success(payParams); + + // 将 payParams 返回给你的APP前端,前端使用这些参数调起微信支付SDK + // return ResponseEntity.ok(payParams); // 示例 + + } catch (Exception e) { + // 处理异常 + // return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("下单失败"); + } + return ResultUtil.success(); + } + +} diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardStudentVO.java b/cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardStudentVO.java new file mode 100644 index 0000000..9d6a8c3 --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardStudentVO.java @@ -0,0 +1,23 @@ +package com.dsh.activity.model.response; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@ApiModel(value = "根据学员id展示惠民卡二维码VO") +public class HuiminCardStudentVO { + @ApiModelProperty("有效期至") + private String endTime; + @ApiModelProperty("二维码规则") + private String qrCode; + @ApiModelProperty("学员名称") + private String studentName; + + + +} diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardVO.java b/cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardVO.java index e5d8873..b03805c 100644 --- a/cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardVO.java +++ b/cloud-server-activity/src/main/java/com/dsh/activity/model/response/HuiminCardVO.java @@ -14,10 +14,18 @@ @ApiModelProperty("惠民卡id") private Integer id; + @ApiModelProperty("惠民卡名称") + private String huiminName; @ApiModelProperty("封面图") private String cover; @ApiModelProperty("是否购买 0否1是") private Integer isBuy; + @ApiModelProperty("二维码规则") + private String qrCode; + @ApiModelProperty("有效期") + private String endTime; + @ApiModelProperty("学员姓名") + private String studentName; diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/model/response/MyHuiminCardDetailVO.java b/cloud-server-activity/src/main/java/com/dsh/activity/model/response/MyHuiminCardDetailVO.java index dfcebb9..9b05310 100644 --- a/cloud-server-activity/src/main/java/com/dsh/activity/model/response/MyHuiminCardDetailVO.java +++ b/cloud-server-activity/src/main/java/com/dsh/activity/model/response/MyHuiminCardDetailVO.java @@ -30,6 +30,8 @@ private List<TStudent> studentList; @ApiModelProperty("惠民卡") private THuiminCard huiminCard; + @ApiModelProperty("购买使用者名称") + private String studentName; } diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java index bf624af..12b44c4 100644 --- a/cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/PayMoneyUtil.java @@ -61,8 +61,9 @@ private String key = "6f5e0c2dcabfa9c27b5da5836a362fef";//微信商户号 -// private String callbackPath = "https://online.daowepark.com:443/activity";//支付回调网关地址 - private String callbackPath = "http://vbef9arg13uu.guyubao.com/activity";//支付回调网关地址 + private String callbackPath = "https://online.daowepark.com:443/activity";//支付回调网关地址 + +// private String callbackPath = "http://vbef9arg13uu.guyubao.com/activity";//支付回调网关地址 private String app_cert_path = "C:/cert/alipay/user/app_cert_path.crt";//应用公钥证书路径 @@ -70,8 +71,8 @@ private String alipay_root_cert_path = "C:/cert/alipay/user/alipay_root_cert_path.crt";//支付宝CA根证书文件路径 -// private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书 - private String certPath = "D:/apiclient_cert.p12";//微信证书 + private String certPath = "/usr/playpai/cert/weixin/apiclient_cert.p12";//微信证书 +// private String certPath = "D:/apiclient_cert.p12";//微信证书 public ResultUtil confirm(String smid,String code, String outTradeNo, String amount) { AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", @@ -407,6 +408,7 @@ Map<String, Object> map = new HashMap<>(); map.put("appid", appid); map.put("mch_id", mchId); + map.put("sub_mch_id", "123456"); map.put("nonce_str", nonce_str); String temp = ""; if (body.split("-").length>1){ diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java new file mode 100644 index 0000000..d08730f --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPayConstants.java @@ -0,0 +1,32 @@ +package com.dsh.activity.util.wx; + +/** + * 常量 + */ +public class WXPayConstants { + + public static final String DOMAIN_API = "https://api.mch.weixin.qq.com"; + + //app下单 + public static final String PAY_TRANSACTIONS_APP = "/v3/pay/partner/transactions/app"; +// public static final String PAY_TRANSACTIONS_APP = "/v3/pay/transactions/app"; + + + //微信支付回调 + public static final String WECHAT_PAY_NOTIFY_URL = + "https://xxx.xxxx.com/deal/api/appPayment/weChatPayNotify"; + + + //申请退款 + public static final String REFUND_DOMESTIC_REFUNDS = "/refund/domestic/refunds"; + + //微信退款回调 + public static final String WECHAT_REFUNDS_NOTIFY_URL = "https://xxx.xxxx.com/api/appPayment/weChatPayRefundsNotify"; + + //关闭订单 + public static final String PAY_TRANSACTIONS_OUT_TRADE_NO = "/pay/transactions/out-trade-no/{}/close"; + + + + +} diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java new file mode 100644 index 0000000..ccc2e53 --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WXPaySignatureCertificateUtil.java @@ -0,0 +1,298 @@ +package com.dsh.activity.util.wx; + +import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; +import com.wechat.pay.contrib.apache.httpclient.auth.AutoUpdateCertificatesVerifier; +import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner; +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.util.PemUtil; +import lombok.SneakyThrows; +import org.apache.http.impl.client.CloseableHttpClient; + +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * 微信支付V3 API 签名验证工具类 + */ +public class WXPaySignatureCertificateUtil { + + // 保存微信平台证书 + private static final ConcurrentHashMap<String, AutoUpdateCertificatesVerifier> verifierMap = new ConcurrentHashMap<>(); + // 缓存公钥,避免频繁IO操作 + private static PublicKey cachedPublicKey = null; + // 缓存私钥,避免频繁IO操作 + private static PrivateKey cachedPrivateKey = null; + + /** + * 获取HTTP客户端,用于微信支付API请求 + */ + public static CloseableHttpClient getWechatPayClient() throws IOException { + PrivateKey merchantPrivateKey = getPrivateKey(); + // 构建支付客户端 + return WechatPayHttpClientBuilder.create() + .withMerchant(WxV3PayConfig.Mch_ID, WxV3PayConfig.mchSerialNo, merchantPrivateKey) + .withValidator(new WechatPay2Validator(getVerifier())) + .build(); + } + + /** + * 获取自动更新的证书验证器 + */ + public static AutoUpdateCertificatesVerifier getVerifier() { + String mchSerialNo = WxV3PayConfig.mchSerialNo; + AutoUpdateCertificatesVerifier verifier = verifierMap.get(mchSerialNo); + + if (verifier == null) { + synchronized (WXPaySignatureCertificateUtil.class) { + verifier = verifierMap.get(mchSerialNo); + if (verifier == null) { + try { + PrivateKey privateKey = getPrivateKey(); + PrivateKeySigner signer = new PrivateKeySigner(mchSerialNo, privateKey); + WechatPay2Credentials credentials = new WechatPay2Credentials(WxV3PayConfig.Mch_ID, signer); + verifier = new AutoUpdateCertificatesVerifier( + credentials, WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); + verifierMap.put(mchSerialNo, verifier); + } catch (Exception e) { + throw new RuntimeException("初始化证书验证器失败", e); + } + } + } + } + + return verifier; + } + + /** + * 使用商户私钥对数据进行签名 + * + * @param message 待签名数据 + * @return Base64编码的签名结果 + */ + public static String sign(String message) { + try { + PrivateKey privateKey = getPrivateKey(); + Signature signature = Signature.getInstance("SHA256withRSA"); + signature.initSign(privateKey); + signature.update(message.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(signature.sign()); + } catch (Exception e) { + throw new RuntimeException("签名失败", e); + } + } + + /** + * 使用微信支付平台公钥验证签名 + * + * @param message 原始消息 + * @param signature Base64编码的签名 + * @return 验证结果 + */ + public static boolean verifySign(String message, String signature) { + try { + PublicKey publicKey = getWechatPublicKey(); + Signature sig = Signature.getInstance("SHA256withRSA"); + sig.initVerify(publicKey); + sig.update(message.getBytes(StandardCharsets.UTF_8)); + return sig.verify(Base64.getDecoder().decode(signature)); + } catch (Exception e) { + throw new RuntimeException("验签失败", e); + } + } + + /** + * 验证微信支付通知消息的签名 + * + * @param request HTTP请求 + * @param body 请求体内容 + * @return 验证结果 + */ + public static boolean verifyNotify(HttpServletRequest request, String body) { + try { + // 获取必要的HTTP头信息 + String timestamp = request.getHeader("Wechatpay-Timestamp"); + String nonce = request.getHeader("Wechatpay-Nonce"); + String serialNo = request.getHeader("Wechatpay-Serial"); + String signature = request.getHeader("Wechatpay-Signature"); + + // 拼接验签字符串 + String message = Stream.of(timestamp, nonce, body) + .collect(Collectors.joining("\n", "", "\n")); + + // 使用自动更新的证书验证器进行验证 + return getVerifier().verify(serialNo, message.getBytes(StandardCharsets.UTF_8), signature); + } catch (Exception e) { + throw new RuntimeException("验证微信支付通知签名失败", e); + } + } + + /** + * APP支付签名 + */ + public static String appPaySign(String timestamp, String nonceStr, String prepayId) { + String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, prepayId) + .collect(Collectors.joining("\n", "", "\n")); + return sign(message); + } + + /** + * JSAPI支付签名 + */ + public static String jsApiPaySign(String timestamp, String nonceStr, String prepayId) { + String message = Stream.of(WxV3PayConfig.APP_ID, timestamp, nonceStr, "prepay_id="+prepayId) + .collect(Collectors.joining("\n", "", "\n")); + return sign(message); + } + + /** + * 构建App支付参数 + */ + public static Map<String, String> buildAppPayParams(String prepayId) { + try { + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + String nonceStr = generateNonceStr(); + String sign = appPaySign(timestamp, nonceStr, prepayId); + + Map<String, String> params = new HashMap<>(); + params.put("appid", WxV3PayConfig.APP_ID); + params.put("partnerid", WxV3PayConfig.Mch_ID); + params.put("prepayid", prepayId); + params.put("package", "Sign=WXPay"); + params.put("noncestr", nonceStr); + params.put("timestamp", timestamp); + params.put("sign", sign); + + return params; + } catch (Exception e) { + throw new RuntimeException("构建App支付参数失败", e); + } + } + + /** + * 构建JSAPI支付参数 + */ + public static Map<String, String> buildJsApiPayParams(String prepayId) { + try { + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + String nonceStr = generateNonceStr(); + String sign = jsApiPaySign(timestamp, nonceStr, prepayId); + + Map<String, String> params = new HashMap<>(); + params.put("appId", WxV3PayConfig.APP_ID); + params.put("timeStamp", timestamp); + params.put("nonceStr", nonceStr); + params.put("package", "prepay_id=" + prepayId); + params.put("signType", "RSA"); + params.put("paySign", sign); + + return params; + } catch (Exception e) { + throw new RuntimeException("构建JSAPI支付参数失败", e); + } + } + + /** + * 生成随机字符串 + */ + private static String generateNonceStr() { + return java.util.UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32); + } + + /** + * 获取商户私钥 + */ + public static PrivateKey getPrivateKey() { + if (cachedPrivateKey != null) { + return cachedPrivateKey; + } + + try { + String filePath = "D:\\玩湃v3参数\\apiclient_key.pem"; + String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); + + String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replaceAll("\\s+", ""); + + KeyFactory kf = KeyFactory.getInstance("RSA"); + cachedPrivateKey = kf.generatePrivate( + new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey))); + return cachedPrivateKey; + } catch (IOException e) { + throw new RuntimeException("读取私钥文件失败", e); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持RSA", e); + } catch (InvalidKeySpecException e) { + throw new RuntimeException("无效的密钥格式", e); + } + } + + /** + * 获取微信支付平台公钥 + */ + public static PublicKey getWechatPublicKey() { + if (cachedPublicKey != null) { + return cachedPublicKey; + } + + try { + // 使用验证器获取最新的证书 + X509Certificate certificate = getVerifier().getValidCertificate(); + cachedPublicKey = certificate.getPublicKey(); + return cachedPublicKey; + } catch (Exception e) { + throw new RuntimeException("获取微信平台公钥失败", e); + } + } + + /** + * 从PEM格式文件加载公钥 + */ + public static PublicKey loadPublicKeyFromFile(String filePath) { + try { + String content = new String(Files.readAllBytes(Paths.get(filePath)), StandardCharsets.UTF_8); + + String publicKeyPEM = content + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s+", ""); + + byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(new X509EncodedKeySpec(encoded)); + } catch (Exception e) { + throw new RuntimeException("加载公钥文件失败", e); + } + } + + /** + * 从证书文件加载公钥 + */ + public static PublicKey loadPublicKeyFromCert(String filePath) { + try (FileInputStream inputStream = new FileInputStream(filePath)) { + CertificateFactory factory = CertificateFactory.getInstance("X.509"); + X509Certificate cert = (X509Certificate) factory.generateCertificate(inputStream); + return cert.getPublicKey(); + } catch (Exception e) { + throw new RuntimeException("加载证书文件失败", e); + } + } +} \ No newline at end of file diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatPaymentServiceImpl.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatPaymentServiceImpl.java new file mode 100644 index 0000000..981d6f2 --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WeChatPaymentServiceImpl.java @@ -0,0 +1,315 @@ +//package com.dsh.activity.util.wx;/* +//* +//*改了七八遍 照顾找包困难的朋友吧 +//* +//*/ +//import cn.hutool.core.text.StrFormatter; +//import com.alibaba.fastjson.JSONObject; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import com.fasterxml.jackson.databind.node.ObjectNode; +//import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder; +//import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator; +//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 org.apache.commons.lang3.RandomStringUtils; +//import org.apache.http.client.methods.CloseableHttpResponse; +//import org.apache.http.client.methods.HttpPost; +//import org.apache.http.entity.StringEntity; +//import org.apache.http.impl.client.CloseableHttpClient; +//import org.apache.http.util.EntityUtils; +//import org.springframework.stereotype.Service; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +//import java.io.BufferedReader; +//import java.io.ByteArrayOutputStream; +//import java.math.BigDecimal; +//import java.nio.charset.StandardCharsets; +//import java.util.HashMap; +//import java.util.Map; +// +//@Service +//public class WeChatPaymentServiceImpl implements WechatPaymentService { +// +// +// +// /** +// * V3微信支付统一下单 +// * +// * @return +// * +// */ +// @Override +// public Map<String, Object>weChatDoUnifiedOrder() { +// Map<String,Object> map =new HashMap<>(); +// //支付总金额 +// BigDecimal totalPrice = BigDecimal.ZERO; +// totalPrice = totalPrice.add(BigDecimal.valueOf(600)); +// //转换金额保留两位小数点 +// Integer money=new BigDecimal(String.valueOf(totalPrice)).movePointRight(2).intValue(); +// try { +// CloseableHttpClient httpClient = WXPaySignatureCertificateUtil.checkSign(); +// +// //app下单 +// HttpPost httpPost = new HttpPost(WXPayConstants.DOMAIN_API+WXPayConstants.PAY_TRANSACTIONS_APP); +// httpPost.addHeader("Accept", "application/json"); +// httpPost.addHeader("Content-type", "application/json; charset=utf-8"); +// ByteArrayOutputStream bos = new ByteArrayOutputStream(); +// ObjectMapper objectMapper = new ObjectMapper(); +// ObjectNode rootNode = objectMapper.createObjectNode(); +// rootNode.put("mchid", "商户id") +// .put("appid", "APPID") +// .put("description","描述") +// .put("notify_url", WXPayConstants.WECHAT_PAY_NOTIFY_URL)//回调 +// .put("out_trade_no", "订单号"); +// rootNode.putObject("amount") +// .put("total","总金额"); +// objectMapper.writeValue(bos, rootNode); +// httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); +// //完成签名并执行请求 +// CloseableHttpResponse response = httpClient.execute(httpPost); +// //获取返回状态 +// int statusCode = response.getStatusLine().getStatusCode(); +// if (statusCode == 200) { //处理成功 +// String result = EntityUtils.toString(response.getEntity(), "UTF-8"); +// JSONObject object = JSONObject.parseObject(result); +// //获取预付单 +// String prepayId = object.getString("prepay_id"); +// //生成签名 +// Long timestamp = System.currentTimeMillis() / 1000; +// //随机字符串 这个是微信支付maven自带的 也可以用其它的 +// //这个是v2支付依赖自带的工具包 可以去掉了 +// //String nonceStr = WXPayUtil.generateNonceStr(); +// //该方法org.apache.commons.lang3.RandomStringUtils依赖自带随机生成字符串 RandomStringUtils.randomAlphanumeric(32) 代表生成32位 +// String nonceStr = RandomStringUtils.randomAlphanumeric(32); +// //生成带签名支付信息 +// String paySign = WXPaySignatureCertificateUtil.appPaySign(String.valueOf(timestamp), nonceStr, prepayId); +// Map<String, String> param = new HashMap<>(); +// param.put("appid", WxV3PayConfig.APP_ID); +// param.put("partnerid", WxV3PayConfig.Mch_ID); +// param.put("prepayid", prepayId); +// param.put("package", "Sign=WXPay"); +// param.put("noncestr", nonceStr); +// param.put("timestamp", String.valueOf(timestamp)); +// param.put("sign", paySign); +// map.put("code",200); +// map.put("message", "下单成功"); +// map.put("data", param); +// return map; +// } +// map.put("code",200); +// map.put("message", "下单失败"); +// map.put("data", response); +// return map; +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return null; +// } +// +// +// /** +// * 微信支付回调通知 +// * @return +// */ +// @Override +// public Map<String, Object> weChatNotificationHandler(HttpServletRequest request, HttpServletResponse response){ +// Map<String,Object> map = new HashMap<>(); +// try { +// BufferedReader br = request.getReader(); +// String str = null; +// StringBuilder sb = new StringBuilder(); +// while ((str = br.readLine())!=null) { +// sb.append(str); +// } +// // 构建request,传入必要参数 +// NotificationRequest requests = new NotificationRequest.Builder() +// .withSerialNumber(request.getHeader("Wechatpay-Serial")) +// .withNonce(request.getHeader("Wechatpay-Nonce")) +// .withTimestamp(request.getHeader("Wechatpay-Timestamp")) +// .withSignature(request.getHeader("Wechatpay-Signature")) +// .withBody(String.valueOf(sb)) +// .build(); +// //验签 +// NotificationHandler handler = new NotificationHandler(WXPaySignatureCertificateUtil.getVerifier(), WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); +// //解析请求体 +// Notification notification = handler.parse(requests); +// String decryptData = notification.getDecryptData(); +// //解析 +// JSONObject jsonObject = JSONObject.parseObject(decryptData); +// //支付状态交易状态,枚举值: SUCCESS:支付成功 REFUND:转入退款 NOTPAY:未支付 CLOSED:已关闭 REVOKED:已撤销(付款码支付) +// // USERPAYING:用户支付中(付款码支付) PAYERROR:支付失败(其他原因,如银行返回失败) +// String trade_state = String.valueOf(jsonObject.get("trade_state")); +// if (trade_state.equals("SUCCESS")) { +// //订单号 +// String orderNumber = String.valueOf(jsonObject.get("out_trade_no")); +// //微信支付微信生成的订单号 +// String transactionId = String.valueOf(jsonObject.get("transaction_id")); +// //省略查询订单 +// //此处处理业务 +// map.put("code","SUCCESS"); +// map.put("message","成功"); +// //消息推送成功 +// return map; +// } +// map.put("code","RESOURCE_NOT_EXISTS"); +// map.put("message", "订单不存在"); +// return map; +// }catch (Exception e) { +// e.printStackTrace(); +// } +// map.put("code","FAIL"); +// map.put("message", "失败"); +// return map; +// } +///** +// * 关闭订单 +// * @param outTradeNo 订单号 +// * @return +// */ +// @Override +// public Map<String, Object> closeOrder(String outTradeNo) { +// Map<String,Object> map = new HashMap<>(); +// try { +// //验证证书 +// CloseableHttpClient httpClient = WXPaySignatureCertificateUtil.checkSign(); +// //关闭订单 +// String url = StrFormatter.format(WXPayConstants.DOMAIN_API+WXPayConstants.PAY_TRANSACTIONS_OUT_TRADE_NO, outTradeNo); +// HttpPost httpPost = new HttpPost(url); +// httpPost.addHeader("Accept", "application/json"); +// httpPost.addHeader("Content-type", "application/json; charset=utf-8"); +// ByteArrayOutputStream bos = new ByteArrayOutputStream(); +// //2.添加商户id +// ObjectMapper objectMapper = new ObjectMapper(); +// ObjectNode rootNode = objectMapper.createObjectNode(); +// rootNode.put("mchid", WxV3PayConfig.Mch_ID); +// objectMapper.writeValue(bos, rootNode); +// //3.调起微信关单接口 +// httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); +// //完成签名并执行请求 +// CloseableHttpResponse response = httpClient.execute(httpPost); +// System.out.println(response.getStatusLine().getStatusCode() == 204); +// //无数据(Http状态码为204) 微信返回结果无数据 状态码为204 成功 +// if (response.getStatusLine().getStatusCode() == 204) { +// +// //code 退款码请前往微信支付文档查询 +// map.put("code",200); +// map.put("message", "关闭订单成功!"); +// return map; +// } +// } catch (Exception e) { +// } +// return null; +// } +// +// /** +// * 微信退款 +// * @param outTradeNo 订单号 +// * @return +// */ +// @Override +// public Map<String, Object> weChatRefunds(String outTradeNo) { +// Map<String,Object> map = new HashMap<>(); +// //退款总金额 +// BigDecimal totalPrice = BigDecimal.ZERO; +// totalPrice = totalPrice.add(BigDecimal.valueOf(600)); +// //转换金额 +// Integer money=new BigDecimal(String.valueOf(totalPrice)).movePointRight(2).intValue(); +// +// try { +// //验证证书 +// CloseableHttpClient httpClient = WXPaySignatureCertificateUtil.checkSign(); +// //申请退款接口 +// HttpPost httpPost = new HttpPost(WXPayConstants.DOMAIN_API+WXPayConstants.REFUND_DOMESTIC_REFUNDS); +// httpPost.addHeader("Accept", "application/json"); +// httpPost.addHeader("Content-type","application/json; charset=utf-8"); +// ByteArrayOutputStream bos = new ByteArrayOutputStream(); +// ObjectMapper objectMapper = new ObjectMapper(); +// ObjectNode rootNode = objectMapper.createObjectNode(); +// //微信支付订单号 +// rootNode.put("transaction_id", "微信支付订单号") +// //退款订单号 +// .put("out_refund_no","生成退款订单号") +// .put("notify_url","退款回调"); +// //退款金额 +// rootNode.putObject("amount") +// .put("refund", "100.00") +// //原订单金额 +// .put("total", "100.00") +// .put("currency","CNY"); +// objectMapper.writeValue(bos, rootNode); +// httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8")); +// CloseableHttpResponse response = httpClient.execute(httpPost); +// //退款成功返回消息 +// String bodyAsString = EntityUtils.toString(response.getEntity()); +// JSONObject jsonObject = JSONObject.parseObject(bodyAsString); +// if (jsonObject.get("status").equals("SUCCESS") || jsonObject.get("status").equals("PROCESSING")) { +// //code返回 +// map.put("code",200); +// map.put("message", "退款成功"); +// return map; +// } +// }catch (Exception e) { +// e.printStackTrace(); +// } +// map.put("code",500); +// map.put("message", "申请退款失败!"); +//// map.put("data", jsonObject); +// return map; +// } +// +// /** +// * 申请退款回调 +// * @param request +// * @return +// */ +// @Override +// public Map<String,Object> weChatPayRefundsNotify(HttpServletRequest request) { +// Map<String,Object> map = new HashMap<>(); +// try { +// BufferedReader br = request.getReader(); +// String str = null; +// StringBuilder sb = new StringBuilder(); +// while ((str = br.readLine())!=null) { +// sb.append(str); +// } +// // 构建request,传入必要参数 +// NotificationRequest requests = new NotificationRequest.Builder() +// .withSerialNumber(request.getHeader("Wechatpay-Serial")) +// .withNonce(request.getHeader("Wechatpay-Nonce")) +// .withTimestamp(request.getHeader("Wechatpay-Timestamp")) +// .withSignature(request.getHeader("Wechatpay-Signature")) +// .withBody(String.valueOf(sb)) +// .build(); +// //验签 +// NotificationHandler handler = new NotificationHandler(WXPaySignatureCertificateUtil.getVerifier(), WxV3PayConfig.apiV3Key.getBytes(StandardCharsets.UTF_8)); +// //解析请求体 +// Notification notification = handler.parse(requests); +// String decryptData = notification.getDecryptData(); +// //解析 +// JSONObject jsonObject = JSONObject.parseObject(decryptData); +// String refund_status = String.valueOf(jsonObject.get("refund_status")); +// if (refund_status.equals("SUCCESS")) { +// //订单号 +// String orderNumber = String.valueOf(jsonObject.get("out_trade_no")); +// //微信支付订单号 +// String transactionId = String.valueOf(jsonObject.get("transaction_id")); +// +// //这里是处理业务逻辑 +// +// +// //code 退款码请前往微信支付文档查询 +// map.put("code","RESOURCE_NOT_EXISTS"); +// map.put("message", "订单不存在"); +// return map; +// } +// }catch (Exception e) { +// e.printStackTrace(); +// } +// map.put("code","USER_ACCOUNT_ABNORMAL"); +// map.put("message", "退款请求失败"); +// return map; +// } +// +//} \ No newline at end of file diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WechatPaymentService.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WechatPaymentService.java new file mode 100644 index 0000000..5199d0c --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WechatPaymentService.java @@ -0,0 +1,56 @@ +//package com.dsh.activity.util.wx; +// +//import javax.servlet.http.HttpServletRequest; +//import javax.servlet.http.HttpServletResponse; +//import java.util.Map; +// +///** +// * @author 影子 +// */ +//public interface WechatPaymentService +//{ +// +// +// /** +// * 微信商品支付 +// * @return +// */ +// public Map<String, Object> weChatDoUnifiedOrder() throws Exception; +// +// +// +// +//// /** +//// * 微信支付回调通知 +//// * @param +//// * @return +//// */ +//// public Map<String, Object> weChatNotificationHandler(HttpServletRequest request, HttpServletResponse response); +//// +//// /** +//// *微信关闭订单 +//// * @param outTradeNo +//// * @return +//// */ +//// public Map<String, Object> closeOrder(String outTradeNo); +//// +//// +//// +//// +//// /** +//// * 申请退款 +//// * @param +//// * @return +//// */ +//// public Map<String, Object> weChatPayRefundsNotify(HttpServletRequest request); +//// +//// +//// +//// +//// /** +//// * 微信退款 +//// * @param outTradeNo 订单号 +//// * @return +//// */ +//// public Map<String, Object> weChatRefunds(String outTradeNo); +//} \ No newline at end of file diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java new file mode 100644 index 0000000..4d53065 --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxAppPayService.java @@ -0,0 +1,221 @@ +package com.dsh.activity.util.wx; + +import com.dsh.activity.util.wx.WXPayConstants; +import com.dsh.activity.util.wx.WXPaySignatureCertificateUtil; +import com.dsh.activity.util.wx.WxV3PayConfig; +import com.fasterxml.jackson.databind.JsonNode; // 引入Jackson库处理JSON +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +@Service +public class WxAppPayService { + + private static final Logger log = LoggerFactory.getLogger(WxAppPayService.class); + + private final ObjectMapper objectMapper; // 用于JSON序列化和反序列化 + private final CloseableHttpClient wechatPayClient; // 注入通过工具类创建的HTTP客户端 + + @Autowired + public WxAppPayService(ObjectMapper objectMapper) throws IOException { + this.objectMapper = objectMapper; + // 在构造函数中初始化带有签名验证功能的HTTP客户端 + this.wechatPayClient = WXPaySignatureCertificateUtil.getWechatPayClient(); + log.info("微信支付V3 HTTP客户端初始化完成。"); + } + + /** + * 创建APP支付预付单 (统一下单) + * + * @param description 商品描述 + * @param outTradeNo 商户订单号 (要求32个字符内,只能是数字、大小写字母_-|* 且在同一个商户号下唯一) + * @param totalAmount 支付总金额 (单位:元) + * @return 用于调起APP支付的参数 Map (appid, partnerid, prepayid, package, noncestr, timestamp, sign) + * @throws IOException 网络或IO异常 + * @throws RuntimeException 支付请求失败或处理异常 + */ + public Map<String, String> createOrder(String description, String outTradeNo, BigDecimal totalAmount) + throws IOException { + + // 1. 构建请求URL + String url = WXPayConstants.DOMAIN_API + WXPayConstants.PAY_TRANSACTIONS_APP; + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + + // 2. 构建请求体 JSON + ObjectNode rootNode = objectMapper.createObjectNode(); +// rootNode.put("appid", WxV3PayConfig.APP_ID);//服务商不需要该字段 +// rootNode.put("mchid", WxV3PayConfig.Mch_ID);//服务商不需要该字段 + rootNode.put("description", description); + rootNode.put("out_trade_no", outTradeNo); + rootNode.put("notify_url", WXPayConstants.WECHAT_PAY_NOTIFY_URL); // 使用常量中的回调地址 + + ObjectNode amountNode = objectMapper.createObjectNode(); + // 微信支付金额单位为分,需要转换 + amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue()); + amountNode.put("currency", "CNY"); // 货币类型,默认人民币 + rootNode.set("amount", amountNode); + + // 如果你是服务商模式,需要添加子商户信息 + rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID); + rootNode.put("sub_mchid", "123"); + rootNode.put("sp_appid", "wx41d32f362ba0f911"); + // rootNode.put("sub_appid", "子商户AppID"); // 如果子商户需要用自己的AppID拉起支付 + + String requestBody = rootNode.toString(); + log.info("微信APP支付下单请求体: {}", requestBody); + httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); + + // 3. 发送请求 + try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + log.info("微信APP支付下单响应状态码: {}, 响应体: {}", statusCode, responseBody); + + if (statusCode == 200) { // HTTP状态码 200 表示成功 + JsonNode responseNode = objectMapper.readTree(responseBody); + String prepayId = responseNode.get("prepay_id").asText(); + log.info("成功获取预支付交易会话标识 (prepay_id): {}", prepayId); + + // 4. 生成调起支付所需的参数 + Map<String, String> payParams = WXPaySignatureCertificateUtil.buildAppPayParams(prepayId); + log.info("生成APP支付参数: {}", payParams); + return payParams; + + } else { + // 处理错误情况 + log.error("微信APP支付下单失败,状态码: {}, 响应: {}", statusCode, responseBody); + // 可以根据 responseBody 中的 code 和 message 进一步分析错误 + throw new RuntimeException("微信APP支付下单失败: " + responseBody); + } + } catch (Exception e) { + log.error("微信APP支付下单请求执行异常", e); + throw new IOException("微信APP支付下单请求执行异常", e); + } + } + + /** + * 关闭订单 (根据商户订单号) + * + * @param outTradeNo 商户订单号 + * @return true 如果关闭成功或订单已关闭/不存在 (根据微信文档,成功是204 No Content) + * @throws IOException 网络或IO异常 + * @throws RuntimeException 关闭请求失败或处理异常 + */ + public boolean closeOrder(String outTradeNo) throws IOException { + // 1. 构建请求URL, 注意替换占位符 + String url = WXPayConstants.DOMAIN_API + + WXPayConstants.PAY_TRANSACTIONS_OUT_TRADE_NO.replace("{}", outTradeNo); + + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + + // 2. 构建请求体 JSON (只需要商户号) + ObjectNode rootNode = objectMapper.createObjectNode(); + rootNode.put("mchid", WxV3PayConfig.Mch_ID); + // 如果是服务商模式,则用 sp_mchid + rootNode.put("sp_mchid", WxV3PayConfig.Mch_ID); + + String requestBody = rootNode.toString(); + log.info("微信关单请求 URL: {}, 请求体: {}", url, requestBody); + httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); + + // 3. 发送请求 + try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { + int statusCode = response.getStatusLine().getStatusCode(); + // 关单成功时,微信返回 HTTP 状态码 204 No Content + log.info("微信关单响应状态码: {}", statusCode); + if (statusCode == 204) { + log.info("订单 {} 关闭成功。", outTradeNo); + return true; + } else { + String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + log.error("微信关单失败,商户订单号: {}, 状态码: {}, 响应: {}", outTradeNo, statusCode, responseBody); + // 根据需要可以解析responseBody获取错误详情 + return false; // 或者抛出异常,根据业务决定 + } + } catch (Exception e) { + log.error("微信关单请求执行异常, 商户订单号: {}", outTradeNo, e); + throw new IOException("微信关单请求执行异常", e); + } + } + + + // --- 退款相关方法 (可选) --- + + /** + * 申请退款 + * + * @param outTradeNo 原商户订单号 + * @param outRefundNo 商户退款单号 (商户系统内部的退款单号,要求唯一) + * @param reason 退款原因 (可选) + * @param refundAmount 退款金额 (单位:元) + * @param totalAmount 原订单总金额 (单位:元) + * @return 退款处理结果,可以返回微信返回的JSON节点或自定义对象 + * @throws IOException 网络或IO异常 + * @throws RuntimeException 退款请求失败或处理异常 + */ + public JsonNode createRefund(String outTradeNo, String outRefundNo, String reason, + BigDecimal refundAmount, BigDecimal totalAmount) throws IOException { + + String url = WXPayConstants.DOMAIN_API + WXPayConstants.REFUND_DOMESTIC_REFUNDS; + HttpPost httpPost = new HttpPost(url); + httpPost.addHeader("Accept", "application/json"); + httpPost.addHeader("Content-type", "application/json; charset=utf-8"); + + ObjectNode rootNode = objectMapper.createObjectNode(); + // 如果是服务商模式,需要添加子商户号 + // rootNode.put("sub_mchid", "子商户号"); + rootNode.put("out_trade_no", outTradeNo); + rootNode.put("out_refund_no", outRefundNo); + if (reason != null && !reason.isEmpty()) { + rootNode.put("reason", reason); + } + // 设置退款回调地址 + rootNode.put("notify_url", WXPayConstants.WECHAT_REFUNDS_NOTIFY_URL); + + ObjectNode amountNode = objectMapper.createObjectNode(); + amountNode.put("refund", refundAmount.multiply(new BigDecimal("100")).intValue()); // 退款金额,分 + amountNode.put("total", totalAmount.multiply(new BigDecimal("100")).intValue()); // 原订单金额,分 + amountNode.put("currency", "CNY"); + rootNode.set("amount", amountNode); + + String requestBody = rootNode.toString(); + log.info("微信申请退款请求体: {}", requestBody); + httpPost.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8)); + + try (CloseableHttpResponse response = wechatPayClient.execute(httpPost)) { + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8); + log.info("微信申请退款响应状态码: {}, 响应体: {}", statusCode, responseBody); + + if (statusCode == 200) { // 成功受理 + log.info("微信退款申请成功受理。"); + return objectMapper.readTree(responseBody); + } else { + log.error("微信申请退款失败,状态码: {}, 响应: {}", statusCode, responseBody); + throw new RuntimeException("微信申请退款失败: " + responseBody); + } + } catch (Exception e) { + log.error("微信申请退款请求执行异常", e); + throw new IOException("微信申请退款请求执行异常", e); + } + } + +} \ No newline at end of file diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java new file mode 100644 index 0000000..d101c61 --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayAesUtil.java @@ -0,0 +1,57 @@ +package com.dsh.activity.util.wx; + +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +/** + * 微信支付V3 AES解密工具类 (AEAD_AES_256_GCM) + */ +public class WxPayAesUtil { + + private static final String ALGORITHM = "AES/GCM/NoPadding"; + private static final int TAG_LENGTH_BIT = 128; // GCM认证标签长度,固定为128位 + private static final int NONCE_LENGTH_BYTE = 12; // GCM随机串长度,固定为12字节 + private static final String TRANSFORMATION = "AES/GCM/NoPadding"; // 算法/模式/填充 + + /** + * 解密微信支付回调通知中的加密信息 + * + * @param apiV3Key APIv3密钥 (来自 WxV3PayConfig) + * @param associatedData 附加数据 (resource.associated_data) + * @param nonce 随机串 (resource.nonce) + * @param ciphertext 密文 (resource.ciphertext),Base64编码 + * @return 解密后的明文字符串 + * @throws GeneralSecurityException 解密失败时抛出异常 + */ + public static String decrypt(String apiV3Key, String associatedData, String nonce, String ciphertext) + throws GeneralSecurityException { + try { + SecretKeySpec key = new SecretKeySpec(apiV3Key.getBytes(StandardCharsets.UTF_8), "AES"); + GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce.getBytes(StandardCharsets.UTF_8)); + + Cipher cipher = Cipher.getInstance(TRANSFORMATION); + cipher.init(Cipher.DECRYPT_MODE, key, spec); + cipher.updateAAD(associatedData.getBytes(StandardCharsets.UTF_8)); // 设置附加认证数据 + + byte[] decodedCiphertext = Base64.getDecoder().decode(ciphertext); + byte[] decryptedBytes = cipher.doFinal(decodedCiphertext); + + return new String(decryptedBytes, StandardCharsets.UTF_8); + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalStateException("当前JDK环境不支持AEAD_AES_256_GCM", e); + } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { + throw new IllegalArgumentException("无效的密钥或GCM参数", e); + } catch (Exception e) { + // 包括 BadPaddingException 等解密失败的情况 + throw new GeneralSecurityException("AES/GCM 解密失败", e); + } + } +} \ No newline at end of file diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java new file mode 100644 index 0000000..4f63fcc --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxPayNotifyController.java @@ -0,0 +1,177 @@ +package com.dsh.activity.util.wx; + +import com.dsh.activity.util.wx.WXPaySignatureCertificateUtil; +import com.dsh.activity.util.wx.WxPayAesUtil; +import com.dsh.activity.util.wx.WxV3PayConfig; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/appPayment") // 路径前缀,与常量中配置的回调URL匹配 +public class WxPayNotifyController { + + private static final Logger log = LoggerFactory.getLogger(WxPayNotifyController.class); + + private final ObjectMapper objectMapper; + + @Autowired + public WxPayNotifyController(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } + + /** + * 接收微信支付结果通知 + * URL需要与 WXPayConstants.WECHAT_PAY_NOTIFY_URL 匹配 + */ + @PostMapping("/weChatPayNotify") + public ResponseEntity<Map<String, String>> handleWeChatPayNotify(HttpServletRequest request, @RequestBody String requestBody) { + + Map<String, String> responseMap = new HashMap<>(); + try { + // 1. 验证签名 (使用工具类,它会处理证书) + boolean verifyResult = WXPaySignatureCertificateUtil.verifyNotify(request, requestBody); + if (!verifyResult) { + log.error("微信支付通知验签失败!"); + responseMap.put("code", "FAIL"); + responseMap.put("message", "验签失败"); + return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST); // 返回错误状态码 + } + log.info("微信支付通知验签成功。"); + + // 2. 解析通知体 + JsonNode notifyData = objectMapper.readTree(requestBody); + String eventType = notifyData.get("event_type").asText(); + + // 只处理支付成功的通知类型 + if ("TRANSACTION.SUCCESS".equals(eventType)) { + log.info("处理支付成功通知..."); + JsonNode resourceNode = notifyData.get("resource"); + String associatedData = resourceNode.get("associated_data").asText(); + String nonce = resourceNode.get("nonce").asText(); + String ciphertext = resourceNode.get("ciphertext").asText(); + + // 3. 解密关键信息 + String decryptedDataJson = WxPayAesUtil.decrypt(WxV3PayConfig.apiV3Key, associatedData, nonce, ciphertext); + log.info("解密后的支付通知信息: {}", decryptedDataJson); + JsonNode decryptedData = objectMapper.readTree(decryptedDataJson); + + // 4. 处理业务逻辑 + String outTradeNo = decryptedData.get("out_trade_no").asText(); + String transactionId = decryptedData.get("transaction_id").asText(); + String tradeState = decryptedData.get("trade_state").asText(); + // ... 获取其他需要的信息,例如支付金额、用户openid等 + + // TODO: 在这里实现你的业务逻辑: + // 1. 根据 outTradeNo 查询你的数据库订单状态。 + // 2. 判断订单是否已经处理过(防止重复处理通知)。 + // 3. 如果订单未处理且 tradeState 为 SUCCESS,则更新订单状态为支付成功。 + // 4. 记录 transactionId (微信支付订单号)。 + // 5. 执行后续业务流程(如发货、增加积分等)。 + // 6. 如果处理失败,可以考虑记录日志并后续重试,但仍需返回成功给微信,避免微信重复通知。 + + log.info("业务逻辑处理完成,商户订单号: {}, 微信订单号: {}", outTradeNo, transactionId); + + } else { + log.warn("收到非支付成功类型的通知: {}", eventType); + // 其他类型的通知,根据需要处理或忽略 + } + + // 5. 返回成功响应给微信平台 + responseMap.put("code", "SUCCESS"); + responseMap.put("message", "成功"); + return new ResponseEntity<>(responseMap, HttpStatus.OK); + + } catch (Exception e) { + log.error("处理微信支付通知异常", e); + responseMap.put("code", "FAIL"); + responseMap.put("message", "处理失败"); + // 即使处理失败,也尽量返回成功给微信,避免重复通知轰炸,然后在后台处理异常。 + // 但如果验签失败,可以返回错误状态码。 + return new ResponseEntity<>(responseMap, HttpStatus.INTERNAL_SERVER_ERROR); // 或者返回OK,根据策略定 + } + } + + + /** + * 接收微信退款结果通知 + * URL需要与 WXPayConstants.WECHAT_REFUNDS_NOTIFY_URL 匹配 + */ + @PostMapping("/weChatPayRefundsNotify") + public ResponseEntity<Map<String, String>> handleWeChatRefundsNotify(HttpServletRequest request, @RequestBody String requestBody) { + + Map<String, String> responseMap = new HashMap<>(); + try { + // 1. 验证签名 + boolean verifyResult = WXPaySignatureCertificateUtil.verifyNotify(request, requestBody); + if (!verifyResult) { + log.error("微信退款通知验签失败!"); + responseMap.put("code", "FAIL"); + responseMap.put("message", "验签失败"); + return new ResponseEntity<>(responseMap, HttpStatus.BAD_REQUEST); + } + log.info("微信退款通知验签成功。"); + + // 2. 解析通知体 + JsonNode notifyData = objectMapper.readTree(requestBody); + String eventType = notifyData.get("event_type").asText(); + + // 处理退款成功或异常的通知 + if ("REFUND.SUCCESS".equals(eventType) || "REFUND.ABNORMAL".equals(eventType) || "REFUND.CLOSED".equals(eventType)) { + log.info("处理退款通知,类型: {}", eventType); + JsonNode resourceNode = notifyData.get("resource"); + String associatedData = resourceNode.get("associated_data").asText(); + String nonce = resourceNode.get("nonce").asText(); + String ciphertext = resourceNode.get("ciphertext").asText(); + + // 3. 解密关键信息 + String decryptedDataJson = WxPayAesUtil.decrypt(WxV3PayConfig.apiV3Key, associatedData, nonce, ciphertext); + log.info("解密后的退款通知信息: {}", decryptedDataJson); + JsonNode decryptedData = objectMapper.readTree(decryptedDataJson); + + // 4. 处理业务逻辑 + String outTradeNo = decryptedData.get("out_trade_no").asText(); + String outRefundNo = decryptedData.get("out_refund_no").asText(); + String refundStatus = decryptedData.get("refund_status").asText(); // SUCCESS, CLOSED, ABNORMAL + // ... 获取其他需要的信息,例如退款金额、微信退款单号 refund_id 等 + + // TODO: 在这里实现你的退款业务逻辑: + // 1. 根据 outRefundNo 查询你的数据库退款单状态。 + // 2. 判断退款单是否已经处理过。 + // 3. 根据 refundStatus 更新退款单状态。 + // 4. 如果退款成功 (SUCCESS),可能需要执行一些操作,如返还库存、通知用户等。 + // 5. 如果退款关闭或异常,也需要记录状态。 + + log.info("退款业务逻辑处理完成,商户订单号: {}, 商户退款单号: {}, 退款状态: {}", outTradeNo, outRefundNo, refundStatus); + + } else { + log.warn("收到非退款类型的通知: {}", eventType); + } + + // 5. 返回成功响应给微信平台 + responseMap.put("code", "SUCCESS"); + responseMap.put("message", "成功"); + return new ResponseEntity<>(responseMap, HttpStatus.OK); + + } catch (Exception e) { + log.error("处理微信退款通知异常", e); + responseMap.put("code", "FAIL"); + responseMap.put("message", "处理失败"); + return new ResponseEntity<>(responseMap, HttpStatus.INTERNAL_SERVER_ERROR); + } + } + +} diff --git a/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java new file mode 100644 index 0000000..5797619 --- /dev/null +++ b/cloud-server-activity/src/main/java/com/dsh/activity/util/wx/WxV3PayConfig.java @@ -0,0 +1,56 @@ +package com.dsh.activity.util.wx; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import javax.annotation.PostConstruct; // 如果需要静态访问 + +@Component +public class WxV3PayConfig { + + private String appIdValue = "wx41d32f362ba0f911"; + public static String APP_ID= "wx41d32f362ba0f911"; + + private String mchIdValue= "1681873607"; + public static String Mch_ID= "1681873607"; + + private String apiV3KeyValue= "1skiujh28376shznxmslwosiusytersq"; + public static String apiV3Key= "1skiujh28376shznxmslwosiusytersq"; + + private String mchSerialNoValue= "55714944F7A7E52526F708280B176DCC838F371A"; + public static String mchSerialNo= "55714944F7A7E52526F708280B176DCC838F371A"; + + private String privateKeyPathValue= "D:\\玩湃v3参数\\1681873607_20250424_cert\\apiclient_key.pem"; + public static String privateKeyPath= "D:\\玩湃v3参数\\1681873607_20250424_cert\\apiclient_key.pem"; + + // 如果需要静态访问,可以使用 @PostConstruct 初始化静态变量 + @PostConstruct + public void init() { + APP_ID = this.appIdValue; + Mch_ID = this.mchIdValue; + apiV3Key = this.apiV3KeyValue; + mchSerialNo = this.mchSerialNoValue; + privateKeyPath = this.privateKeyPathValue; // WXPaySignatureCertificateUtil 会用到这个路径 + + // 可以在这里加一些非空检查 + if (APP_ID == null || Mch_ID == null || apiV3Key == null || mchSerialNo == null || privateKeyPath == null) { + System.err.println("微信支付V3配置加载不完整,请检查配置文件!"); + // 在实际应用中,这里可能需要抛出异常或采取其他错误处理措施 + } else { + System.out.println("微信支付V3配置加载完成。"); + } + } + + // 注意: WXPaySignatureCertificateUtil 中的 getPrivateKey() 方法现在应该使用 WxV3PayConfig.privateKeyPath + // 你需要稍微修改 WXPaySignatureCertificateUtil.getPrivateKey() 方法: + /* + public static PrivateKey getPrivateKey() { + if (cachedPrivateKey != null) { + return cachedPrivateKey; + } + try { + String filePath = WxV3PayConfig.privateKeyPath; // 使用配置类中的路径 + // ... rest of the method ... + } // ... catch blocks ... + } + */ +} \ No newline at end of file diff --git a/cloud-server-activity/src/main/resources/mapper/HuiminCardMapper.xml b/cloud-server-activity/src/main/resources/mapper/HuiminCardMapper.xml index a727d27..56106c9 100644 --- a/cloud-server-activity/src/main/resources/mapper/HuiminCardMapper.xml +++ b/cloud-server-activity/src/main/resources/mapper/HuiminCardMapper.xml @@ -12,7 +12,6 @@ and t1.status = 1 order by t1.sort desc - limit #{pageNo}, #{pageSize} </select> <select id="getHuiminAgreementAndListNolimit" resultType="com.dsh.activity.entity.THuiminCard"> @@ -31,6 +30,7 @@ t1.status!=1 and t1.appUserId = #{appUserId} +order by t1.status asc limit #{pageNo}, #{pageSize} </select> diff --git a/cloud-server-activity/src/main/resources/mapper/PayHuiminMapper.xml b/cloud-server-activity/src/main/resources/mapper/PayHuiminMapper.xml index 2793cb5..758cf33 100644 --- a/cloud-server-activity/src/main/resources/mapper/PayHuiminMapper.xml +++ b/cloud-server-activity/src/main/resources/mapper/PayHuiminMapper.xml @@ -173,6 +173,7 @@ </when> </choose> </if> + and tph.paymentTime IS NOT NULL </where> GROUP BY tph.id ORDER BY tph.insertTime DESC diff --git a/cloud-server-activity/src/main/resources/sharding-jdbc.properties b/cloud-server-activity/src/main/resources/sharding-jdbc.properties index a8273e2..7f8645e 100644 --- a/cloud-server-activity/src/main/resources/sharding-jdbc.properties +++ b/cloud-server-activity/src/main/resources/sharding-jdbc.properties @@ -1,22 +1,9 @@ -datasource.names=master0 -datasource.master0.type=com.alibaba.druid.pool.DruidDataSource -datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver -datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai -datasource.master0.username=root -datasource.master0.password=123456 -datasource.master0.maxActive=20 -datasource.master0.maxWait=60000 -datasource.master0.minIdle=5 -datasource.master0.initialSize=2 - - - #datasource.names=master0 #datasource.master0.type=com.alibaba.druid.pool.DruidDataSource #datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver -#datasource.master0.url=jdbc:mysql://127.0.0.1:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai +#datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai #datasource.master0.username=root -#datasource.master0.password=playpai2023! +#datasource.master0.password=123456 #datasource.master0.maxActive=20 #datasource.master0.maxWait=60000 #datasource.master0.minIdle=5 @@ -24,4 +11,17 @@ +datasource.names=master0 +datasource.master0.type=com.alibaba.druid.pool.DruidDataSource +datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver +datasource.master0.url=jdbc:mysql://127.0.0.1:3306/playpai_activity?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai +datasource.master0.username=root +datasource.master0.password=playpai2023! +datasource.master0.maxActive=20 +datasource.master0.maxWait=60000 +datasource.master0.minIdle=5 +datasource.master0.initialSize=2 + + + diff --git a/cloud-server-communityWorldCup/src/main/resources/sharding-jdbc.properties b/cloud-server-communityWorldCup/src/main/resources/sharding-jdbc.properties index 395fba3..9156bff 100644 --- a/cloud-server-communityWorldCup/src/main/resources/sharding-jdbc.properties +++ b/cloud-server-communityWorldCup/src/main/resources/sharding-jdbc.properties @@ -1,22 +1,9 @@ -#datasource.names=master0 -#datasource.master0.type=com.alibaba.druid.pool.DruidDataSource -#datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver -#datasource.master0.url=jdbc:mysql://8.137.22.229:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai -#datasource.master0.username=root -#datasource.master0.password=playpai2023! -#datasource.master0.maxActive=20 -#datasource.master0.maxWait=60000 -#datasource.master0.minIdle=5 -#datasource.master0.initialSize=2 - - - datasource.names=master0 datasource.master0.type=com.alibaba.druid.pool.DruidDataSource datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver -datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai +datasource.master0.url=jdbc:mysql://8.137.22.229:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai datasource.master0.username=root -datasource.master0.password=123456 +datasource.master0.password=playpai2023! datasource.master0.maxActive=20 datasource.master0.maxWait=60000 datasource.master0.minIdle=5 @@ -24,3 +11,16 @@ +#datasource.names=master0 +#datasource.master0.type=com.alibaba.druid.pool.DruidDataSource +#datasource.master0.driverClassName=com.mysql.cj.jdbc.Driver +#datasource.master0.url=jdbc:mysql://192.168.110.80:3306/playpai_community_world_cup?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai +#datasource.master0.username=root +#datasource.master0.password=123456 +#datasource.master0.maxActive=20 +#datasource.master0.maxWait=60000 +#datasource.master0.minIdle=5 +#datasource.master0.initialSize=2 + + + diff --git a/cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java b/cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java index efaa004..65f9804 100644 --- a/cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java +++ b/cloud-server-competition/src/main/java/com/dsh/competition/service/impl/ParticipantServiceImpl.java @@ -67,6 +67,10 @@ if (null != one) { return ResultUtil.error("电话号码重复"); } +// Participant two = this.getOne(new QueryWrapper<Participant>().eq("appUserId", uid).eq("idcard", addParticipant.getIdcard()).eq("state", 1)); +// if (null != two) { +// return ResultUtil.error("身份证号码重复"); +// } if (ToolUtil.isNotEmpty(addParticipant.getName()) && ToolUtil.isNotEmpty(addParticipant.getIdcard())) { Boolean aBoolean = JuHeUtil.idcardAuthentication(addParticipant.getIdcard(), addParticipant.getName()); if (!aBoolean) { @@ -94,8 +98,8 @@ tStudent.setName(addParticipant.getName()); tStudent.setPhone(addParticipant.getPhone()); if (ToolUtil.isNotEmpty(addParticipant.getBirthday())){ - Date date = DateUtils.parseDate(addParticipant.getBirthday()); - tStudent.setBirthday(date); + Date parse = sdf.parse(addParticipant.getBirthday()); + tStudent.setBirthday(parse); }else{ String birthDateStr = addParticipant.getIdcard().substring(6, 14); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); @@ -187,7 +191,9 @@ Integer integer = coursePackagePaymentClient.queryResidueClassHour(tStudent.getId()); participantVo.setResidueClassHour(integer); participantVo.setPhone(tStudent.getPhone()); - participantVo.setHeight(tStudent.getHeight().intValue()); + if (tStudent.getHeight()!=null){ + participantVo.setHeight(tStudent.getHeight().intValue()); + } participantVo.setWeight(tStudent.getWeight()); participantVo.setHeadImg(tStudent.getHeadImg()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); -- Gitblit v1.7.1