| | |
| | | @ResponseBody |
| | | @GetMapping("/base/appUser/getAppUserByPhone/{phone}") |
| | | public TAppUser getAppUserByPhone(@PathVariable("phone") String phone) { |
| | | TAppUser appUser = appUserService.getOne(new QueryWrapper<TAppUser>().eq("phone", phone)); |
| | | TAppUser appUser = appUserService.getOne(new QueryWrapper<TAppUser>().eq("phone", phone) |
| | | .eq("state",1)); |
| | | return appUser; |
| | | } |
| | | |
| | |
| | | return new ArrayList<>(); |
| | | } |
| | | } |
| | | @PostMapping("/student/getStudentById") |
| | | @ResponseBody |
| | | TStudent getStudentById(Integer studentId) { |
| | | TStudent byId = studentService.getById(studentId); |
| | | return byId; |
| | | } |
| | | |
| | | |
| | | @ResponseBody |
| | |
| | | return ResultUtil.tokenErr(); |
| | | } |
| | | TPayHuimin tPayHuimin = payHuiminService.getById(id); |
| | | if (tPayHuimin.getPaymentType()==3){ |
| | | // 后台录入直接退款 |
| | | tPayHuimin.setStatus(3); |
| | | payHuiminService.updateById(tPayHuimin); |
| | | return ResultUtil.success(); |
| | | } |
| | | if (tPayHuimin == null) { |
| | | return ResultUtil.error("订单不存在"); |
| | | } |
| | |
| | | // 已过期 |
| | | return ResultUtil.error("玩湃惠民卡已过期,不可退款"); |
| | | } |
| | | tPayHuimin.setStatus(4); |
| | | payHuiminService.updateById(tPayHuimin); |
| | | int count = huiminRecordService.list(new LambdaQueryWrapper<THuiminRecord>() |
| | | .eq(THuiminRecord::getPayId, tPayHuimin.getId())).size(); |
| | |
| | | if (ToolUtil.isNotEmpty(pointMercharsPayedVo.getStatus())) { |
| | | userPointsMerchandiseLambdaQueryWrapper.eq(UserPointsMerchandise::getStatus, pointMercharsPayedVo.getStatus()); |
| | | } |
| | | List<UserPointsMerchandise> list = upmseService.list(userPointsMerchandiseLambdaQueryWrapper.orderByDesc(UserPointsMerchandise::getPaymentTime)); |
| | | List<UserPointsMerchandise> list = upmseService.list(userPointsMerchandiseLambdaQueryWrapper.orderByDesc(UserPointsMerchandise::getPaymentTime) |
| | | .isNotNull(UserPointsMerchandise::getPaymentTime)); |
| | | System.out.println(list); |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); |
| | | if (list.size() > 0) { |
| | |
| | | package com.dsh.activity.controller; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.mb.cloud.common.data.controller.BaseController; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
| | |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.dsh.activity.entity.THuiminCard; |
| | | import com.dsh.activity.entity.TPayHuimin; |
| | | import com.dsh.activity.entity.TStudent; |
| | | import com.dsh.activity.feignclient.account.StudentClient; |
| | | import com.dsh.activity.service.HuiminCardService; |
| | | import com.dsh.activity.service.PayHuiminService; |
| | | import com.dsh.activity.util.UUIDUtil; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.util.StringUtils; |
| | | import org.springframework.web.bind.annotation.*; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | private HuiminCardService tHuiminCardService; |
| | | @Autowired |
| | | private PayHuiminService payHuiminService; |
| | | @Autowired |
| | | private StudentClient studentClient; |
| | | |
| | | |
| | | @PostMapping("/queryPage") |
| | |
| | | tHuiminCardService.updateById(tHuiminCard); |
| | | return "success"; |
| | | } |
| | | @GetMapping("/getCards") |
| | | |
| | | public List<THuiminCard> getCards(@RequestParam("storeId")String storeId){ |
| | | List<THuiminCard> list = tHuiminCardService.lambdaQuery().apply("FIND_IN_SET(" + storeId + ",storeIds)").list(); |
| | | return list; |
| | | } |
| | | @GetMapping("/addPayHuiMing") |
| | | public void addPayHuiMing(@RequestParam("studentId")Integer studentId, @RequestParam("cardId")Integer cardId){ |
| | | TPayHuimin tPayHuimin = new TPayHuimin(); |
| | | |
| | | THuiminCard byId = tHuiminCardService.getById(cardId); |
| | | List<TStudent> studentByIds = studentClient.getStudentByIds(studentId + ""); |
| | | if (CollUtil.isNotEmpty(studentByIds)){ |
| | | tPayHuimin.setAppUserId(studentByIds.get(0).getAppUserId()); |
| | | } |
| | | tPayHuimin.setSalesMoney(byId.getSalesMoney()); |
| | | tPayHuimin.setStudentId(studentId+""); |
| | | switch (byId.getHuiMinType()) { |
| | | case 1: |
| | | // 年度卡 |
| | | LocalDateTime localDateTime = LocalDateTime.now().plusDays(365); |
| | | // 将LocalDateTime转换为Date类型 |
| | | Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); |
| | | tPayHuimin.setEndTime(date); |
| | | break; |
| | | case 2: |
| | | // 年内卡 |
| | | Date date1 = new Date(); |
| | | date1.setMonth(11); |
| | | date1.setDate(31); |
| | | date1.setHours(23); |
| | | date1.setMinutes(59); |
| | | date1.setSeconds(57); |
| | | tPayHuimin.setEndTime(date1); |
| | | break; |
| | | } |
| | | tPayHuimin.setInsertTime(new Date()); |
| | | tPayHuimin.setPaymentType(3); |
| | | tPayHuimin.setRefundStatus(1); |
| | | tPayHuimin.setCardId(byId.getId()); |
| | | SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS"); |
| | | tPayHuimin.setCode(sdf.format(new Date()) + UUIDUtil.getNumberRandom(5)); |
| | | System.err.println("支付数据" + tPayHuimin); |
| | | tPayHuimin.setPaymentTime(new Date()); |
| | | tPayHuimin.setStatus(2); |
| | | payHuiminService.save(tPayHuimin); |
| | | } |
| | | } |
| | |
| | | @TableField("status") |
| | | private Integer status; |
| | | /** |
| | | *付款方式1微信2支付宝 |
| | | *付款方式1微信2支付宝 3后台录入 |
| | | */ |
| | | @TableField("paymentType") |
| | | private Integer paymentType; |
| | |
| | | @PostMapping("/student/queryStudentList") |
| | | List<TStudent> queryStudentList(Integer appUserId); |
| | | |
| | | @PostMapping("/student/getStudentById") |
| | | TStudent getStudentById(Integer studentId); |
| | | |
| | | /** |
| | | * 获取有学员的用户ids |
| | | * |
| | |
| | | import com.dsh.activity.util.DateUtil; |
| | | import com.dsh.activity.util.PayMoneyUtil; |
| | | import com.dsh.activity.util.ResultUtil; |
| | | import com.sun.org.apache.bcel.internal.generic.NEW; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import javax.annotation.Resource; |
| | | import java.math.BigDecimal; |
| | |
| | | .eq(THuiminRecord::getPayId, record.getId())); |
| | | record.setUseTimes(list.size()); |
| | | } |
| | | if (CollUtil.isNotEmpty(records)){ |
| | | List<Integer> appUserIdList = records.stream().map(SalesDetailVO::getAppUserId).collect(Collectors.toList()); |
| | | List<AppUser> appUserList = appUserClient.queryAppUserBatch(appUserIdList); |
| | | Map<Integer, AppUser> appUserMap = appUserList.stream().collect(Collectors.toMap(AppUser::getId, appUser -> appUser)); |
| | | records.forEach(item->{ |
| | | AppUser appUser = appUserMap.get(item.getAppUserId()); |
| | | if (Objects.nonNull(appUser)){ |
| | | item.setUserName(appUser.getName()); |
| | | item.setPhone(appUser.getPhone()); |
| | | for (SalesDetailVO record : records) { |
| | | AppUser appUser = appUserClient.queryAppUser(record.getAppUserId()); |
| | | if (Objects.nonNull(appUser)){ |
| | | record.setUserName(appUser.getName()); |
| | | record.setPhone(appUser.getPhone()); |
| | | } |
| | | StringBuilder stringBuilder = new StringBuilder(); |
| | | String[] split = record.getStudentId().split(","); |
| | | for (String s : split) { |
| | | String replace = s.replace(" ", ""); |
| | | List<TStudent> studentById = studentClient.getStudentByIds(replace); |
| | | if (!studentById.isEmpty()){ |
| | | stringBuilder.append(studentById.get(0).getName()+","); |
| | | } |
| | | List<TStudent> studentList = studentClient.getStudentByIds(item.getStudentId()); |
| | | if (CollUtil.isNotEmpty(studentList)){ |
| | | item.setStudentName(studentList.stream().map(TStudent::getName).collect(Collectors.joining(","))); |
| | | } |
| | | }); |
| | | |
| | | } |
| | | String string = stringBuilder.toString(); |
| | | if (StringUtils.hasLength(string)) { |
| | | String stringWithoutLastCharacter = string.substring(0, string.length() - 1); |
| | | record.setStudentName(stringWithoutLastCharacter); |
| | | } |
| | | } |
| | | // if (CollUtil.isNotEmpty(records)){ |
| | | // List<Integer> appUserIdList = records.stream().map(SalesDetailVO::getAppUserId).collect(Collectors.toList()); |
| | | // List<AppUser> appUserList = appUserClient.queryAppUserBatch(appUserIdList); |
| | | // Map<Integer, AppUser> appUserMap = appUserList.stream().collect(Collectors.toMap(AppUser::getId, appUser -> appUser)); |
| | | // records.forEach(item->{ |
| | | // AppUser appUser = appUserMap.get(item.getAppUserId()); |
| | | // if (Objects.nonNull(appUser)){ |
| | | // item.setUserName(appUser.getName()); |
| | | // item.setPhone(appUser.getPhone()); |
| | | // } |
| | | // List<TStudent> studentList = studentClient.getStudentByIds(item.getStudentId()); |
| | | // if (CollUtil.isNotEmpty(studentList)){ |
| | | // item.setStudentName(studentList.stream().map(TStudent::getName).collect(Collectors.joining(","))); |
| | | // } |
| | | // }); |
| | | // } |
| | | return salesDetailVOPage; |
| | | } |
| | | |
| | |
| | | public ResultUtil<?> refund(Integer id) throws AlipayApiException { |
| | | TPayHuimin payHuimin = this.getById(id); |
| | | if (Objects.nonNull(payHuimin)) { |
| | | // 后台录入订单直接修改为已退款 |
| | | if (payHuimin.getPaymentType()==3){ |
| | | payHuimin.setStatus(3); |
| | | payHuimin.setRefundStatus(3); |
| | | payHuimin.setRefundNumber(payHuimin.getCode()); |
| | | payHuimin.setRefundTime(new Date()); |
| | | this.updateById(payHuimin); |
| | | return ResultUtil.success(); |
| | | } |
| | | Integer count = huiminRecordService.lambdaQuery() |
| | | .eq(THuiminRecord::getPayId, payHuimin.getId()) |
| | | .count(); |
| | |
| | | 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"; |
| | | |
| | | |
| | | |
| | | |
| | | } |
| | | //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"; |
| | | // |
| | | // |
| | | // |
| | | // |
| | | //} |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | //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); |
| | | // } |
| | | // } |
| | | //} |
| | |
| | | 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.Component; |
| | | 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; |
| | | |
| | | @Component |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | } |
| | | //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.Component; |
| | | //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; |
| | | // |
| | | //@Component |
| | | //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); |
| | | // } |
| | | // } |
| | | // |
| | | //} |
| | |
| | | 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); |
| | | } |
| | | } |
| | | } |
| | | //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); |
| | | // } |
| | | // } |
| | | //} |
| | |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | } |
| | | //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); |
| | | // } |
| | | // } |
| | | // |
| | | //} |
| | |
| | | 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 ... |
| | | } |
| | | */ |
| | | } |
| | | //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 ... |
| | | // } |
| | | // */ |
| | | //} |
| | |
| | | |
| | | @GetMapping("/tHuiminCard/changeState") |
| | | String changeState(@RequestParam("id")Integer id, @RequestParam("status") Integer status); |
| | | @GetMapping("/tHuiminCard/getCards") |
| | | |
| | | List<THuiminCard> getCards(@RequestParam("storeId")String storeId); |
| | | @GetMapping("/tHuiminCard/addPayHuiMing") |
| | | void addPayHuiMing(@RequestParam("studentId")Integer studentId, @RequestParam("cardId")Integer cardId); |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.core.metadata.IPage; |
| | | import com.baomidou.mybatisplus.extension.api.R; |
| | | import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
| | | import com.dsh.course.feignClient.account.AppUserClient; |
| | | import com.dsh.course.feignClient.account.StudentClient; |
| | | import com.dsh.course.feignClient.account.model.TAppUser; |
| | | import com.dsh.course.feignClient.activity.HuiminCardClient; |
| | | import com.dsh.course.feignClient.activity.model.THuiminCard; |
| | | import com.dsh.guns.config.UserExt; |
| | |
| | | private TOperatorService operatorService; |
| | | @Autowired |
| | | private ITSiteService tSiteService; |
| | | @Autowired |
| | | private AppUserClient appUserClient; |
| | | @Autowired |
| | | private StudentClient studentClient; |
| | | |
| | | |
| | | /** |
| | | * 跳转到玩湃惠民卡首页 |
| | |
| | | model.addAttribute("objectType", user.getObjectType()); |
| | | return PREFIX + "tHuiminCard_detail.html"; |
| | | } |
| | | /** |
| | | * 跳转到订单录入页面 |
| | | */ |
| | | @RequestMapping("/tHuiminCardAddOrder") |
| | | public String tHuiminCardAddOrder(Model model) { |
| | | Integer objectType = UserExt.getUser().getObjectType(); |
| | | Integer objectId = UserExt.getUser().getObjectId(); |
| | | model.addAttribute("role", objectType); |
| | | if (objectType==1){ |
| | | List<TOperator> list = operatorService.list(new LambdaQueryWrapper<TOperator>() |
| | | .eq(TOperator::getState, 1)); |
| | | model.addAttribute("operators", list); |
| | | }else if (objectType==2){ |
| | | List<TStore> list = storeService.list(new LambdaQueryWrapper<TStore>() |
| | | .eq(TStore::getOperatorId, objectId)); |
| | | model.addAttribute("stores", list); |
| | | }else{ |
| | | List<THuiminCard> cards = huiminCardClient.getCards(objectId + ""); |
| | | model.addAttribute("cards", cards); |
| | | } |
| | | return PREFIX + "tHuiminCard_addOrder.html"; |
| | | } |
| | | @RequestMapping("/addPayHuiMing") |
| | | @ResponseBody |
| | | public Object addPayHuiMing(Integer studentId,Integer cardId) { |
| | | |
| | | huiminCardClient.addPayHuiMing(studentId ,cardId); |
| | | |
| | | return 200; |
| | | } |
| | | @RequestMapping(value = "/checkUser") |
| | | @ResponseBody |
| | | public Object checkUser(String phone) { |
| | | TAppUser appUserByPhone = appUserClient.getAppUserByPhone(phone); |
| | | if (appUserByPhone==null){ |
| | | return 500; |
| | | } |
| | | List<TStudent> tStudents = studentClient.queryStudentList(appUserByPhone.getId()); |
| | | if (tStudents.isEmpty()){ |
| | | return 501; |
| | | } |
| | | return tStudents; |
| | | } |
| | | @RequestMapping(value = "/getStores") |
| | | @ResponseBody |
| | | public Object getStores(String operatorId) { |
| | | if (operatorId.isEmpty()){ |
| | | return new ArrayList<>(); |
| | | }else{ |
| | | List<TStore> list = storeService.lambdaQuery().eq(TStore::getOperatorId, operatorId).list(); |
| | | return list; |
| | | } |
| | | |
| | | } |
| | | @RequestMapping(value = "/getCards") |
| | | @ResponseBody |
| | | public Object getCards(String storeId) { |
| | | List<THuiminCard> list = huiminCardClient.getCards(storeId); |
| | | return list; |
| | | } |
| | | /** |
| | | * 获取玩湃惠民卡详情 |
| | | */ |
| | |
| | | } |
| | | } |
| | | }else { |
| | | TCity one = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getId, tStore.getProvinceCode())); |
| | | TCity one = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getCode, tStore.getProvinceCode())); |
| | | tStore.setProvince(one.getName()); |
| | | tStore.setProvinceCode(one.getCode()); |
| | | TCity one1 = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getId, tStore.getCityCode())); |
| | | TCity one1 = cityService.getOne(new LambdaQueryWrapper<TCity>().eq(TCity::getCode, tStore.getCityCode())); |
| | | tStore.setCity(one1.getName()); |
| | | tStore.setCityCode(one1.getCode()); |
| | | } |
| | |
| | | <i class="fa fa-remove"></i> |
| | | 下架 |
| | | </button> |
| | | <button type="button" class="btn btn-primary" v-on:click="addOrder()"> |
| | | <i class="fa fa-remove"></i> |
| | | 订单录入 |
| | | </button> |
| | | </div> |
| | | </div> |
| | | <el-table |
| | |
| | | </div> |
| | | <script src="${ctxPath}/js/vue/vue.js"></script> |
| | | <script src="${ctxPath}/js/elementui/index.js"></script> |
| | | <script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard2.js"></script> |
| | | <link rel="stylesheet" href="${ctxPath}/js/elementui/index.css"> |
| | | |
| | | <script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard2.js"></script> |
| | | <script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard.js"></script> |
| | | <script src="${ctxPath}/modular/system/tHuiminAgreement/tHuiminAgreement.js"></script> |
| | | <script> |
New file |
| | |
| | | @layout("/common/_container.html"){ |
| | | <style> |
| | | .avatar-uploader .el-upload { |
| | | border: 1px dashed #d9d9d9; |
| | | border-radius: 6px; |
| | | cursor: pointer; |
| | | position: relative; |
| | | height: 100px; |
| | | width: 100px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .avatar-uploader .el-upload:hover { |
| | | border-color: #409EFF; |
| | | } |
| | | |
| | | .avatar-uploader-icon { |
| | | font-size: 28px; |
| | | color: #8c939d; |
| | | width: 100px; |
| | | height: 100px; |
| | | line-height: 100px; |
| | | margin-top: 32px; |
| | | text-align: center; |
| | | } |
| | | |
| | | .avatar { |
| | | width: 100px; |
| | | height: 100px; |
| | | display: block; |
| | | } |
| | | |
| | | .col-sm-12 { |
| | | margin-top: 20px; |
| | | } |
| | | |
| | | .col-sm-12 select { |
| | | height: 33px; |
| | | } |
| | | |
| | | |
| | | </style> |
| | | <script type="text/javascript" src="http://webapi.amap.com/maps?v=1.4.15&key=77b37f0753049c4e712ea79a24e0719c"></script> |
| | | <div class="ibox float-e-margins"> |
| | | <div class="ibox-content"> |
| | | <div class="form-horizontal" id="carInfoForm"> |
| | | <div class="form-group"> |
| | | <input hidden id="role" value="${role}"> |
| | | |
| | | <div style="display:flex"> |
| | | <label class="col-sm-3 control-label">*用户手机号:</label> |
| | | <input type="number" id="phone" name="type" placeholder="请输入"> |
| | | <button type="button" class="btn btn-primary" onclick="THuiminCardInfo.checkUser()">搜索 |
| | | </button> |
| | | </div> |
| | | </div> |
| | | <div class="form-group" id="users"> |
| | | <label class="col-sm-3 control-label">*绑定人员信息:</label> |
| | | <div class="col-sm-9"> |
| | | <select class="form-control" id="userList" name="userList"> |
| | | |
| | | </select> |
| | | </div> |
| | | </div> |
| | | |
| | | @if(role==1){ |
| | | <div class="form-group"> |
| | | <label class="col-sm-3 control-label">*所属运营商:</label> |
| | | <div class="col-sm-9"> |
| | | <select class="form-control" id="account" name="account" onchange="THuiminCardInfo.getStores(this)"> |
| | | <option value="">请选择运营商</option> |
| | | <option value="0">平台</option> |
| | | @for(i in operators){ |
| | | <option value="${i.id}">${i.name}</option> |
| | | @} |
| | | </select> |
| | | </div> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label class="col-sm-3 control-label">*可用门店:</label> |
| | | <div class="col-sm-9"> |
| | | <select class="form-control" id="shopId" name="shopId" onchange="THuiminCardInfo.getCards(this)"> |
| | | <option value="">选择门店</option> |
| | | |
| | | </select> |
| | | </div> |
| | | </div> |
| | | <div class="form-group"> |
| | | <label class="col-sm-3 control-label">选择惠民卡:</label> |
| | | <div class="col-sm-9"> |
| | | <select class="form-control" id="cards" name="cards" > |
| | | <option value="">选择惠民卡</option> |
| | | </select> |
| | | </div> |
| | | </div> |
| | | @} |
| | | |
| | | @if(role==2){ |
| | | <div class="form-group" id="shop"> |
| | | <label class="col-sm-3 control-label">所属门店:</label> |
| | | <div class="col-sm-9"> |
| | | <select class="form-control" id="shopId" name="shopId" onchange="THuiminCardInfo.getCards(this)" > |
| | | <option value="">选择门店</option> |
| | | @for(i in stores){ |
| | | <option value="${i.id}">${i.name}</option> |
| | | @} |
| | | </select> |
| | | </div> |
| | | </div> |
| | | <div class="form-group" id="card"> |
| | | <label class="col-sm-3 control-label">选择惠民卡:</label> |
| | | <div class="col-sm-9"> |
| | | <select class="form-control" id="cards" name="cards" > |
| | | <option value="">选择惠民卡</option> |
| | | </select> |
| | | </div> |
| | | </div> |
| | | @} |
| | | @if(role==3){ |
| | | <div class="form-group" id="card"> |
| | | <label class="col-sm-3 control-label">选择惠民卡:</label> |
| | | <div class="col-sm-9"> |
| | | <select class="form-control" id="cards" name="cards" > |
| | | <option value="">选择惠民卡</option> |
| | | @for(i in cards){ |
| | | <option value="${i.id}">${i.name}</option> |
| | | @} |
| | | </select> |
| | | </div> |
| | | </div> |
| | | @} |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="row btn-group-m-t"> |
| | | <div class="col-sm-10 col-sm-offset-5"> |
| | | <#button btnCss="info" name="提交" id="ensure" icon="fa-check" clickFun="THuiminCardInfo.addSubmit()"/> |
| | | <#button btnCss="danger" name="关闭" id="cancel" icon="fa-eraser" clickFun="THuiminCardInfo.close()"/> |
| | | </div> |
| | | </div> |
| | | |
| | | </div> |
| | | |
| | | |
| | | <script src="${ctxPath}/modular/system/tHuiminCard/tHuiminCard3.js"></script> |
| | | <script src="${ctxPath}/js/vue/vue.js"></script> |
| | | <script src="${ctxPath}/js/elementui/index.js"></script> |
| | | <link rel="stylesheet" href="${ctxPath}/js/elementui/index.css"> |
| | | |
| | | @} |
| | |
| | | <script type="text/javascript" src="http://webapi.amap.com/maps?v=1.4.15&key=77b37f0753049c4e712ea79a24e0719c"></script> |
| | | <div class="ibox float-e-margins"> |
| | | <div class="ibox-content"> |
| | | |
| | | |
| | | <div class="form-horizontal" id="carInfoForm"> |
| | | <input hidden id="role" value="${role}"> |
| | | <input hidden id="operator" value="${operator}"> |
| | |
| | | }); |
| | | this.layerIndex = index; |
| | | }; |
| | | THuiminCard.addOrder = function (id, pageType) { |
| | | console.log(id) |
| | | let index = layer.open({ |
| | | type: 2, |
| | | title: '订单录入', |
| | | area: ['100%', '100%'], //宽高 |
| | | fix: false, //不固定 |
| | | maxmin: true, |
| | | content: Feng.ctxPath + '/tHuiminCard/tHuiminCardAddOrder' |
| | | }); |
| | | this.layerIndex = index; |
| | | }; |
| | | |
| | | /** |
| | | * 删除玩湃惠民卡 |
| | |
| | | id = this.ids[0] |
| | | } |
| | | THuiminCard.openTHuiminCardDetail(id,'detail') |
| | | }, |
| | | addOrder() { |
| | | THuiminCard.addOrder() |
| | | } |
| | | }, |
| | | created() { |
| | | // 初始化逻辑 |
| | | this.handleSearch() |
| | | } |
| | | }); |
| | | }); |
| | | var THuiminCardInfo = { |
| | | goodsPicArray: [], |
| | | tCarInfoData : {}, |
| | | validateFields: { |
| | | } |
| | | }; |
| | | THuiminCardInfo.close = function() { |
| | | parent.layer.close(window.parent.THuiminCard.layerIndex); |
| | | } |
| | | THuiminCardInfo.checkUser = function (e) { |
| | | console.log("用户手机号") |
| | | console.log(e) |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/checkUser", function(data){ |
| | | if(data==500){ |
| | | $("#userList").empty() |
| | | Feng.error("操作失败,当前用户未注册") |
| | | return; |
| | | }else if (data==501){ |
| | | $("#userList").empty() |
| | | Feng.error("操作失败,当前用户未添加人员信息") |
| | | return; |
| | | } |
| | | |
| | | else{ |
| | | var content='<option value="">选择人员</option>'; |
| | | $.each(data, function(k,v) { |
| | | content += "<option value='"+v.id+"'>"+v.name+"</option>"; |
| | | }); |
| | | $("#userList").empty().append(content); |
| | | $("#shopId").empty(); |
| | | $("#cards").empty(); |
| | | } |
| | | }); |
| | | ajax.set("phone",phone); |
| | | ajax.start(); |
| | | } |
| | | THuiminCardInfo.getStores = function (e) { |
| | | var operatorId=$(e).val(); |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getStores", function(data){ |
| | | if(data!=null){ |
| | | var content='<option value="">选择门店</option>'; |
| | | $.each(data, function(k,v) { |
| | | content += "<option value='"+v.id+"'>"+v.name+"</option>"; |
| | | }); |
| | | $("#shopId").empty().append(content); |
| | | $("#cards").empty() |
| | | } |
| | | }); |
| | | ajax.set("operatorId",operatorId); |
| | | ajax.start(); |
| | | } |
| | | THuiminCardInfo.getCards = function (e) { |
| | | var operatorId=$(e).val(); |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getCards", function(data){ |
| | | if(data!=null){ |
| | | var content='<option value="">选择玩湃惠民卡</option>'; |
| | | $.each(data, function(k,v) { |
| | | content += "<option value='"+v.id+"'>"+v.name+"</option>"; |
| | | }); |
| | | $("#cards").empty().append(content); |
| | | } |
| | | }); |
| | | ajax.set("storeId",operatorId); |
| | | ajax.start(); |
| | | } |
| | | THuiminCardInfo.addSubmit = function () { |
| | | let cardId = $("#cards").val() |
| | | if(cardId==""){ |
| | | Feng.info("请选择惠民卡") |
| | | return; |
| | | } |
| | | let user = $("#user").val() |
| | | if(user==""){ |
| | | Feng.info("请选择绑定人员信息") |
| | | return; |
| | | } |
| | | var operatorId=$(e).val(); |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/addPayHuiMing", function(data){ |
| | | if(data!=null){ |
| | | var content='<option value="">选择玩湃惠民卡</option>'; |
| | | $.each(data, function(k,v) { |
| | | content += "<option value='"+v.id+"'>"+v.name+"</option>"; |
| | | }); |
| | | $("#cards").empty().append(content); |
| | | } |
| | | }); |
| | | ajax.set("studentId",user); |
| | | ajax.set("cardId",cardId); |
| | | ajax.start(); |
| | | } |
New file |
| | |
| | | var THuiminCardInfo = { |
| | | goodsPicArray: [], |
| | | tCarInfoData : {}, |
| | | validateFields: { |
| | | } |
| | | }; |
| | | THuiminCardInfo.close = function() { |
| | | parent.layer.close(window.parent.THuiminCard.layerIndex); |
| | | } |
| | | THuiminCardInfo.checkUser = function () { |
| | | |
| | | if ($("#phone").val()==""){ |
| | | Feng.error("请输入手机号") |
| | | return |
| | | } |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/checkUser", function(data){ |
| | | if(data==500){ |
| | | $("#userList").empty() |
| | | Feng.error("操作失败,当前用户未注册") |
| | | return; |
| | | }else if (data==501){ |
| | | $("#userList").empty() |
| | | Feng.error("操作失败,当前用户未添加人员信息") |
| | | return; |
| | | } |
| | | |
| | | else{ |
| | | var content='<option value="">选择人员</option>'; |
| | | $.each(data, function(k,v) { |
| | | content += "<option value='"+v.id+"'>"+v.name+"</option>"; |
| | | }); |
| | | $("#userList").empty().append(content); |
| | | $("#shopId").empty(); |
| | | $("#cards").empty(); |
| | | } |
| | | }); |
| | | ajax.set("phone",$("#phone").val()); |
| | | ajax.start(); |
| | | } |
| | | THuiminCardInfo.getStores = function (e) { |
| | | var operatorId=$("#account").val(); |
| | | console.log("运营商id") |
| | | console.log(operatorId) |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getStores", function(data){ |
| | | if(data!=null){ |
| | | var content='<option value="">选择门店</option>'; |
| | | $.each(data, function(k,v) { |
| | | content += "<option value='"+v.id+"'>"+v.name+"</option>"; |
| | | }); |
| | | $("#shopId").empty().append(content); |
| | | $("#cards").empty() |
| | | } |
| | | if (operatorId==""){ |
| | | console.log("置空") |
| | | $("#shopId").empty() |
| | | $("#cards").empty() |
| | | } |
| | | }); |
| | | ajax.set("operatorId",operatorId); |
| | | ajax.start(); |
| | | } |
| | | THuiminCardInfo.getCards = function (e) { |
| | | var operatorId=$("#shopId").val(); |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/getCards", function(data){ |
| | | if(data!=null){ |
| | | var content='<option value="">选择玩湃惠民卡</option>'; |
| | | $.each(data, function(k,v) { |
| | | content += "<option value='"+v.id+"'>"+v.huiMinName+"</option>"; |
| | | }); |
| | | $("#cards").empty().append(content); |
| | | } |
| | | }); |
| | | ajax.set("storeId",operatorId); |
| | | ajax.start(); |
| | | } |
| | | THuiminCardInfo.addSubmit = function () { |
| | | let cardId = $("#cards").val() |
| | | if(cardId==""){ |
| | | Feng.info("请选择惠民卡") |
| | | return; |
| | | } |
| | | let user = $("#userList").val() |
| | | if(user==""){ |
| | | Feng.info("请选择绑定人员信息") |
| | | return; |
| | | } |
| | | var ajax = new $ax(Feng.ctxPath + "/tHuiminCard/addPayHuiMing", function(data){ |
| | | if (data==200){ |
| | | Feng.success("录入成功") |
| | | THuiminCardInfo.close() |
| | | return |
| | | } |
| | | |
| | | }); |
| | | ajax.set("studentId",user); |
| | | ajax.set("cardId",cardId); |
| | | ajax.start(); |
| | | } |