ruoyi-admin/src/main/resources/application.yml
@@ -1,4 +1,4 @@ # 项目相关配置 spring: profiles: active: prod active: test ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/InsuranceController.java
@@ -1,22 +1,36 @@ package com.ruoyi.web.controller.api; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.common.core.domain.BaseModel; import com.ruoyi.common.core.domain.BasePage; import com.ruoyi.common.core.domain.R; import com.ruoyi.common.utils.OrderNos; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.dto.BuyInsuranceDto; import com.ruoyi.system.model.TBanner; import com.ruoyi.system.model.TCourse; import com.ruoyi.system.model.TInsuranceOrder; import com.ruoyi.system.model.*; import com.ruoyi.system.query.GetCourseListQuery; import com.ruoyi.system.service.TAppUserService; import com.ruoyi.system.service.TBannerService; import com.ruoyi.system.service.TInsuranceOrderService; import com.ruoyi.system.service.TInsuranceService; import com.ruoyi.system.vo.UserInsuranceVo; import com.ruoyi.system.wxPay.utils.WxRefundUtil; import com.ruoyi.system.wxPay.utils.WxV3Pay; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.LocalDateTime; import java.util.Date; import java.util.Map; import java.util.Objects; @Slf4j @@ -27,10 +41,16 @@ private final TInsuranceOrderService tInsuranceOrderService; private final TokenService tokenService; private final TInsuranceService tInsuranceService; private final WxV3Pay wxV3Pay; private final TAppUserService tAppUserService; public InsuranceController(TInsuranceOrderService tInsuranceOrderService, TokenService tokenService) { public InsuranceController(TInsuranceOrderService tInsuranceOrderService, TokenService tokenService, TInsuranceService tInsuranceService, WxV3Pay wxV3Pay, TAppUserService tAppUserService) { this.tInsuranceOrderService = tInsuranceOrderService; this.tokenService = tokenService; this.tInsuranceService = tInsuranceService; this.wxV3Pay = wxV3Pay; this.tAppUserService = tAppUserService; } @@ -55,12 +75,16 @@ @PostMapping("/buyInsurance") @ApiOperation(value = "保险模块--购买保险") public R<?> buyInsurance(@RequestBody BuyInsuranceDto dto) { public R<?> buyInsurance(@RequestBody BuyInsuranceDto dto) throws ParseException { String userId = tokenService.getLoginUserApplet().getUserId(); TAppUser appUser = tAppUserService.getById(userId); // 查询保险订单 TInsuranceOrder one = tInsuranceOrderService.getOne(Wrappers.<TInsuranceOrder>lambdaQuery().eq(TInsuranceOrder::getAppUserId, userId).eq(TInsuranceOrder::getStatus, 2).orderByDesc(BaseModel::getCreateTime).last("LIMIT 1")); if(one!=null){ if(one.getEndTime().after(new Date())){ return R.fail("您已购买保险"); } } TInsuranceOrder insuranceOrder = new TInsuranceOrder(); insuranceOrder.setAppUserId(userId); @@ -69,15 +93,86 @@ insuranceOrder.setIdCard(dto.getIdCard()); insuranceOrder.setImg(dto.getImg()); insuranceOrder.setPayType(dto.getType()); TInsurance insurance = tInsuranceService.getOne(new LambdaQueryWrapper<TInsurance>().eq(TInsurance::getStatus, 1)); if(insurance==null){ return R.fail("保险已关闭"); } insuranceOrder.setInsuranceId(insurance.getId()); insuranceOrder.setPayMoney(BigDecimal.valueOf(insurance.getInsuranceMoney()).setScale(2, RoundingMode.HALF_UP)); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = simpleDateFormat.parse(insurance.getValidityTime()); insuranceOrder.setEndTime(date); String payNo = OrderNos.getDid(); insuranceOrder.setPayNo(payNo); boolean save = tInsuranceOrderService.save(insuranceOrder); if(save){ return R.ok("购买成功"); } return R.fail("购买失败"); Map<String, Object> result = wxV3Pay.jsApiInsurance(payNo, insuranceOrder.getPayMoney().multiply(BigDecimal.valueOf(100)).longValue(), appUser.getOpenId(),"购买保险",""); return R.ok(result); } @ApiOperation(value = "保险模块--缴费记录") @PostMapping("/getInsuranceOrderList") public R<?> getInsuranceOrderList(@RequestBody BasePage query) { String userId = tokenService.getLoginUserApplet().getUserId(); Page<TInsuranceOrder> page = tInsuranceOrderService.page(new Page<>(query.getPageNum(), query.getPageSize()), Wrappers.<TInsuranceOrder>lambdaQuery().eq(TInsuranceOrder::getAppUserId, userId).ne(TInsuranceOrder::getStatus,0).orderByDesc(BaseModel::getCreateTime)); return R.ok(page); } @PostMapping("/refundOrder") @ApiOperation(value = "保险模块--退款") public R<?> refundOrder(@RequestBody TInsuranceOrder order) { String userId = tokenService.getLoginUserApplet().getUserId(); TInsuranceOrder order1 = tInsuranceOrderService.getById(order.getId()); order1.setRefundReason(order.getRefundReason()); order1.setRefundReasonPictures(order.getRefundReasonPictures()); order1.setRequestRefundTime(new Date()); order1.setRefundMoney(order1.getPayMoney().doubleValue()); if(!userId.equals(order1.getAppUserId())){ return R.fail("无权限操作"); } if(order1.getStatus()!=2){ return R.fail("状态不允许退款"); } order1.setStatus(4); LocalDateTime payTime = order1.getPayTime(); if(payTime==null){ return R.fail("状态不允许退款"); } if(payTime.plusDays(2).isBefore(LocalDateTime.now())){ order1.setBankCardNumber(order.getBankCardNumber()); order1.setCardholderName(order.getCardholderName()); order1.setRefundStatus(1); tInsuranceOrderService.updateById(order1); }else { String outRefundNo = OrderNos.getDid(); order1.setRefundNumber(outRefundNo); tInsuranceOrderService.updateById(order1); // tInsuranceOrderService refundModel = new WxPaymentRefundModel(); //// refundModel.setSub_mchid(weixinPayProperties.getMchId()); //// refundModel.setSp_appid(weixinPayProperties.getAppId()); //// refundModel.setTransaction_id(courseOrder1.getTransactionId()); // refundModel.setOut_trade_no(courseOrder1.getOrderNumber()); // refundModel.setOut_refund_no(OrderNos.getDid()); // refundModel.setReason(courseOrder1.getRefundReason()); // refundModel.setGoods_detail(new ArrayList<>()); // refundModel.setAmount(new WxPaymentRefundModel.RefundAmount(courseOrder1.getPayMoney().multiply(BigDecimal.valueOf(100)).intValue(), "CNY", courseOrder1.getPayMoney().multiply(BigDecimal.valueOf(100)).intValue())); // Map<String, Object> refund = wxV3Pay.refund(refundModel); // System.out.println(refund); String result = wxRefundUtil.refund(order1.getPayMoney().multiply(BigDecimal.valueOf(100)).intValue(), order1.getPayNo(),outRefundNo); if(result!=null){ return R.fail(result); } } return R.ok(); } @Autowired private WxRefundUtil wxRefundUtil; } ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/WxPayController.java
@@ -7,7 +7,10 @@ import com.ruoyi.common.utils.CodeGenerateUtils; import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.system.model.TCourseOrder; import com.ruoyi.system.model.TInsurance; import com.ruoyi.system.model.TInsuranceOrder; import com.ruoyi.system.service.TCourseOrderService; import com.ruoyi.system.service.TInsuranceOrderService; import com.ruoyi.system.wxPay.enums.RefundEnum; import com.ruoyi.system.wxPay.enums.TradeStateEnum; import com.ruoyi.system.wxPay.model.WeixinPayProperties; @@ -28,6 +31,8 @@ import java.io.PrintWriter; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.Date; import java.util.Map; import java.util.Objects; @@ -49,6 +54,9 @@ @Autowired private TCourseOrderService courseOrderService; @Autowired private TInsuranceOrderService tInsuranceOrderService; @@ -90,6 +98,55 @@ order.setPayTime(WxTimeUtils.dateToLocalDateTime(WxTimeUtils.toRfc3339Date(success_time))); order.setTransactionId(transaction_id); courseOrderService.updateById(order); PrintWriter out = response.getWriter(); out.print("SUCCESS"); out.flush(); out.close(); } } catch (Exception e) { log.error("支付回调异常:{}", e, e); wxV3Pay.ack(false, e.getMessage()); } } /** * 支付回调 */ @PostMapping("pay/notifyInsurance") @ApiOperation("保险订单回调") public void notifyInsurance(HttpServletRequest request, HttpServletResponse response) throws Exception { try { System.err.println("微信回调"); BufferedReader reader = request.getReader(); StringBuilder requestBody = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { requestBody.append(line); } System.err.println("全部请求体" + requestBody); JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); JSONObject resource = jsonObject.getJSONObject("resource"); AesUtil aesUtil = new AesUtil(weixinPayProperties.getV3().getApiKey().getBytes(StandardCharsets.UTF_8)); String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), resource.getString("ciphertext")); System.err.println("微信解密的字符串信息" + decryptedData); JSONObject jsonInfo = JSONObject.parse(decryptedData); String out_trade_no = jsonInfo.getString("out_trade_no"); String transaction_id = jsonInfo.getString("transaction_id"); String trade_state = jsonInfo.getString("trade_state"); String success_time = jsonInfo.getString("success_time"); // 查询订单 TInsuranceOrder order = tInsuranceOrderService.getOne(Wrappers.lambdaQuery(TInsuranceOrder.class) .eq(TInsuranceOrder::getPayNo, out_trade_no).last("LIMIT 1")); if (trade_state.equals(TradeStateEnum.SUCCESS.name())) { log.info("回调成功"); // 订单号查询订单 order.setStatus(2); order.setPayTime(LocalDateTime.now()); order.setTransactionId(transaction_id); tInsuranceOrderService.updateById(order); PrintWriter out = response.getWriter(); out.print("SUCCESS"); @@ -168,4 +225,50 @@ wxV3Pay.ack(false, "不是成功的退款状态"); } } @PostMapping("refund/notifyInsurance") public void notifyInsurance(HttpServletRequest request) throws IOException { try { System.err.println("微信退款回调"); BufferedReader reader = request.getReader(); StringBuilder requestBody = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { requestBody.append(line); } System.err.println("全部请求体" + requestBody); JSONObject jsonObject = JSONObject.parseObject(requestBody.toString()); JSONObject resource = jsonObject.getJSONObject("resource"); AesUtil aesUtil = new AesUtil(weixinPayProperties.getV3().getApiKey().getBytes(StandardCharsets.UTF_8)); String decryptedData = aesUtil.decryptToString(resource.getString("associated_data").getBytes(StandardCharsets.UTF_8), resource.getString("nonce").getBytes(StandardCharsets.UTF_8), resource.getString("ciphertext")); System.err.println("微信解密的字符串信息" + decryptedData); JSONObject params = JSONObject.parseObject(decryptedData); // 商户订单号 String out_trade_no = params.get("out_trade_no").toString(); // 商户退款单号 String out_refund_no = params.get("out_refund_no").toString(); // 退款状态 String tradeState = params.get("refund_status").toString(); // 微信支付订单号 String transaction_id = params.get("transaction_id").toString(); if (tradeState.equals(RefundEnum.SUCCESS.name())) { TInsuranceOrder order = tInsuranceOrderService.getOne(Wrappers.lambdaQuery(TInsuranceOrder.class) .eq(TInsuranceOrder::getRefundNumber, out_refund_no).last("LIMIT 1")); order.setStatus(1); order.setRefundStatus(2); order.setRefundTransactionId(transaction_id); tInsuranceOrderService.updateById(order); wxV3Pay.ack(); } }catch (Exception e){ wxV3Pay.ack(false, "不是成功的退款状态"); } } } ruoyi-applet/src/main/resources/application-prod.yml
@@ -226,5 +226,9 @@ mchSerialNo: 5ED4C99FE5657DA2579BE5E7AE4255C9468627D9 # 支付成功回调地址 notifyPayUrl: https://rhljsq.pzhsoar.com/applet/wx/pay/notify # 保险支付成功回调地址 insuranceNotifyPayUrl: https://rhljsq.pzhsoar.com/applet/wx/pay/notifyInsurance # 支付退款回调地址 notifyRefundUrl: https://rhljsq.pzhsoar.com/applet/wx/refund/notify # 保险支付退款回调地址 notifyInsuranceRefundUrl: https://rhljsq.pzhsoar.com/applet/wx/refund/notifyInsurance ruoyi-system/src/main/java/com/ruoyi/system/model/TInsuranceOrder.java
@@ -15,6 +15,7 @@ import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.Date; /** @@ -81,7 +82,7 @@ @TableField("pay_time") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @ApiModelProperty(value = "支付时间") private Date payTime; private LocalDateTime payTime; @TableField("end_time") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") @ApiModelProperty(value = "截至时间") ruoyi-system/src/main/java/com/ruoyi/system/vo/UserInsuranceVo.java
@@ -5,6 +5,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.time.LocalDateTime; import java.util.Date; @Data @@ -16,7 +17,7 @@ @ApiModelProperty("保险支付时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") private Date payTime; private LocalDateTime payTime; @ApiModelProperty("保险到期时间") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") ruoyi-system/src/main/java/com/ruoyi/system/wxPay/model/V3.java
@@ -48,11 +48,24 @@ private String notifyPayUrl; /** * 支付回调地址 保险 * * @return */ private String insuranceNotifyPayUrl; /** * 退款回调地址 * * @return */ private String notifyRefundUrl; /** * 退款回调地址 * * @return */ private String notifyInsuranceRefundUrl; /** * 退款回调地址 ruoyi-system/src/main/java/com/ruoyi/system/wxPay/utils/WxRefundUtil.java
@@ -80,6 +80,41 @@ // TODO: 请求失败,根据状态码执行不同的逻辑 } } public String refundInsurance(int money,String outTradeNo,String outRefundNo) { // TODO: 请准备商户开发必要参数,参考:https://pay.weixin.qq.com/doc/v3/merchant/4013070756 WxRefundUtil client = new WxRefundUtil( this. wxV3Pay.getConfig().getMchId(), // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 this. wxV3Pay.getConfig().getV3().getMchSerialNo(), // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 this. wxV3Pay.getConfig().getV3().getPrivateKeyPath() , // 商户API证书私钥文件路径,本地文件路径 this. wxV3Pay.getConfig().getV3().getPublicKeyId() , // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 this. wxV3Pay.getConfig().getV3().getPublicKeyPath() // 微信支付公钥文件路径,本地文件路径 ); CreateRequest request = new CreateRequest(); request.outTradeNo = outTradeNo; request.reason = "退款"; request.fundsAccount = ReqFundsAccount.AVAILABLE; request.notifyUrl =this.wxV3Pay.getConfig().getV3().getNotifyRefundUrl(); request.amount = new AmountReq(); request.amount.refund = money; request.outRefundNo =outRefundNo; request.amount.total = money; request.amount.currency = "CNY"; request.goodsDetail = new ArrayList<>(); try { Refund response = client.run(request); // TODO: 请求成功,继续业务逻辑 System.out.println(response); return null; } catch (WXPayUtility.ApiException e) { String body = e.getBody(); JSONObject jsonObject = JSONObject.parseObject(body); String result = jsonObject.get("message").toString(); return result; // TODO: 请求失败,根据状态码执行不同的逻辑 } } public Refund run(CreateRequest request) { String uri = PATH; ruoyi-system/src/main/java/com/ruoyi/system/wxPay/utils/WxV3Pay.java
@@ -138,6 +138,36 @@ throw new RuntimeException("获取支付数据错误!"); } } public Map<String, Object> jsApiInsurance(String tradeNo, Long amount, String openid, String description,String attach) { JsapiPrepay client = new JsapiPrepay( this.config.getMchId(), // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756 this.config.getV3().getMchSerialNo(), // 商户API证书序列号,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053 this.config.getV3().getPrivateKeyPath(), // 商户API证书私钥文件路径,本地文件路径 this.config.getV3().getPublicKeyId(), // 微信支付公钥ID,如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816 this.config.getV3().getPublicKeyPath() // 微信支付公钥文件路径,本地文件路径 ); JsapiPrepay.DirectAPIv3JsapiPrepayRequest request = new JsapiPrepay.DirectAPIv3JsapiPrepayRequest(); request.appid = this.config.getAppId(); request.mchid = this.config.getMchId(); request.description = description; request.outTradeNo = tradeNo; // request.timeExpire = "2018-06-08T10:34:56+08:00"; request.attach = attach; request.notifyUrl = this.config.getV3().getInsuranceNotifyPayUrl(); request.goodsTag = "WXG"; request.supportFapiao = false; request.amount = new JsapiPrepay.CommonAmountInfo(); request.amount.total = amount; request.amount.currency = "CNY"; request.payer = new JsapiPrepay.JsapiReqPayerInfo(); request.payer.openid = openid; try { JsapiPrepay.DirectAPIv3JsapiPrepayResponse response = client.run(request); return wxTuneUp(this.privateKeySigner, this.config.getAppId(), response.prepayId); } catch (WXPayUtility.ApiException e) { throw new RuntimeException("获取支付数据错误!"); } } // /** // * jsApi下单 // *