lin
2024-04-08 18118188e370e45f6e1bd43e5b33fd4c4a156655
微信支付+获取用户的openid
12 files modified
33 files added
3281 ■■■■■ changed files
src/main/java/org/springblade/common/config/WxMiniConfig.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/common/config/WxPayConfig.java 39 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/common/enums/wxpay/WxNotifyType.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/controller/OrderInfoController.java 135 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/controller/PaymentInfoController.java 125 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/controller/RefundInfoController.java 125 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/controller/WxPayController.java 139 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/dto/OrderInfoDTO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/dto/PaymentInfoDTO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/dto/RefundInfoDTO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/entity/OrderInfoEntity.java 100 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/entity/PaymentInfoEntity.java 95 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/entity/RefundInfoEntity.java 102 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/entity/WeChatMiniAuthorizeVo.java 39 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/mapper/OrderInfoMapper.java 44 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/mapper/OrderInfoMapper.xml 15 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/mapper/PaymentInfoMapper.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/mapper/PaymentInfoMapper.xml 64 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/mapper/RefundInfoMapper.java 60 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/mapper/RefundInfoMapper.xml 71 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/IOrderInfoService.java 59 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/IPaymentInfoService.java 45 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/IRefundInfoService.java 46 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/IWxPayService.java 31 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/impl/OrderInfoServiceImpl.java 207 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/impl/PaymentInfoServiceImpl.java 88 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/impl/RefundInfoServiceImpl.java 118 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/service/impl/WxPayServiceImpl.java 732 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/vo/OrderInfoVO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/vo/PaymentInfoVO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/vo/RefundInfoVO.java 34 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/wrapper/OrderInfoWrapper.java 51 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/wrapper/PaymentInfoWrapper.java 51 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/pay/wrapper/RefundInfoWrapper.java 51 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/property/dto/PropertyChargeRecordDTO.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/property/entity/PropertyChargeRecord.java 108 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/property/mapper/PropertyChargeRecordMapper.java 18 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/property/mapper/PropertyChargeRecordMapper.xml 79 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/property/service/IPropertyChargeRecordService.java 3 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/property/service/impl/PropertyChargeRecordServiceImpl.java 3 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/system/entity/User.java 4 ●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/wechat/controller/WechatController.java 33 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/wechat/service/WechatService.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/modules/wechat/service/impl/WechatServiceImpl.java 59 ●●●●● patch | view | raw | blame | history
src/main/resources/application.yml 15 ●●●●● patch | view | raw | blame | history
src/main/java/org/springblade/common/config/WxMiniConfig.java
New file
@@ -0,0 +1,21 @@
package org.springblade.common.config;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "wxmini") //读取wxmini节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxMiniConfig {
    // appid
    private String appid;
    // 小程序appSecret key
    private String secret;
    private String jscode2sessionUrl;
}
src/main/java/org/springblade/common/config/WxPayConfig.java
@@ -18,9 +18,9 @@
import java.security.PrivateKey;
//@Configuration
@Configuration
// @PropertySource("classpath:wxpay.properties") //读取配置文件
//@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@ConfigurationProperties(prefix = "wxpay") //读取wxpay节点
@Data //使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {
@@ -111,8 +111,8 @@
     *
     * @return
     */
//    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier(String privateKeyPath, String mchSerialNo, String mchId, String apiV3Key) {
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {
        log.info("获取签名验证器");
@@ -140,8 +140,8 @@
     * @param verifier
     * @return
     */
//    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(String privateKeyPath, String mchSerialNo, String mchId, String apiV3Key) {
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {
        log.info("获取httpClient");
@@ -150,7 +150,7 @@
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
            .withMerchant(mchId, mchSerialNo, privateKey)
            .withValidator(new WechatPay2Validator(getVerifier(privateKeyPath, mchSerialNo, mchId, apiV3Key)));
            .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
@@ -159,5 +159,30 @@
        return httpClient;
    }
    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient() {
        //获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);
        //用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
            //设置商户信息
            .withMerchant(mchId, mchSerialNo, privateKey)
            //无需进行签名验证、通过withValidator((response) -> true)实现
            .withValidator((response) -> true);
        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();
        log.info("== getWxPayNoSignClient END ==");
        return httpClient;
    }
}
src/main/java/org/springblade/common/enums/wxpay/WxNotifyType.java
@@ -21,7 +21,18 @@
    /**
     * 退款结果通知
     */
    REFUND_NOTIFY("/wxPay/refunds/notify");
    REFUND_NOTIFY("/wxPay/refunds/notify"),
    /**
     * 添加分账接收方API
     */
    RECEIVERS_ADD("/v3/profitsharing/receivers/add"),
    /**
     * 创建分账订单API
     *
     */
    PROFITSHARING_ORDERS("/v3/profitsharing/orders");
    /**
     * 类型
src/main/java/org/springblade/modules/pay/controller/OrderInfoController.java
New file
@@ -0,0 +1,135 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import lombok.AllArgsConstructor;
import javax.validation.Valid;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.modules.pay.vo.OrderInfoVO;
import org.springblade.modules.pay.wrapper.OrderInfoWrapper;
import org.springblade.modules.pay.service.IOrderInfoService;
import org.springblade.core.boot.ctrl.BladeController;
/**
 * 订单信息 控制器
 *
 * @author BladeX
 * @since 2024-04-07
 */
@RestController
@AllArgsConstructor
@RequestMapping("blade-orderInfo/orderInfo")
@Api(value = "订单信息", tags = "订单信息接口")
public class OrderInfoController extends BladeController {
    private final IOrderInfoService orderInfoService;
    /**
     * 订单信息 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入orderInfo")
    public R<OrderInfoVO> detail(OrderInfoEntity orderInfo) {
        OrderInfoEntity detail = orderInfoService.getOne(Condition.getQueryWrapper(orderInfo));
        return R.data(OrderInfoWrapper.build().entityVO(detail));
    }
    /**
     * 订单信息 分页
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入orderInfo")
    public R<IPage<OrderInfoVO>> list(OrderInfoEntity orderInfo, Query query) {
        IPage<OrderInfoEntity> pages = orderInfoService.page(Condition.getPage(query), Condition.getQueryWrapper(orderInfo));
        return R.data(OrderInfoWrapper.build().pageVO(pages));
    }
    /**
     * 订单信息 自定义分页
     */
    @GetMapping("/page")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入orderInfo")
    public R<IPage<OrderInfoVO>> page(OrderInfoVO orderInfo, Query query) {
        IPage<OrderInfoVO> pages = orderInfoService.selectOrderInfoPage(Condition.getPage(query), orderInfo);
        return R.data(pages);
    }
    /**
     * 订单信息 新增
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入orderInfo")
    public R save(@Valid @RequestBody OrderInfoEntity orderInfo) {
        return R.status(orderInfoService.save(orderInfo));
    }
    /**
     * 创建订单信息
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入orderInfo")
    public R save(@RequestParam Long propertyChargeRecordId) {
        return R.data(orderInfoService.createOrderByPropertyChargeRecordId(propertyChargeRecordId));
    }
    /**
     * 订单信息 修改
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入orderInfo")
    public R update(@Valid @RequestBody OrderInfoEntity orderInfo) {
        return R.status(orderInfoService.updateById(orderInfo));
    }
    /**
     * 订单信息 新增或修改
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入orderInfo")
    public R submit(@Valid @RequestBody OrderInfoEntity orderInfo) {
        return R.status(orderInfoService.saveOrUpdate(orderInfo));
    }
    /**
     * 订单信息 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(orderInfoService.removeBatchByIds(Func.toLongList(ids)));
    }
}
src/main/java/org/springblade/modules/pay/controller/PaymentInfoController.java
New file
@@ -0,0 +1,125 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import lombok.AllArgsConstructor;
import javax.validation.Valid;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.pay.entity.PaymentInfoEntity;
import org.springblade.modules.pay.vo.PaymentInfoVO;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.modules.pay.wrapper.PaymentInfoWrapper;
import org.springblade.modules.pay.service.IPaymentInfoService;
import org.springblade.core.boot.ctrl.BladeController;
/**
 * 支付信息 控制器
 *
 * @author BladeX
 * @since 2024-04-07
 */
@RestController
@AllArgsConstructor
@RequestMapping("blade-paymentInfo/paymentInfo")
@Api(value = "支付信息", tags = "支付信息接口")
public class PaymentInfoController extends BladeController {
    private final IPaymentInfoService paymentInfoService;
    /**
     * 支付信息 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入paymentInfo")
    public R<PaymentInfoVO> detail(PaymentInfoEntity paymentInfo) {
        PaymentInfoEntity detail = paymentInfoService.getOne(Condition.getQueryWrapper(paymentInfo));
        return R.data(PaymentInfoWrapper.build().entityVO(detail));
    }
    /**
     * 支付信息 分页
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入paymentInfo")
    public R<IPage<PaymentInfoVO>> list(PaymentInfoEntity paymentInfo, Query query) {
        IPage<PaymentInfoEntity> pages = paymentInfoService.page(Condition.getPage(query), Condition.getQueryWrapper(paymentInfo));
        return R.data(PaymentInfoWrapper.build().pageVO(pages));
    }
    /**
     * 支付信息 自定义分页
     */
    @GetMapping("/page")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入paymentInfo")
    public R<IPage<PaymentInfoVO>> page(PaymentInfoVO paymentInfo, Query query) {
        IPage<PaymentInfoVO> pages = paymentInfoService.selectPaymentInfoPage(Condition.getPage(query), paymentInfo);
        return R.data(pages);
    }
    /**
     * 支付信息 新增
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入paymentInfo")
    public R save(@Valid @RequestBody PaymentInfoEntity paymentInfo) {
        return R.status(paymentInfoService.save(paymentInfo));
    }
    /**
     * 支付信息 修改
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入paymentInfo")
    public R update(@Valid @RequestBody PaymentInfoEntity paymentInfo) {
        return R.status(paymentInfoService.updateById(paymentInfo));
    }
    /**
     * 支付信息 新增或修改
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入paymentInfo")
    public R submit(@Valid @RequestBody PaymentInfoEntity paymentInfo) {
        return R.status(paymentInfoService.saveOrUpdate(paymentInfo));
    }
    /**
     * 支付信息 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(paymentInfoService.removeBatchByIds(Func.toLongList(ids)));
    }
}
src/main/java/org/springblade/modules/pay/controller/RefundInfoController.java
New file
@@ -0,0 +1,125 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import lombok.AllArgsConstructor;
import javax.validation.Valid;
import org.springblade.core.mp.support.Condition;
import org.springblade.core.mp.support.Query;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import org.springblade.modules.pay.service.IRefundInfoService;
import org.springblade.modules.pay.vo.RefundInfoVO;
import org.springblade.modules.pay.wrapper.RefundInfoWrapper;
import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springblade.core.boot.ctrl.BladeController;
/**
 * 退款信息 控制器
 *
 * @author BladeX
 * @since 2024-04-07
 */
@RestController
@AllArgsConstructor
@RequestMapping("blade-refundInfo/refundInfo")
@Api(value = "退款信息", tags = "退款信息接口")
public class RefundInfoController extends BladeController {
    private final IRefundInfoService refundInfoService;
    /**
     * 退款信息 详情
     */
    @GetMapping("/detail")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "详情", notes = "传入refundInfo")
    public R<RefundInfoVO> detail(RefundInfoEntity refundInfo) {
        RefundInfoEntity detail = refundInfoService.getOne(Condition.getQueryWrapper(refundInfo));
        return R.data(RefundInfoWrapper.build().entityVO(detail));
    }
    /**
     * 退款信息 分页
     */
    @GetMapping("/list")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "分页", notes = "传入refundInfo")
    public R<IPage<RefundInfoVO>> list(RefundInfoEntity refundInfo, Query query) {
        IPage<RefundInfoEntity> pages = refundInfoService.page(Condition.getPage(query), Condition.getQueryWrapper(refundInfo));
        return R.data(RefundInfoWrapper.build().pageVO(pages));
    }
    /**
     * 退款信息 自定义分页
     */
    @GetMapping("/page")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "分页", notes = "传入refundInfo")
    public R<IPage<RefundInfoVO>> page(RefundInfoVO refundInfo, Query query) {
        IPage<RefundInfoVO> pages = refundInfoService.selectRefundInfoPage(Condition.getPage(query), refundInfo);
        return R.data(pages);
    }
    /**
     * 退款信息 新增
     */
    @PostMapping("/save")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "新增", notes = "传入refundInfo")
    public R save(@Valid @RequestBody RefundInfoEntity refundInfo) {
        return R.status(refundInfoService.save(refundInfo));
    }
    /**
     * 退款信息 修改
     */
    @PostMapping("/update")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "修改", notes = "传入refundInfo")
    public R update(@Valid @RequestBody RefundInfoEntity refundInfo) {
        return R.status(refundInfoService.updateById(refundInfo));
    }
    /**
     * 退款信息 新增或修改
     */
    @PostMapping("/submit")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "新增或修改", notes = "传入refundInfo")
    public R submit(@Valid @RequestBody RefundInfoEntity refundInfo) {
        return R.status(refundInfoService.saveOrUpdate(refundInfo));
    }
    /**
     * 退款信息 删除
     */
    @PostMapping("/remove")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "逻辑删除", notes = "传入ids")
    public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
        return R.status(refundInfoService.removeBatchByIds(Func.toLongList(ids)));
    }
}
src/main/java/org/springblade/modules/pay/controller/WxPayController.java
@@ -1,35 +1,32 @@
package org.springblade.modules.pay.controller;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.common.config.WxPayConfig;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.utils.HttpUtils;
import org.springblade.common.utils.WechatPay2ValidatorForRequest;
import org.springblade.core.boot.ctrl.BladeController;
import org.springblade.core.tool.api.R;
import org.springblade.modules.pay.entity.WxPayInfo;
import org.springblade.modules.pay.service.IWxPayService;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
@CrossOrigin //跨域
@RestController
@AllArgsConstructor
@RequestMapping("/wxpay")
@Api(value = "微信支付接口", tags = "微信支付接口")
public class WxPayController extends BladeController {
@RequestMapping("/wxPay")
@Api(tags = "网站微信支付APIv3")
@Slf4j
public class WxPayController {
    private static final Logger logger = LoggerFactory.getLogger(WxPayController.class);
    @Resource
    private IWxPayService wxPayService;
    @PostMapping("/save")
    public R save(@RequestBody WxPayInfo wxPayInfo) {
@@ -57,21 +54,22 @@
        return R.data(wxPayService.getOpenId(code));
    }
    /**
     * jsapi下单
     *
     * @param productId
     * @param properChargeRecordId
     * @return
     * @throws Exception
     */
    @ApiOperation("调用jsapi统一下单API")
    @GetMapping("/jsapiPay")
    public R jsapiPay(@RequestParam("productId") Long productId) throws Exception {
    public R jsapiPay(@RequestParam("properChargeRecordId") Long properChargeRecordId) throws Exception {
        logger.info("发起支付请求 v3");
        log.info("发起支付请求 v3");
        //返回支付二维码连接和订单号
        Map<String, String> map = wxPayService.jsapiPay(productId);
        Map<String, String> map = wxPayService.jsapiPay(properChargeRecordId);
        return R.data(map);
    }
@@ -83,55 +81,82 @@
    @ApiOperation("支付通知")
    @PostMapping("/native/notify")
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
        return wxPayService.nativeNotify(request, response);
    }
        Gson gson = new Gson();
        Map<String, String> map = new HashMap<>();//应答对象
    /**
     * 用户取消订单
     *
     * @param orderNo
     * @return
     * @throws Exception
     */
    @ApiOperation("用户取消订单")
    @PostMapping("/cancel/{orderNo}")
    public R cancel(@PathVariable String orderNo) throws Exception {
        try {
        log.info("取消订单");
            //处理通知参数
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            logger.info("支付通知的id ===> {}", requestId);
            logger.info("支付通知的完整数据 ===> {}", body);
            //int a = 9 / 0;
            ScheduledUpdateCertificatesVerifier verifier = new WxPayConfig().getVerifier("111", "111", "111", "111");
            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                = new WechatPay2ValidatorForRequest(verifier, requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                logger.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return gson.toJson(map);
            }
            logger.info("通知验签成功");
        wxPayService.cancelOrder(orderNo);
        return R.data("订单已取消");
    }
            //处理订单
            wxPayService.processOrder(bodyMap);
    /**
     * 查询订单
     *
     * @param orderNo
     * @return
     * @throws Exception
     */
    @ApiOperation("查询订单")
    @GetMapping("/query/{orderNo}")
    public R queryOrder(@PathVariable String orderNo) throws Exception {
            //应答超时
            //模拟接收微信端的重复通知
            // TimeUnit.SECONDS.sleep(5);
        log.info("查询订单");
            //成功应答
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        String result = wxPayService.queryOrder(orderNo);
        return R.data("result", result);
        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }
    }
    @ApiOperation("申请退款")
    @PostMapping("/refunds/{orderNo}/{reason}")
    public R refunds(@PathVariable String orderNo, @PathVariable String reason) throws Exception {
        log.info("申请退款");
        wxPayService.refund(orderNo, reason);
        return R.success("退款申请成功");
    }
    /**
     * 查询退款
     *
     * @param refundNo
     * @return
     * @throws Exception
     */
    @ApiOperation("查询退款")
    @GetMapping("/query-refund/{refundNo}")
    public R queryRefund(@PathVariable String refundNo) throws Exception {
        log.info("查询退款");
        String result = wxPayService.queryRefund(refundNo);
        return R.data("result", result);
    }
    /**
     * 退款结果通知
     * 退款状态改变后,微信会把相关退款结果发送给商户。
     */
    @ApiOperation("退款结果通知")
    @PostMapping("/refunds/notify")
    public String refundsNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("退款通知执行");
        return wxPayService.refundsNotify(request, response);
    }
src/main/java/org/springblade/modules/pay/dto/OrderInfoDTO.java
New file
@@ -0,0 +1,34 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.dto;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 订单信息 数据传输对象实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class OrderInfoDTO extends OrderInfoEntity {
    private static final long serialVersionUID = 1L;
}
src/main/java/org/springblade/modules/pay/dto/PaymentInfoDTO.java
New file
@@ -0,0 +1,34 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.dto;
import org.springblade.modules.pay.entity.PaymentInfoEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 支付信息 数据传输对象实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class PaymentInfoDTO extends PaymentInfoEntity {
    private static final long serialVersionUID = 1L;
}
src/main/java/org/springblade/modules/pay/dto/RefundInfoDTO.java
New file
@@ -0,0 +1,34 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.dto;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 退款信息 数据传输对象实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class RefundInfoDTO extends RefundInfoEntity {
    private static final long serialVersionUID = 1L;
}
src/main/java/org/springblade/modules/pay/entity/OrderInfoEntity.java
New file
@@ -0,0 +1,100 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.util.Date;
/**
 * 订单信息 实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@TableName("jczz_order_info")
@ApiModel(value = "OrderInfo对象", description = "订单信息")
public class OrderInfoEntity {
    /** id */
    @ApiModelProperty(value = "主键ID", example = "")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    /** 订单标题 */
    @ApiModelProperty(value = "订单标题", example = "")
    @TableField("title")
    private String title;
    /** 商户订单编号 */
    @ApiModelProperty(value = "商户订单编号", example = "")
    @TableField("order_no")
    private String orderNo;
    /** 用户id */
    @ApiModelProperty(value = "用户id", example = "")
    @TableField("user_id")
    private Long userId;
    /** 支付产品id */
    @ApiModelProperty(value = "支付产品id", example = "")
    @TableField("proper_charge_record_id")
    private Long properChargeRecordId;
    /** 订单二维码连接 */
    @ApiModelProperty(value = "订单二维码连接", example = "")
    @TableField("code_url")
    private String codeUrl;
    /** 订单金额(分) */
    @ApiModelProperty(value = "订单金额(分)", example = "")
    @TableField("total_fee")
    private Integer totalFee;
    /** 订单状态 */
    @ApiModelProperty(value = "订单状态", example = "")
    @TableField("order_status")
    private String orderStatus;
    /** 创建时间 */
    @ApiModelProperty(value = "创建时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("create_time")
    private Date createTime;
    /** 更新时间 */
    @ApiModelProperty(value = "更新时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("update_time")
    private Date updateTime;
    /** 物业id */
    @ApiModelProperty(value = "物业id", example = "")
    @TableId("proper_id")
    private Long properId;
}
src/main/java/org/springblade/modules/pay/entity/PaymentInfoEntity.java
New file
@@ -0,0 +1,95 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.util.Date;
/**
 * 支付信息 实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@TableName("jczz_payment_info")
@ApiModel(value = "PaymentInfo对象", description = "支付信息")
public class PaymentInfoEntity {
    /** id */
    @ApiModelProperty(value = "主键ID", example = "")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    /** 订单编号 */
    @ApiModelProperty(value = "订单编号", example = "")
    @TableField("order_no")
    private String orderNo;
    /** 支付系统交易编号 */
    @ApiModelProperty(value = "支付系统交易编号", example = "")
    @TableField("transaction_id")
    private String transactionId;
    /** 支付类型 */
    @ApiModelProperty(value = "支付类型", example = "")
    @TableField("payment_type")
    private String paymentType;
    /** 交易类型 */
    @ApiModelProperty(value = "交易类型", example = "")
    @TableField("trade_type")
    private String tradeType;
    /** 交易状态 */
    @ApiModelProperty(value = "交易状态", example = "")
    @TableField("trade_state")
    private String tradeState;
    /** 支付金额(分) */
    @ApiModelProperty(value = "支付金额(分)", example = "")
    @TableField("payer_total")
    private Integer payerTotal;
    /** 通知参数 */
    @ApiModelProperty(value = "通知参数", example = "")
    @TableField("content")
    private String content;
    /** 创建时间 */
    @ApiModelProperty(value = "创建时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("create_time")
    private Date createTime;
    /** 更新时间 */
    @ApiModelProperty(value = "更新时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("update_time")
    private Date updateTime;
}
src/main/java/org/springblade/modules/pay/entity/RefundInfoEntity.java
New file
@@ -0,0 +1,102 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import org.springblade.core.tenant.mp.TenantEntity;
import java.util.Date;
/**
 * 退款信息 实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@TableName("jczz_refund_info")
@ApiModel(value = "RefundInfo对象", description = "退款信息")
public class RefundInfoEntity  {
    /** id */
    @ApiModelProperty(value = "主键ID", example = "")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    /** 订单编号 */
    @ApiModelProperty(value = "订单编号", example = "")
    @TableField("order_no")
    private String orderNo;
    /** 退款单编号 */
    @ApiModelProperty(value = "退款单编号", example = "")
    @TableField("refund_no")
    private String refundNo;
    /** 支付系统退款单号 */
    @ApiModelProperty(value = "支付系统退款单号", example = "")
    @TableField("refund_id")
    private String refundId;
    /** 原订单金额(分) */
    @ApiModelProperty(value = "原订单金额(分)", example = "")
    @TableField("total_fee")
    private Integer totalFee;
    /** 退款金额(分) */
    @ApiModelProperty(value = "退款金额(分)", example = "")
    @TableField("refund")
    private Integer refund;
    /** 退款原因 */
    @ApiModelProperty(value = "退款原因", example = "")
    @TableField("reason")
    private String reason;
    /** 退款单状态 */
    @ApiModelProperty(value = "退款单状态", example = "")
    @TableField("refund_status")
    private String refundStatus;
    /** 申请退款返回参数 */
    @ApiModelProperty(value = "申请退款返回参数", example = "")
    @TableField("content_return")
    private String contentReturn;
    /** 退款结果通知参数 */
    @ApiModelProperty(value = "退款结果通知参数", example = "")
    @TableField("content_notify")
    private String contentNotify;
    /** 创建时间 */
    @ApiModelProperty(value = "创建时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField(value = "create_time",fill = FieldFill.INSERT)
    private Date createTime;
    /** 更新时间 */
    @ApiModelProperty(value = "更新时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField(value = "update_time",fill = FieldFill.UPDATE)
    private Date updateTime;
}
src/main/java/org/springblade/modules/pay/entity/WeChatMiniAuthorizeVo.java
New file
@@ -0,0 +1,39 @@
package org.springblade.modules.pay.entity;
import com.alibaba.fastjson.annotation.JSONField;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value="WeChatMiniAuthorizeVo对象", description="微信小程序用户授权返回数据")
public class WeChatMiniAuthorizeVo implements Serializable {
    private static final long serialVersionUID=1L;
    @ApiModelProperty(value = "会话密钥")
    @JSONField(name = "session_key")
    private String sessionKey;
    @ApiModelProperty(value = "用户唯一标识")
    @JSONField(name = "openid")
    private String openId;
    @ApiModelProperty(value = "用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回")
    @JSONField(name = "unionid")
    private String unionId;
    @ApiModelProperty(value = "错误码")
    @JSONField(name = "errcode")
    private String errCode;
    @ApiModelProperty(value = "错误信息")
    @JSONField(name = "errmsg")
    private String errMsg;
}
src/main/java/org/springblade/modules/pay/mapper/OrderInfoMapper.java
New file
@@ -0,0 +1,44 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.mapper;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import org.springblade.modules.pay.vo.OrderInfoVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
 * 订单信息 Mapper 接口
 *
 * @author BladeX
 * @since 2024-04-07
 */
public interface OrderInfoMapper extends BaseMapper<OrderInfoEntity> {
    /**
     * 自定义分页
     *
     * @param page
     * @param orderInfo
     * @return
     */
    List<OrderInfoVO> selectOrderInfoPage(IPage page, OrderInfoVO orderInfo);
}
src/main/java/org/springblade/modules/pay/mapper/OrderInfoMapper.xml
New file
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.modules.pay.mapper.OrderInfoMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="orderInfoResultMap" type="org.springblade.modules.pay.entity.OrderInfoEntity">
    </resultMap>
    <select id="selectOrderInfoPage" resultMap="orderInfoResultMap">
        select * from jczz_order_info where is_deleted = 0
    </select>
</mapper>
src/main/java/org/springblade/modules/pay/mapper/PaymentInfoMapper.java
New file
@@ -0,0 +1,59 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.mapper;
import org.springblade.modules.pay.dto.PaymentInfoDTO;
import org.springblade.modules.pay.entity.PaymentInfoEntity;
import org.springblade.modules.pay.vo.PaymentInfoVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
 * 支付信息 Mapper 接口
 *
 * @author BladeX
 * @since 2024-04-07
 */
public interface PaymentInfoMapper extends BaseMapper<PaymentInfoEntity> {
    /**
     * 自定义分页
     *
     * @param page
     * @param paymentInfo
     * @return
     */
    List<PaymentInfoVO> selectPaymentInfoPage(IPage page, PaymentInfoVO paymentInfo);
    /**
     * 查询支付信息
     *
     * @param id 支付信息ID
     * @return 支付信息
     */
    public PaymentInfoDTO selectPaymentInfoById(Long id);
    /**
     * 查询支付信息列表
     *
     * @param paymentInfoDTO 支付信息
     * @return 支付信息集合
     */
    public List<PaymentInfoDTO> selectPaymentInfoList(PaymentInfoDTO paymentInfoDTO);
}
src/main/java/org/springblade/modules/pay/mapper/PaymentInfoMapper.xml
New file
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.modules.pay.mapper.PaymentInfoMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="paymentInfoResultMap" type="org.springblade.modules.pay.entity.PaymentInfoEntity">
    </resultMap>
    <select id="selectPaymentInfoPage" resultMap="paymentInfoResultMap">
        select * from jczz_payment_info where is_deleted = 0
    </select>
    <resultMap type="org.springblade.modules.pay.dto.PaymentInfoDTO" id="PaymentInfoDTOResult">
        <result property="id"    column="id"    />
        <result property="orderNo"    column="order_no"    />
        <result property="transactionId"    column="transaction_id"    />
        <result property="paymentType"    column="payment_type"    />
        <result property="tradeType"    column="trade_type"    />
        <result property="tradeState"    column="trade_state"    />
        <result property="payerTotal"    column="payer_total"    />
        <result property="content"    column="content"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateTime"    column="update_time"    />
    </resultMap>
    <sql id="selectPaymentInfo">
        select
            id,
            order_no,
            transaction_id,
            payment_type,
            trade_type,
            trade_state,
            payer_total,
            content,
            create_time,
            update_time
        from
            jczz_payment_info
    </sql>
    <select id="selectPaymentInfoById" parameterType="long" resultMap="PaymentInfoDTOResult">
        <include refid="selectPaymentInfo"/>
        where
        id = #{id}
    </select>
    <select id="selectPaymentInfoList" parameterType="org.springblade.modules.pay.dto.PaymentInfoDTO" resultMap="PaymentInfoDTOResult">
        <include refid="selectPaymentInfo"/>
        <where>
            <if test="id != null "> and id = #{id}</if>
            <if test="orderNo != null  and orderNo != ''"> and order_no = #{orderNo}</if>
            <if test="transactionId != null  and transactionId != ''"> and transaction_id = #{transactionId}</if>
            <if test="paymentType != null  and paymentType != ''"> and payment_type = #{paymentType}</if>
            <if test="tradeType != null  and tradeType != ''"> and trade_type = #{tradeType}</if>
            <if test="tradeState != null  and tradeState != ''"> and trade_state = #{tradeState}</if>
            <if test="payerTotal != null  and payerTotal != ''"> and payer_total = #{payerTotal}</if>
            <if test="content != null  and content != ''"> and content = #{content}</if>
            <if test="createTime != null "> and create_time = #{createTime}</if>
            <if test="updateTime != null "> and update_time = #{updateTime}</if>
        </where>
    </select>
</mapper>
src/main/java/org/springblade/modules/pay/mapper/RefundInfoMapper.java
New file
@@ -0,0 +1,60 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.mapper;
import org.springblade.modules.pay.dto.RefundInfoDTO;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import org.springblade.modules.pay.vo.RefundInfoVO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
 * 退款信息 Mapper 接口
 *
 * @author BladeX
 * @since 2024-04-07
 */
public interface RefundInfoMapper extends BaseMapper<RefundInfoEntity> {
    /**
     * 自定义分页
     *
     * @param page
     * @param refundInfo
     * @return
     */
    List<RefundInfoVO> selectRefundInfoPage(IPage page, RefundInfoVO refundInfo);
    /**
     * 查询退款信息
     *
     * @param id 退款信息ID
     * @return 退款信息
     */
    public RefundInfoDTO selectRefundInfoById(Long id);
    /**
     * 查询退款信息列表
     *
     * @param refundInfoDTO 退款信息
     * @return 退款信息集合
     */
    public List<RefundInfoDTO> selectRefundInfoList(RefundInfoDTO refundInfoDTO);
}
src/main/java/org/springblade/modules/pay/mapper/RefundInfoMapper.xml
New file
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.springblade.modules.pay.mapper.RefundInfoMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="refundInfoResultMap" type="org.springblade.modules.pay.entity.RefundInfoEntity">
    </resultMap>
    <select id="selectRefundInfoPage" resultMap="refundInfoResultMap">
        select * from jczz_refund_info where is_deleted = 0
    </select>
    <resultMap type="org.springblade.modules.pay.dto.RefundInfoDTO" id="RefundInfoDTOResult">
        <result property="id"    column="id"    />
        <result property="orderNo"    column="order_no"    />
        <result property="refundNo"    column="refund_no"    />
        <result property="refundId"    column="refund_id"    />
        <result property="totalFee"    column="total_fee"    />
        <result property="refund"    column="refund"    />
        <result property="reason"    column="reason"    />
        <result property="refundStatus"    column="refund_status"    />
        <result property="contentReturn"    column="content_return"    />
        <result property="contentNotify"    column="content_notify"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateTime"    column="update_time"    />
    </resultMap>
    <sql id="selectRefundInfo">
        select
            id,
            order_no,
            refund_no,
            refund_id,
            total_fee,
            refund,
            reason,
            refund_status,
            content_return,
            content_notify,
            create_time,
            update_time
        from
            jczz_refund_info
    </sql>
    <select id="selectRefundInfoById" parameterType="long" resultMap="RefundInfoDTOResult">
        <include refid="selectRefundInfo"/>
        where
        id = #{id}
    </select>
    <select id="selectRefundInfoList" parameterType="org.springblade.modules.pay.dto.RefundInfoDTO" resultMap="RefundInfoDTOResult">
        <include refid="selectRefundInfo"/>
        <where>
            <if test="id != null "> and id = #{id}</if>
            <if test="orderNo != null "> and order_no = #{orderNo}</if>
            <if test="refundNo != null "> and refund_no = #{refundNo}</if>
            <if test="refundId != null "> and refund_id = #{refundId}</if>
            <if test="totalFee != null  and totalFee != ''"> and total_fee = #{totalFee}</if>
            <if test="refund != null  and refund != ''"> and refund = #{refund}</if>
            <if test="reason != null  and reason != ''"> and reason = #{reason}</if>
            <if test="refundStatus != null  and refundStatus != ''"> and refund_status = #{refundStatus}</if>
            <if test="contentReturn != null  and contentReturn != ''"> and content_return = #{contentReturn}</if>
            <if test="contentNotify != null  and contentNotify != ''"> and content_notify = #{contentNotify}</if>
            <if test="createTime != null "> and create_time = #{createTime}</if>
            <if test="updateTime != null "> and update_time = #{updateTime}</if>
        </where>
    </select>
</mapper>
src/main/java/org/springblade/modules/pay/service/IOrderInfoService.java
New file
@@ -0,0 +1,59 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.common.enums.OrderStatus;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import org.springblade.modules.pay.vo.OrderInfoVO;
import org.springblade.core.mp.base.BaseService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;
/**
 * 订单信息 服务类
 *
 * @author BladeX
 * @since 2024-04-07
 */
public interface IOrderInfoService extends IService<OrderInfoEntity> {
    /**
     * 自定义分页
     *
     * @param page
     * @param orderInfo
     * @return
     */
    IPage<OrderInfoVO> selectOrderInfoPage(IPage<OrderInfoVO> page, OrderInfoVO orderInfo);
    OrderInfoEntity createOrderByPropertyChargeRecordId(Long propertyChargeRecordId);
    void saveCodeUrl(String orderNo, String codeUrl);
    List<OrderInfoEntity> listOrderByCreateTimeDesc();
    void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus);
    String getOrderStatus(String orderNo);
    List<OrderInfoEntity> getNoPayOrderByDuration(int minutes);
    OrderInfoEntity getOrderByOrderNo(String orderNo);
}
src/main/java/org/springblade/modules/pay/service/IPaymentInfoService.java
New file
@@ -0,0 +1,45 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.pay.entity.PaymentInfoEntity;
import org.springblade.modules.pay.vo.PaymentInfoVO;
import org.springblade.core.mp.base.BaseService;
import com.baomidou.mybatisplus.core.metadata.IPage;
/**
 * 支付信息 服务类
 *
 * @author BladeX
 * @since 2024-04-07
 */
public interface IPaymentInfoService extends IService<PaymentInfoEntity> {
    /**
     * 自定义分页
     *
     * @param page
     * @param paymentInfo
     * @return
     */
    IPage<PaymentInfoVO> selectPaymentInfoPage(IPage<PaymentInfoVO> page, PaymentInfoVO paymentInfo);
    void createPaymentInfo(String plainText);
}
src/main/java/org/springblade/modules/pay/service/IRefundInfoService.java
New file
@@ -0,0 +1,46 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.service;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import org.springblade.modules.pay.vo.RefundInfoVO;
import org.springblade.core.mp.base.BaseService;
import com.baomidou.mybatisplus.core.metadata.IPage;
/**
 * 退款信息 服务类
 *
 * @author BladeX
 * @since 2024-04-07
 */
public interface IRefundInfoService extends IService<RefundInfoEntity> {
    /**
     * 自定义分页
     *
     * @param page
     * @param refundInfo
     * @return
     */
    IPage<RefundInfoVO> selectRefundInfoPage(IPage<RefundInfoVO> page, RefundInfoVO refundInfo);
    RefundInfoEntity createRefundByOrderNo(String orderNo, String reason);
    void updateRefund(String plainText);
}
src/main/java/org/springblade/modules/pay/service/IWxPayService.java
@@ -3,20 +3,35 @@
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.modules.pay.entity.WxPayInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Map;
public interface IWxPayService extends IService<WxPayInfo> {
    /**
     * 获取openId
     * @param code
     * @return
     */
    Object getOpenId(String code);
    Map<String, String> jsapiPay(Long productId) throws IOException;
    Object getOpenId(String code);
    void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException;
    void cancelOrder(String orderNo) throws Exception;
    String queryOrder(String orderNo) throws Exception;
    void checkOrderStatus(String orderNo) throws Exception;
    void refund(String orderNo, String reason) throws Exception;
    String queryRefund(String orderNo) throws Exception;
    void checkRefundStatus(String refundNo) throws Exception;
    void processRefund(Map<String, Object> bodyMap) throws Exception;
    Map<String, String> jsapiPay(Long productId) throws IOException, Exception;
    String refundsNotify(HttpServletRequest request, HttpServletResponse response);
    String nativeNotify(HttpServletRequest request, HttpServletResponse response);
}
src/main/java/org/springblade/modules/pay/service/impl/OrderInfoServiceImpl.java
New file
@@ -0,0 +1,207 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.common.enums.OrderStatus;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import org.springblade.modules.pay.vo.OrderInfoVO;
import org.springblade.modules.pay.mapper.OrderInfoMapper;
import org.springblade.modules.pay.service.IOrderInfoService;
import org.springblade.modules.property.entity.PropertyChargeRecord;
import org.springblade.modules.property.service.IPropertyChargeRecordService;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import java.util.List;
import java.util.Random;
/**
 * 订单信息 服务实现类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfoEntity> implements IOrderInfoService {
    private static Logger logger = LoggerFactory.getLogger(OrderInfoServiceImpl.class);
    @Resource
    private IPropertyChargeRecordService iPropertyChargeRecordService;
    @Override
    public IPage<OrderInfoVO> selectOrderInfoPage(IPage<OrderInfoVO> page, OrderInfoVO orderInfo) {
        return page.setRecords(baseMapper.selectOrderInfoPage(page, orderInfo));
    }
    @Override
    public OrderInfoEntity createOrderByPropertyChargeRecordId(Long propertyChargeRecordId) {
        //查找已存在但未支付的订单
        OrderInfoEntity orderInfo = this.getNoPayOrderByPropertyChargeRecordId(propertyChargeRecordId);
        if( orderInfo != null){
            return orderInfo;
        }
        //获取商品信息
        PropertyChargeRecord propertyChargeRecord = iPropertyChargeRecordService.getById(propertyChargeRecordId);
        //生成订单
        orderInfo = new OrderInfoEntity();
        orderInfo.setTitle(propertyChargeRecord.getPayContent());
        orderInfo.setOrderNo(getOrderNoStr()); //订单号
        orderInfo.setProperChargeRecordId(propertyChargeRecordId);
        orderInfo.setTotalFee(propertyChargeRecord.getPayPrice().intValue()); //分
        orderInfo.setOrderStatus(OrderStatus.NOTPAY.getType());
        baseMapper.insert(orderInfo);
        return orderInfo;
    }
    /**
     * 存储订单二维码
     * @param orderNo
     * @param codeUrl
     */
    @Override
    public void saveCodeUrl(String orderNo, String codeUrl) {
        QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfoEntity orderInfo = new OrderInfoEntity();
        orderInfo.setCodeUrl(codeUrl);
        baseMapper.update(orderInfo, queryWrapper);
    }
    /**
     * 查询订单列表,并倒序查询
     * @return
     */
    @Override
    public List<OrderInfoEntity> listOrderByCreateTimeDesc() {
        QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<OrderInfoEntity>().orderByDesc("create_time");
        return baseMapper.selectList(queryWrapper);
    }
    /**
     * 根据订单号更新订单状态
     * @param orderNo
     * @param orderStatus
     */
    @Override
    public void updateStatusByOrderNo(String orderNo, OrderStatus orderStatus) {
        logger.info("更新订单状态 ===> {}", orderStatus.getType());
        QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfoEntity orderInfo = new OrderInfoEntity();
        orderInfo.setOrderStatus(orderStatus.getType());
        baseMapper.update(orderInfo, queryWrapper);
    }
    /**
     * 根据订单号获取订单状态
     * @param orderNo
     * @return
     */
    @Override
    public String getOrderStatus(String orderNo) {
        QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfoEntity orderInfo = baseMapper.selectOne(queryWrapper);
        if(orderInfo == null){
            return null;
        }
        return orderInfo.getOrderStatus();
    }
    /**
     * 查询创建超过minutes分钟并且未支付的订单
     * @param minutes
     * @return
     */
    @Override
    public List<OrderInfoEntity> getNoPayOrderByDuration(int minutes) {
        Instant instant = Instant.now().minus(Duration.ofMinutes(minutes));
        QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType());
        queryWrapper.le("create_time", instant);
        List<OrderInfoEntity> orderInfoList = baseMapper.selectList(queryWrapper);
        return orderInfoList;
    }
    /**
     * 根据订单号获取订单
     * @param orderNo
     * @return
     */
    @Override
    public OrderInfoEntity getOrderByOrderNo(String orderNo) {
        QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("order_no", orderNo);
        OrderInfoEntity orderInfo = baseMapper.selectOne(queryWrapper);
        return orderInfo;
    }
    /**
     * 根据商品id查询未支付订单
     * 防止重复创建订单对象
     * @param propertyChargeRecordId
     * @return
     */
    private OrderInfoEntity getNoPayOrderByPropertyChargeRecordId(Long propertyChargeRecordId) {
        QueryWrapper<OrderInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("product_id", propertyChargeRecordId);
        queryWrapper.eq("order_status", OrderStatus.NOTPAY.getType());
//        queryWrapper.eq("user_id", userId);
        OrderInfoEntity orderInfo = baseMapper.selectOne(queryWrapper);
        return orderInfo;
    }
    //生成订单号
    public static String getOrderNoStr() {
        String order = "pay" + new SimpleDateFormat("yyyyMMdd").format(new Date());
        Random r = new Random();
        for (int i = 0; i < 10; i++) {
            order += r.nextInt(9);
        }
        return order;
    }
}
src/main/java/org/springblade/modules/pay/service/impl/PaymentInfoServiceImpl.java
New file
@@ -0,0 +1,88 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.common.enums.PayType;
import org.springblade.modules.pay.entity.PaymentInfoEntity;
import org.springblade.modules.pay.mapper.PaymentInfoMapper;
import org.springblade.modules.pay.vo.PaymentInfoVO;
import org.springblade.modules.pay.service.IPaymentInfoService;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.HashMap;
import java.util.Map;
/**
 * 支付信息 服务实现类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Service
public class PaymentInfoServiceImpl extends ServiceImpl<PaymentInfoMapper, PaymentInfoEntity> implements IPaymentInfoService {
    private static Logger logger = LoggerFactory.getLogger(PaymentInfoServiceImpl.class);
    @Override
    public IPage<PaymentInfoVO> selectPaymentInfoPage(IPage<PaymentInfoVO> page, PaymentInfoVO paymentInfo) {
        return page.setRecords(baseMapper.selectPaymentInfoPage(page, paymentInfo));
    }
    /**
     * 记录支付日志
     * @param plainText
     */
    @Override
    public void createPaymentInfo(String plainText) {
        logger.info("记录支付日志");
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        //订单号
        String orderNo = (String)plainTextMap.get("out_trade_no");
        //业务编号
        String transactionId = (String)plainTextMap.get("transaction_id");
        //支付类型
        String tradeType = (String)plainTextMap.get("trade_type");
        //交易状态
        String tradeState = (String)plainTextMap.get("trade_state");
        //用户实际支付金额
        Map<String, Object> amount = (Map)plainTextMap.get("amount");
        Integer payerTotal = ((Double) amount.get("payer_total")).intValue();
        PaymentInfoEntity paymentInfo = new PaymentInfoEntity();
        paymentInfo.setOrderNo(orderNo);
        paymentInfo.setPaymentType(PayType.WXPAY.getType());
        paymentInfo.setTransactionId(transactionId);
        paymentInfo.setTradeType(tradeType);
        paymentInfo.setTradeState(tradeState);
        paymentInfo.setPayerTotal(payerTotal);
        paymentInfo.setContent(plainText);
        baseMapper.insert(paymentInfo);
    }
}
src/main/java/org/springblade/modules/pay/service/impl/RefundInfoServiceImpl.java
New file
@@ -0,0 +1,118 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import org.springblade.modules.pay.mapper.RefundInfoMapper;
import org.springblade.modules.pay.service.IOrderInfoService;
import org.springblade.modules.pay.service.IRefundInfoService;
import org.springblade.modules.pay.vo.RefundInfoVO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
/**
 * 退款信息 服务实现类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Service
public class RefundInfoServiceImpl extends ServiceImpl<RefundInfoMapper, RefundInfoEntity> implements IRefundInfoService {
    @Resource
    private IOrderInfoService orderInfoService;
    @Override
    public IPage<RefundInfoVO> selectRefundInfoPage(IPage<RefundInfoVO> page, RefundInfoVO refundInfo) {
        return page.setRecords(baseMapper.selectRefundInfoPage(page, refundInfo));
    }
    @Override
    public RefundInfoEntity createRefundByOrderNo(String orderNo, String reason) {
        //根据订单号获取订单信息
        OrderInfoEntity orderInfo = orderInfoService.getOne(Wrappers.<OrderInfoEntity>lambdaQuery()
            .eq(OrderInfoEntity::getOrderNo, orderNo));
        //根据订单号生成退款订单
        RefundInfoEntity refundInfo = new RefundInfoEntity();
        refundInfo.setOrderNo(orderNo);//订单编号
        refundInfo.setRefundNo(getOrderNoStr());//退款单编号
        refundInfo.setTotalFee(orderInfo.getTotalFee());//原订单金额(分)
        refundInfo.setRefund(orderInfo.getTotalFee());//退款金额(分)
        refundInfo.setReason(reason);//退款原因
        //保存退款订单
        baseMapper.insert(refundInfo);
        return refundInfo;
    }
    @Override
    public void updateRefund(String content) {
        //将json字符串转换成Map
        Gson gson = new Gson();
        Map<String, String> resultMap = gson.fromJson(content, HashMap.class);
        //根据退款单编号修改退款单
        QueryWrapper<RefundInfoEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("refund_no", resultMap.get("out_refund_no"));
        //设置要修改的字段
        RefundInfoEntity refundInfo = new RefundInfoEntity();
        refundInfo.setRefundId(resultMap.get("refund_id"));//微信支付退款单号
        //查询退款和申请退款中的返回参数
        if(resultMap.get("status") != null){
            refundInfo.setRefundStatus(resultMap.get("status"));//退款状态
            refundInfo.setContentReturn(content);//将全部响应结果存入数据库的content字段
        }
        //退款回调中的回调参数
        if(resultMap.get("refund_status") != null){
            refundInfo.setRefundStatus(resultMap.get("refund_status"));//退款状态
            refundInfo.setContentNotify(content);//将全部响应结果存入数据库的content字段
        }
        //更新退款单
        baseMapper.update(refundInfo, queryWrapper);
    }
    //生成订单号
    public static String getOrderNoStr() {
        String order = "refund" + new SimpleDateFormat("yyyyMMdd").format(new Date());
        Random r = new Random();
        for (int i = 0; i < 10; i++) {
            order += r.nextInt(9);
        }
        return order;
    }
}
src/main/java/org/springblade/modules/pay/service/impl/WxPayServiceImpl.java
@@ -1,26 +1,48 @@
package org.springblade.modules.pay.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.wxpay.sdk.WXPayUtil;
import com.google.gson.Gson;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
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.springblade.common.config.WxPayConfig;
import org.springblade.common.enums.OrderStatus;
import org.springblade.common.enums.wxpay.WxApiType;
import org.springblade.common.enums.wxpay.WxNotifyType;
import org.springblade.modules.pay.controller.WxPayController;
import org.springblade.common.enums.wxpay.WxRefundStatus;
import org.springblade.common.enums.wxpay.WxTradeState;
import org.springblade.common.utils.HttpUtils;
import org.springblade.common.utils.SpringUtils;
import org.springblade.common.utils.WechatPay2ValidatorForRequest;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import org.springblade.modules.pay.entity.WxPayInfo;
import org.springblade.modules.pay.mapper.WxPayMapper;
import org.springblade.modules.pay.service.IOrderInfoService;
import org.springblade.modules.pay.service.IPaymentInfoService;
import org.springblade.modules.pay.service.IRefundInfoService;
import org.springblade.modules.pay.service.IWxPayService;
import org.springframework.beans.factory.annotation.Value;
import org.springblade.modules.property.entity.PropertyChargeRecord;
import org.springblade.modules.property.service.IPropertyChargeService;
import org.springblade.modules.system.entity.User;
import org.springblade.modules.system.service.IUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -31,100 +53,37 @@
@Service
public class WxPayServiceImpl extends ServiceImpl<WxPayMapper,WxPayInfo> implements IWxPayService {
    private static Logger logger = LoggerFactory.getLogger(WxPayServiceImpl.class);
    private final String  MINI_PROGRAM_APP_ID = "wx41aa8a5d2e565a05";
    private static final Logger logger = LoggerFactory.getLogger(WxPayServiceImpl.class);
    @Resource
    private WxPayConfig wxPayConfig;
    @Resource
    private IPropertyChargeService iPropertyChargeService;
    @Resource
    private CloseableHttpClient wxPayClient;
    @Resource
    private IOrderInfoService orderInfoService;
    @Resource
    private IPaymentInfoService paymentInfoService;
    @Resource
    private IRefundInfoService refundsInfoService;
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public Object getOpenId(String code) {
        return null;
    }
    @Override
    public Map<String, String> jsapiPay(Long productId) throws IOException {
        String prepayId = "";
        //生成订单
//        OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId);
        logger.info("调用统一下单API");
//        //调用统一下单API
//        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PAY.getType()));
//
//        // 请求body参数
//
//        Gson gson = new Gson();
//        Map paramsMap = new HashMap();
//        paramsMap.put("appid", wxPayConfig.getAppid());
//        paramsMap.put("mchid", wxPayConfig.getMchId());
//        paramsMap.put("description", orderInfo.getTitle());
//        paramsMap.put("out_trade_no", orderInfo.getOrderNo());
//        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
//
//        Map amountMap = new HashMap();
//        amountMap.put("total", orderInfo.getTotalFee());
//        amountMap.put("currency", "CNY");
//
//        paramsMap.put("amount", amountMap);
//
//        Map payerMap = new HashMap();
//        payerMap.put("openid", "oSX3G6OUuGaKJbWzSHUpPbzgtDXo");
//
//        paramsMap.put("payer", payerMap);
//
//        //将参数转换成json字符串
//        String jsonParams = gson.toJson(paramsMap);
//        logger.info("请求参数 ===> {}" + jsonParams);
//
//        StringEntity entity = new StringEntity(jsonParams, "utf-8");
//        entity.setContentType("application/json");
//        httpPost.setEntity(entity);
//        httpPost.setHeader("Accept", "application/json");
//
//        //完成签名并执行请求
//        CloseableHttpResponse response = wxPayClient.execute(httpPost);
//
//        try {
//            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
//            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
//            if (statusCode == 200) { //处理成功
//                logger.info("成功, 返回结果 = " + bodyAsString);
//            } else if (statusCode == 204) { //处理成功,无返回Body
//                logger.info("成功");
//            } else {
//                logger.info("jszpi下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
//                throw new IOException("request failed");
//            }
//            //响应结果
//            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
//            //预支付交易会话标识
//            prepayId = resultMap.get("prepay_id");
//
//            //返回预支付交易会话标识
//            // 组装前端预下单参数
//            /** 返回给前端所需要的数据 */
//            SortedMap<String, String> payMap = new TreeMap<>();
//            String nonceStr = WXPayUtil.generateNonceStr();
//            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
//            payMap.put("appId", wxPayConfig.getAppid());
//            payMap.put("timeStamp", timestamp);
//            payMap.put("nonceStr", nonceStr);
//            payMap.put("signType", "RSA");
//            payMap.put("package", "prepay_id=" + prepayId);
//            String paySign = getSign(wxPayConfig.getAppid(), prepayId, timestamp, nonceStr);
//            payMap.put("paySign", paySign);
//            logger.info("返回参数 ===> {}" + gson.toJson(payMap));
//            return payMap;
//        } finally {
//            response.close();
//        }
        return null;
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        logger.info("处理订单");
@@ -143,32 +102,410 @@
        以避免函数重入造成的数据混乱*/
        //尝试获取锁:
        // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
//        if (lock.tryLock()) {
//            try {
//                //处理重复的通知
//                //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
//                String orderStatus = orderInfoService.getOrderStatus(orderNo);
//                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
//                    return;
//                }
//
//                //模拟通知并发
//                try {
//                    TimeUnit.SECONDS.sleep(5);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//
//                //更新订单状态
//                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
//
//                //记录支付日志
//                paymentInfoService.createPaymentInfo(plainText);
//            } finally {
//                //要主动释放锁
//                lock.unlock();
//            }
//        }
        if (lock.tryLock()) {
            try {
                //处理重复的通知
                //接口调用的幂等性:无论接口被调用多少次,产生的结果是一致的。
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                    return;
                }
                //模拟通知并发
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                //记录支付日志
                paymentInfoService.createPaymentInfo(plainText);
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }
    }
    /**
     * 用户取消订单
     *
     * @param orderNo
     */
    @Override
    public void cancelOrder(String orderNo) throws Exception {
        //调用微信支付的关单接口
        this.closeOrder(orderNo);
        //更新商户端的订单状态
        orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CANCEL);
    }
    @Override
    public String queryOrder(String orderNo) throws Exception {
        logger.info("查单接口调用 ===> {}", orderNo);
        String url = String.format(WxApiType.ORDER_QUERY_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url).concat("?mchid=").concat(wxPayConfig.getMchId());
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                logger.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功,无返回Body
                logger.info("成功");
            } else {
                logger.info("查单接口调用,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }
    /**
     * 根据订单号查询微信支付查单接口,核实订单状态
     * 如果订单已支付,则更新商户端订单状态,并记录支付日志
     * 如果订单未支付,则调用关单接口关闭订单,并更新商户端订单状态
     *
     * @param orderNo
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkOrderStatus(String orderNo) throws Exception {
        logger.info("根据订单号核实订单状态 ===> {}", orderNo);
        //调用微信支付查单接口
        String result = this.queryOrder(orderNo);
        Gson gson = new Gson();
        Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
        //获取微信支付端的订单状态
        String tradeState = resultMap.get("trade_state");
        //判断订单状态
        if (WxTradeState.SUCCESS.getType().equals(tradeState)) {
            logger.info("核实订单已支付 ===> {}", orderNo);
            //如果确认订单已支付则更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
            //记录支付日志
            paymentInfoService.createPaymentInfo(result);
        }
        if (WxTradeState.NOTPAY.getType().equals(tradeState)) {
            logger.info("核实订单未支付 ===> {}", orderNo);
            //如果订单未支付,则调用关单接口
            this.closeOrder(orderNo);
            //更新本地订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.CLOSED);
        }
    }
    /**
     * 退款
     *
     * @param orderNo
     * @param reason
     * @throws IOException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo, String reason) throws Exception {
        logger.info("创建退款单记录");
        //根据订单编号创建退款单
        RefundInfoEntity refundsInfo = refundsInfoService.createRefundByOrderNo(orderNo, reason);
        logger.info("调用退款API");
        //调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("out_trade_no", orderNo);//订单编号
        paramsMap.put("out_refund_no", refundsInfo.getRefundNo());//退款单编号
        paramsMap.put("reason", reason);//退款原因
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));//退款通知地址
        Map amountMap = new HashMap();
        amountMap.put("refund", refundsInfo.getRefund());//退款金额
        amountMap.put("total", refundsInfo.getTotalFee());//原订单金额
        amountMap.put("currency", "CNY");//退款币种
        paramsMap.put("amount", amountMap);
        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        logger.info("请求参数 ===> {}" + jsonParams);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");//设置请求报文格式
        httpPost.setEntity(entity);//将请求报文放入请求对象
        httpPost.setHeader("Accept", "application/json");//设置响应报文格式
        //完成签名并执行请求,并完成验签
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                logger.info("成功, 退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                logger.info("成功");
            } else {
                throw new RuntimeException("退款异常, 响应码 = " + statusCode + ", 退款返回结果 = " + bodyAsString);
            }
            //更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_PROCESSING);
            //更新退款单
            refundsInfoService.updateRefund(bodyAsString);
        } finally {
            response.close();
        }
    }
    /**
     * 查询退款接口调用
     *
     * @param refundNo
     * @return
     */
    @Override
    public String queryRefund(String refundNo) throws Exception {
        logger.info("查询退款接口调用 ===> {}", refundNo);
        String url = String.format(WxApiType.DOMESTIC_REFUNDS_QUERY.getType(), refundNo);
        url = wxPayConfig.getDomain().concat(url);
        //创建远程Get 请求对象
        HttpGet httpGet = new HttpGet(url);
        httpGet.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpGet);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode == 200) {
                logger.info("成功, 查询退款返回结果 = " + bodyAsString);
            } else if (statusCode == 204) {
                logger.info("成功");
            } else {
                throw new RuntimeException("查询退款异常, 响应码 = " + statusCode + ", 查询退款返回结果 = " + bodyAsString);
            }
            return bodyAsString;
        } finally {
            response.close();
        }
    }
    /**
     * 根据退款单号核实退款单状态
     *
     * @param refundNo
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void checkRefundStatus(String refundNo) throws Exception {
        logger.info("根据退款单号核实退款单状态 ===> {}", refundNo);
        //调用查询退款单接口
        String result = this.queryRefund(refundNo);
        //组装json请求体字符串
        Gson gson = new Gson();
        Map<String, String> resultMap = gson.fromJson(result, HashMap.class);
        //获取微信支付端退款状态
        String status = resultMap.get("status");
        String orderNo = resultMap.get("out_trade_no");
        if (WxRefundStatus.SUCCESS.getType().equals(status)) {
            logger.info("核实订单已退款成功 ===> {}", refundNo);
            //如果确认退款成功,则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
            //更新退款单
            refundsInfoService.updateRefund(result);
        }
        if (WxRefundStatus.ABNORMAL.getType().equals(status)) {
            logger.info("核实订单退款异常  ===> {}", refundNo);
            //如果确认退款成功,则更新订单状态
            orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);
            //更新退款单
            refundsInfoService.updateRefund(result);
        }
    }
    /**
     * 处理退款单
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processRefund(Map<String, Object> bodyMap) throws Exception {
        logger.info("退款单");
        //解密报文
        String plainText = decryptFromResource(bodyMap);
        //将明文转换成map
        Gson gson = new Gson();
        HashMap plainTextMap = gson.fromJson(plainText, HashMap.class);
        String orderNo = (String) plainTextMap.get("out_trade_no");
        if (lock.tryLock()) {
            try {
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if (!OrderStatus.REFUND_PROCESSING.getType().equals(orderStatus)) {
                    return;
                }
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
                //更新退款单
                refundsInfoService.updateRefund(plainText);
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }
    }
    @Override
    public Map<String, String> jsapiPay(Long properChargeRecordId) throws Exception {
        String prepayId = "";
        //生成订单
        OrderInfoEntity orderInfo = orderInfoService.getOne(Wrappers.<OrderInfoEntity>lambdaQuery().eq(OrderInfoEntity::getProperChargeRecordId,properChargeRecordId));
        IUserService userService = SpringUtils.getBean(IUserService.class);
        User serviceOne = userService.getOne(Wrappers.<User>lambdaQuery().eq(User::getId, AuthUtil.getUserId()));
        logger.info("调用统一下单API");
        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PAY.getType()));
        // 请求body参数
        Gson gson = new Gson();
        Map paramsMap = new HashMap();
        paramsMap.put("appid", wxPayConfig.getAppid());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", orderInfo.getTitle());
        paramsMap.put("out_trade_no", orderInfo.getOrderNo());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
        Map amountMap = new HashMap();
        amountMap.put("total", orderInfo.getTotalFee());
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);
        Map payerMap = new HashMap();
        payerMap.put("openid", serviceOne.getMiniOpenId());
        paramsMap.put("payer", payerMap);
        // 是否指定分账,枚举值 true:是 false:否
        paramsMap.put("profit_sharing", true);
        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        logger.info("请求参数 ===> {}" + jsonParams);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            String bodyAsString = EntityUtils.toString(response.getEntity());//响应体
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                logger.info("成功, 返回结果 = " + bodyAsString);
            } else if (statusCode == 204) { //处理成功,无返回Body
                logger.info("成功");
            } else {
                logger.info("jszpi下单失败,响应码 = " + statusCode + ",返回结果 = " + bodyAsString);
                throw new IOException("request failed");
            }
            //响应结果
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            //预支付交易会话标识
            prepayId = resultMap.get("prepay_id");
            //返回预支付交易会话标识
            // 组装前端预下单参数
            /** 返回给前端所需要的数据 */
            SortedMap<String, String> payMap = new TreeMap<>();
            String nonceStr = WXPayUtil.generateNonceStr();
            String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
            payMap.put("appId", wxPayConfig.getAppid());
            payMap.put("timeStamp", timestamp);
            payMap.put("nonceStr", nonceStr);
            payMap.put("signType", "RSA");
            payMap.put("package", "prepay_id=" + prepayId);
            String paySign = getSign(wxPayConfig.getAppid(), prepayId, timestamp, nonceStr);
            payMap.put("paySign", paySign);
            logger.info("返回参数 ===> {}" + gson.toJson(payMap));
            return payMap;
        } finally {
            response.close();
        }
    }
    /**
@@ -196,7 +533,7 @@
        //签名方式
        Signature sign = Signature.getInstance("SHA256withRSA");
        //私钥,通过MyPrivateKey来获取,这是个静态类可以接调用方法 ,需要的是_key.pem文件的绝对路径配上文件名
//        sign.initSign(getPrivateKey(wxPayConfig.getPrivateKeyPath()));
        sign.initSign(getPrivateKey(wxPayConfig.getPrivateKeyPath()));
        sign.update(message);
        return Base64.getEncoder().encodeToString(sign.sign());
@@ -227,6 +564,52 @@
        }
    }
    /**
     * 关单接口的调用
     *
     * @param orderNo
     */
    private void closeOrder(String orderNo) throws Exception {
        logger.info("关单接口的调用,订单号 ===> {}", orderNo);
        //创建远程请求对象
        String url = String.format(WxApiType.CLOSE_ORDER_BY_NO.getType(), orderNo);
        url = wxPayConfig.getDomain().concat(url);
        HttpPost httpPost = new HttpPost(url);
        //组装json请求体
        Gson gson = new Gson();
        Map<String, String> paramsMap = new HashMap<>();
        paramsMap.put("mchid", wxPayConfig.getMchId());
        String jsonParams = gson.toJson(paramsMap);
        logger.info("请求参数 ===> {}", jsonParams);
        //将请求参数设置到请求对象中
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");
        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            int statusCode = response.getStatusLine().getStatusCode();//响应状态码
            if (statusCode == 200) { //处理成功
                logger.info("成功200");
            } else if (statusCode == 204) { //处理成功,无返回Body
                logger.info("成功204");
            } else {
                logger.info("Native下单失败,响应码 = " + statusCode);
                throw new IOException("request failed");
            }
        } finally {
            response.close();
        }
    }
    /**
     * 对称解密
     *
@@ -247,15 +630,122 @@
        String associatedData = resourceMap.get("associated_data");
        logger.info("密文 ===> {}", ciphertext);
//        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
//        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
//            nonce.getBytes(StandardCharsets.UTF_8),
//            ciphertext);
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
            nonce.getBytes(StandardCharsets.UTF_8),
            ciphertext);
//        logger.info("明文 ===> {}", plainText);
        logger.info("明文 ===> {}", plainText);
//        return plainText;
        return null;
        return plainText;
    }
    @Override
    public String nativeNotify(HttpServletRequest request, HttpServletResponse response) {
        Gson gson = new Gson();
        Map<String, String> map = new HashMap<>();//应答对象
        try {
            //处理通知参数
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            logger.info("支付通知的id ===> {}", requestId);
            logger.info("支付通知的完整数据 ===> {}", body);
            //int a = 9 / 0;
            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                = new WechatPay2ValidatorForRequest(wxPayConfig.getVerifier(), requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                logger.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return gson.toJson(map);
            }
            logger.info("通知验签成功");
            //处理订单
            processOrder(bodyMap);
            //成功应答
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }
    }
    @Override
    public String refundsNotify(HttpServletRequest request, HttpServletResponse response) {
        Gson gson = new Gson();
        Map<String, String> map = new HashMap<>();//应答对象
        try {
            //处理通知参数
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            logger.info("支付通知的id ===> {}", requestId);
            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                = new WechatPay2ValidatorForRequest(wxPayConfig.getVerifier(), requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                logger.error("通知验签失败");
                //失败应答
                response.setStatus(500);
                map.put("code", "ERROR");
                map.put("message", "通知验签失败");
                return gson.toJson(map);
            }
            logger.info("通知验签成功");
            //处理退款单
            processRefund(bodyMap);
            //成功应答
            response.setStatus(200);
            map.put("code", "SUCCESS");
            map.put("message", "成功");
            return gson.toJson(map);
        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            response.setStatus(500);
            map.put("code", "ERROR");
            map.put("message", "失败");
            return gson.toJson(map);
        }
    }
    //创建签名所需要的参数xml格式
//    public static String createXML(Map<String, Object> map) {
//        String xml = "<xml>";
//        Set<String> set = map.keySet();
//        Iterator<String> i = set.iterator();
//        while (i.hasNext()) {
//            String str = i.next();
//            xml += "<" + str + ">" + "<![CDATA[" + map.get(str) + "]]>" + "</" + str + ">";
//        }
//        xml += "</xml>";
//        return xml;
//    }
}
src/main/java/org/springblade/modules/pay/vo/OrderInfoVO.java
New file
@@ -0,0 +1,34 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.vo;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 订单信息 视图实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class OrderInfoVO extends OrderInfoEntity {
    private static final long serialVersionUID = 1L;
}
src/main/java/org/springblade/modules/pay/vo/PaymentInfoVO.java
New file
@@ -0,0 +1,34 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.vo;
import org.springblade.modules.pay.entity.PaymentInfoEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 支付信息 视图实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class PaymentInfoVO extends PaymentInfoEntity {
    private static final long serialVersionUID = 1L;
}
src/main/java/org/springblade/modules/pay/vo/RefundInfoVO.java
New file
@@ -0,0 +1,34 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.vo;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
 * 退款信息 视图实体类
 *
 * @author BladeX
 * @since 2024-04-07
 */
@Data
@EqualsAndHashCode(callSuper = true)
public class RefundInfoVO extends RefundInfoEntity {
    private static final long serialVersionUID = 1L;
}
src/main/java/org/springblade/modules/pay/wrapper/OrderInfoWrapper.java
New file
@@ -0,0 +1,51 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.wrapper;
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.modules.pay.entity.OrderInfoEntity;
import org.springblade.modules.pay.vo.OrderInfoVO;
import java.util.Objects;
/**
 * 订单信息 包装类,返回视图层所需的字段
 *
 * @author BladeX
 * @since 2024-04-07
 */
public class OrderInfoWrapper extends BaseEntityWrapper<OrderInfoEntity, OrderInfoVO>  {
    public static OrderInfoWrapper build() {
        return new OrderInfoWrapper();
     }
    @Override
    public OrderInfoVO entityVO(OrderInfoEntity orderInfo) {
        OrderInfoVO orderInfoVO = Objects.requireNonNull(BeanUtil.copy(orderInfo, OrderInfoVO.class));
        //User createUser = UserCache.getUser(orderInfo.getCreateUser());
        //User updateUser = UserCache.getUser(orderInfo.getUpdateUser());
        //orderInfoVO.setCreateUserName(createUser.getName());
        //orderInfoVO.setUpdateUserName(updateUser.getName());
        return orderInfoVO;
    }
}
src/main/java/org/springblade/modules/pay/wrapper/PaymentInfoWrapper.java
New file
@@ -0,0 +1,51 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.wrapper;
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.modules.pay.entity.PaymentInfoEntity;
import org.springblade.modules.pay.vo.PaymentInfoVO;
import java.util.Objects;
/**
 * 支付信息 包装类,返回视图层所需的字段
 *
 * @author BladeX
 * @since 2024-04-07
 */
public class PaymentInfoWrapper extends BaseEntityWrapper<PaymentInfoEntity, PaymentInfoVO>  {
    public static PaymentInfoWrapper build() {
        return new PaymentInfoWrapper();
     }
    @Override
    public PaymentInfoVO entityVO(PaymentInfoEntity paymentInfo) {
        PaymentInfoVO paymentInfoVO = Objects.requireNonNull(BeanUtil.copy(paymentInfo, PaymentInfoVO.class));
        //User createUser = UserCache.getUser(paymentInfo.getCreateUser());
        //User updateUser = UserCache.getUser(paymentInfo.getUpdateUser());
        //paymentInfoVO.setCreateUserName(createUser.getName());
        //paymentInfoVO.setUpdateUserName(updateUser.getName());
        return paymentInfoVO;
    }
}
src/main/java/org/springblade/modules/pay/wrapper/RefundInfoWrapper.java
New file
@@ -0,0 +1,51 @@
/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.pay.wrapper;
import org.springblade.core.mp.support.BaseEntityWrapper;
import org.springblade.core.tool.utils.BeanUtil;
import org.springblade.modules.pay.entity.RefundInfoEntity;
import org.springblade.modules.pay.vo.RefundInfoVO;
import java.util.Objects;
/**
 * 退款信息 包装类,返回视图层所需的字段
 *
 * @author BladeX
 * @since 2024-04-07
 */
public class RefundInfoWrapper extends BaseEntityWrapper<RefundInfoEntity, RefundInfoVO>  {
    public static RefundInfoWrapper build() {
        return new RefundInfoWrapper();
     }
    @Override
    public RefundInfoVO entityVO(RefundInfoEntity refundInfo) {
        RefundInfoVO refundInfoVO = Objects.requireNonNull(BeanUtil.copy(refundInfo, RefundInfoVO.class));
        //User createUser = UserCache.getUser(refundInfo.getCreateUser());
        //User updateUser = UserCache.getUser(refundInfo.getUpdateUser());
        //refundInfoVO.setCreateUserName(createUser.getName());
        //refundInfoVO.setUpdateUserName(updateUser.getName());
        return refundInfoVO;
    }
}
src/main/java/org/springblade/modules/property/dto/PropertyChargeRecordDTO.java
New file
@@ -0,0 +1,18 @@
package org.springblade.modules.property.dto;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.springblade.modules.property.entity.PropertyChargeRecord;
/**
 * 缴费记录表对象 jczz_property_charge_record
 *
 * @author ${context.author}
 * @date 2024-04-07 15:44:09
 */
@ApiModel(value = "PropertyChargeRecordDTO对象")
@Data
public class PropertyChargeRecordDTO extends PropertyChargeRecord
{
}
src/main/java/org/springblade/modules/property/entity/PropertyChargeRecord.java
@@ -1,47 +1,127 @@
package org.springblade.modules.property.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springblade.core.mp.base.BaseEntity;
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.util.Date;
@Data
@TableName("jczz_property_charge_record")
@ApiModel(value = "PropertyChargeRecord", description = "物业缴费记录")
public class PropertyChargeRecord extends BaseEntity {
public class PropertyChargeRecord  {
    //物业公司的deptId
    /** 主键 */
    @ApiModelProperty(value = "主键ID", example = "")
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    /** 物业公司id */
    @ApiModelProperty(value = "物业公司id", example = "")
    @TableField("property_id")
    private String propertyId;
    /** 订单号 */
    @ApiModelProperty(value = "订单号", example = "")
    @TableField("order_no")
    private String orderNo;
    //缴费项id
    /** 缴费项id */
    @ApiModelProperty(value = "缴费项id", example = "")
    @TableField("charge_id")
    private String chargeId;
    //编号
    /** 缴费单号 */
    @ApiModelProperty(value = "缴费单号", example = "")
    @TableField("no")
    private String no;
    //付款人id
    /** 付款人id */
    @ApiModelProperty(value = "付款人id", example = "")
    @TableField("pay_user")
    private String payUser;
    //付款时间
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    /** 付费时间 */
    @ApiModelProperty(value = "付费时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("pay_time")
    private Date payTime;
    //缴费方式
    /** 付款方式 */
    @ApiModelProperty(value = "付款方式", example = "")
    @TableField("pay_func")
    private String payFunc;
    //付款金额
    private String payPrice;
    /** 付款金额 */
    @ApiModelProperty(value = "付款金额(分)", example = "")
    @TableField("pay_price")
    private BigDecimal payPrice;
    //付款房屋的houseCode
    private String payHouse;
    /** 房屋编码 */
    @ApiModelProperty(value = "房屋编码", example = "")
    @TableField("house_code")
    private String houseCode;
    //缴费内容
    /** 缴费内容 */
    @ApiModelProperty(value = "缴费内容", example = "")
    @TableField("pay_content")
    private String payContent;
    /** 创建时间 */
    @ApiModelProperty(value = "创建时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("create_time")
    private Date createTime;
    /** 创建人 */
    @ApiModelProperty(value = "创建人", example = "")
    @TableField("create_user")
    private Long createUser;
    /** 创建部门 */
    @ApiModelProperty(value = "创建部门", example = "")
    @TableField("create_dept")
    private Long createDept;
    /** 修改人 */
    @ApiModelProperty(value = "修改人", example = "")
    @TableField("update_user")
    private Long updateUser;
    /** 修改时间 */
    @ApiModelProperty(value = "修改时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("update_time")
    private Date updateTime;
    /** 状态(1、待支付;2、已支付;99取消支付) */
    @ApiModelProperty(value = "状态(1、待支付;2、已支付;99取消支付)", example = "")
    @TableField("status")
    private Integer status;
    /** 是否已删除 */
    @ApiModelProperty(value = "是否已删除", example = "")
    @TableField("is_deleted")
    private Integer isDeleted;
    /** 开始时间 */
    @ApiModelProperty(value = "开始时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("start_time")
    private Date startTime;
    /** 到期时间 */
    @ApiModelProperty(value = "到期时间", example = "")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @TableField("end_time")
    private Date endTime;
}
src/main/java/org/springblade/modules/property/mapper/PropertyChargeRecordMapper.java
@@ -19,6 +19,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.apache.ibatis.annotations.Param;
import org.springblade.modules.property.dto.PropertyChargeRecordDTO;
import org.springblade.modules.property.entity.PropertyCharge;
import org.springblade.modules.property.entity.PropertyChargeRecord;
import org.springblade.modules.property.vo.PropertyChargeRecordVO;
@@ -36,4 +37,21 @@
    List<PropertyChargeRecordVO> getPage(IPage<PropertyChargeRecordVO> page, @Param("vo") PropertyChargeRecordVO vo);
    /**
     * 查询缴费记录表
     *
     * @param id 缴费记录表ID
     * @return 缴费记录表
     */
    public PropertyChargeRecordDTO selectPropertyChargeRecordById(Long id);
    /**
     * 查询缴费记录表列表
     *
     * @param propertyChargeRecordDTO 缴费记录表
     * @return 缴费记录表集合
     */
    public List<PropertyChargeRecordDTO> selectPropertyChargeRecordList(PropertyChargeRecordDTO propertyChargeRecordDTO);
}
src/main/java/org/springblade/modules/property/mapper/PropertyChargeRecordMapper.xml
@@ -6,4 +6,83 @@
    <select id="getPage" resultType="org.springblade.modules.property.vo.PropertyChargeRecordVO">
        SELECT * FROM jczz_property_charge_record where is_deleted = 0
    </select>
    <resultMap type="org.springblade.modules.property.dto.PropertyChargeRecordDTO" id="PropertyChargeRecordDTOResult">
        <result property="id"    column="id"    />
        <result property="propertyId"    column="property_id"    />
        <result property="chargeId"    column="charge_id"    />
        <result property="no"    column="no"    />
        <result property="payUser"    column="pay_user"    />
        <result property="payTime"    column="pay_time"    />
        <result property="payFunc"    column="pay_func"    />
        <result property="payPrice"    column="pay_price"    />
        <result property="houseCode"    column="house_code"    />
        <result property="payContent"    column="pay_content"    />
        <result property="createTime"    column="create_time"    />
        <result property="createUser"    column="create_user"    />
        <result property="createDept"    column="create_dept"    />
        <result property="updateUser"    column="update_user"    />
        <result property="updateTime"    column="update_time"    />
        <result property="status"    column="status"    />
        <result property="isDeleted"    column="is_deleted"    />
        <result property="startTime"    column="start_time"    />
        <result property="endTime"    column="end_time"    />
    </resultMap>
    <sql id="selectPropertyChargeRecord">
        select
            id,
            property_id,
            charge_id,
            no,
            pay_user,
            pay_time,
            pay_func,
            pay_price,
            house_code,
            pay_content,
            create_time,
            create_user,
            create_dept,
            update_user,
            update_time,
            status,
            is_deleted,
            start_time,
            end_time
        from
            jczz_property_charge_record
    </sql>
    <select id="selectPropertyChargeRecordById" parameterType="long" resultMap="PropertyChargeRecordDTOResult">
        <include refid="selectPropertyChargeRecord"/>
        where
        id = #{id}
    </select>
    <select id="selectPropertyChargeRecordList" parameterType="org.springblade.modules.property.dto.PropertyChargeRecordDTO" resultMap="PropertyChargeRecordDTOResult">
        <include refid="selectPropertyChargeRecord"/>
        <where>
            <if test="id != null "> and id = #{id}</if>
            <if test="propertyId != null  and propertyId != ''"> and property_id = #{propertyId}</if>
            <if test="chargeId != null  and chargeId != ''"> and charge_id = #{chargeId}</if>
            <if test="no != null  and no != ''"> and no = #{no}</if>
            <if test="payUser != null  and payUser != ''"> and pay_user = #{payUser}</if>
            <if test="payTime != null "> and pay_time = #{payTime}</if>
            <if test="payFunc != null  and payFunc != ''"> and pay_func = #{payFunc}</if>
            <if test="payPrice != null "> and pay_price = #{payPrice}</if>
            <if test="houseCode != null  and houseCode != ''"> and house_code = #{houseCode}</if>
            <if test="payContent != null  and payContent != ''"> and pay_content = #{payContent}</if>
            <if test="createTime != null "> and create_time = #{createTime}</if>
            <if test="createUser != null "> and create_user = #{createUser}</if>
            <if test="createDept != null "> and create_dept = #{createDept}</if>
            <if test="updateUser != null "> and update_user = #{updateUser}</if>
            <if test="updateTime != null "> and update_time = #{updateTime}</if>
            <if test="status != null "> and status = #{status}</if>
            <if test="isDeleted != null "> and is_deleted = #{isDeleted}</if>
            <if test="startTime != null "> and start_time = #{startTime}</if>
            <if test="endTime != null "> and end_time = #{endTime}</if>
        </where>
    </select>
</mapper>
src/main/java/org/springblade/modules/property/service/IPropertyChargeRecordService.java
@@ -17,6 +17,7 @@
package org.springblade.modules.property.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springblade.core.mp.base.BaseService;
import org.springblade.modules.property.entity.PropertyCharge;
import org.springblade.modules.property.entity.PropertyChargeRecord;
@@ -29,7 +30,7 @@
 * @author BladeX
 * @since 2023-11-23
 */
public interface IPropertyChargeRecordService extends BaseService<PropertyChargeRecord> {
public interface IPropertyChargeRecordService extends IService<PropertyChargeRecord> {
    IPage<PropertyChargeRecordVO> getPage(IPage<PropertyChargeRecordVO> page, PropertyChargeRecordVO vo);
src/main/java/org/springblade/modules/property/service/impl/PropertyChargeRecordServiceImpl.java
@@ -1,6 +1,7 @@
package org.springblade.modules.property.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.modules.property.entity.PropertyCharge;
import org.springblade.modules.property.entity.PropertyChargeRecord;
@@ -13,7 +14,7 @@
import org.springframework.stereotype.Service;
@Service
public class PropertyChargeRecordServiceImpl extends BaseServiceImpl<PropertyChargeRecordMapper, PropertyChargeRecord> implements IPropertyChargeRecordService {
public class PropertyChargeRecordServiceImpl extends ServiceImpl<PropertyChargeRecordMapper, PropertyChargeRecord> implements IPropertyChargeRecordService {
    @Override
    public IPage<PropertyChargeRecordVO> getPage(IPage<PropertyChargeRecordVO> page, PropertyChargeRecordVO vo) {
        return page.setRecords(baseMapper.getPage(page, vo));
src/main/java/org/springblade/modules/system/entity/User.java
@@ -104,6 +104,10 @@
    @TableField("lat")
    private String lat;
    @ApiModelProperty(value = "小程序openid", example = "")
    @TableField("mini_open_id")
    private String miniOpenId;
}
src/main/java/org/springblade/modules/wechat/controller/WechatController.java
New file
@@ -0,0 +1,33 @@
package org.springblade.modules.wechat.controller;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springblade.core.tool.api.R;
import org.springblade.modules.pay.entity.WeChatMiniAuthorizeVo;
import org.springblade.modules.wechat.service.WechatService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@Slf4j
@RestController
@RequestMapping("blade-wechat/wechat/")
@Api(tags = "用户 -- 登录注册")
public class WechatController {
    @Resource
    private WechatService wechatService;
    @ApiOperation(value = "微信小程序授权")
    @RequestMapping(value = "/getOpenId", method = RequestMethod.GET)
    public R<WeChatMiniAuthorizeVo> wechatRoutineLogin(String code) throws Exception {
        return R.data(wechatService.miniAuthCode(code));
    }
}
src/main/java/org/springblade/modules/wechat/service/WechatService.java
New file
@@ -0,0 +1,12 @@
package org.springblade.modules.wechat.service;
import org.springblade.modules.pay.entity.WeChatMiniAuthorizeVo;
public interface WechatService {
    /**
     * 小程序登录凭证校验
     *
     * @return 小程序登录校验对象
     */
    WeChatMiniAuthorizeVo miniAuthCode(String code) throws Exception;
}
src/main/java/org/springblade/modules/wechat/service/impl/WechatServiceImpl.java
New file
@@ -0,0 +1,59 @@
package org.springblade.modules.wechat.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.common.config.WxMiniConfig;
import org.springblade.common.utils.HttpClientUtils;
import org.springblade.common.utils.SpringUtils;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.modules.pay.entity.WeChatMiniAuthorizeVo;
import org.springblade.modules.system.entity.User;
import org.springblade.modules.system.service.IUserService;
import org.springblade.modules.wechat.service.WechatService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class WechatServiceImpl implements WechatService {
    private static final Logger logger = LoggerFactory.getLogger(WechatServiceImpl.class);
    @Resource
    private WxMiniConfig wxMiniConfig;
    /**
     * 小程序登录凭证校验
     *
     * @return 小程序登录校验对象
     */
    @Override
    public WeChatMiniAuthorizeVo miniAuthCode(String code) throws Exception {
        String url = StrUtil.format(wxMiniConfig.getJscode2sessionUrl(), wxMiniConfig.getAppid(), wxMiniConfig.getSecret(), code);
        JSONObject data = JSON.parseObject(HttpClientUtils.doGet(url));
        if (ObjectUtil.isNull(data)) {
            logger.error("微信平台接口异常,没任何数据返回!");
            throw new Exception("微信平台接口异常,没任何数据返回!");
        }
        if (data.containsKey("errcode") && !data.getString("errcode").equals("0")) {
            if (data.containsKey("errmsg")) {
                logger.error("微信接口调用失败:" + data.getString("errcode") + data.getString("errmsg"));
                throw new Exception("微信接口调用失败:" + data.getString("errcode") + data.getString("errmsg"));
            }
        }
        WeChatMiniAuthorizeVo weChatMiniAuthorizeVo = JSONObject.parseObject(data.toJSONString(), WeChatMiniAuthorizeVo.class);
        // 绑定用户openid
        Long userId = AuthUtil.getUserId();
        IUserService userService = SpringUtils.getBean(IUserService.class);
        userService.update(Wrappers.<User>lambdaUpdate().set(User::getMiniOpenId, weChatMiniAuthorizeVo.getOpenId()).eq(User::getId, userId));
        return weChatMiniAuthorizeVo;
    }
}
src/main/resources/application.yml
@@ -316,3 +316,18 @@
  appId: 202403288666059313538996533
  privateKey: MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCCL4tz9BbUz+z3vs/eFwmUmQhsTiP3ZLwruTMJuEhsTEsT58PrdUOKGipwhKF88zj8SCk/Tn8izvDXj5IzqHEWjSEkfivYHx/6cE3VdHc6vvpvpeC06264d5NN4T6hwYH9CYz9iSmx9HFxADlg5zxj0lHqLSFf25cmEPF3F7PvAzj1cmYrKjet1HtCKaAyKADD+ojHHRjNLXZg3E5CINUqRYlLi+OKQph3VdrfokkO8efe8CR2wdiGU78mV1/UtTaiSYOEwv7pkbGECkqIXp+xYzaAVNCxikIWkc+qKmvvJXKsQVOKqv5b7/FQ/ag8trWPe8JLcQCN/Zg7DXSTc9YHAgMBAAECggEAENI73punZ5AGOmy5RyUK49xyLixakVi+x5eCdLsPAI9LFCNaOU5Jj8qZuTgp9Y8nydE5IyjtXH4IuJGgfnRBSHyrz59N4vsCksV4CSCnVzvd+Bci1b2l7MfmjKWSBiyOszdMtGXIsWm4TrWMtGysbOl8FBoIue4RtaVyzcSOR0+gDCIkn2XGqh3FhSIIApYm+eTOPDLZuSOsrgoTrppTOVUCXGN+/cSBAegWQnCES0b49F2EIRCAtSybc52N7zFPB9f8Kzzmgozilf00uS27HLZplwyLA6FMargdOi619x3T/XY52a55r4ldu/aZF2EbD1RpvMi9aGH/O77HtmLfAQKBgQC/32H0ibR5BgqfUjOWS0v+GRqG0gXr4x2R/oY9Pym16F5gEFKDYauBfEK4GYDtTEahB0Wo0n57StSZ3fX6yy9uBsPxywy0yUvzkxwe2th8fBg5czEBbLzHz4RMqo7LkfwKBatKMJLCOLdecSV2uJlDt/AIRFx5ymypiVLyYHb4IQKBgQCtsjv0ThHMM861CEfe9FZ2B4LFGfCptcek95ilARc7r3RpZpSSi+K0mF2992JVen+dkPh3FuG5NM5VSJeFyReNj5RxVz8A+4d9w3fxrqi2qfC1TFHR7IRv4JzPeZ+7wkMSc7rwkJ9UBSOdecXm2FV7BOivtnf7bGWIGLJNUb7pJwKBgCu0ZkudUk5+4cyInNePLzAh57jt/gtNIF8w6sLosEePNnALR8BFbxt/gnMM3CExTqU/Us6xo73RvbuOo4kM3zDguaFJ7wr/Dx3cVHtyvf0dByeO5Yq9igK5chlEgGI+Tnq579pmVgeHg70vaKkyQzb5nuNDLw1O3pF/hjVPY8oBAoGAFLW5nElqqdRLgx43o4eyf2Qp3WJYZYO9CBhVZOcaZA1pbd/DOy3em/XGx42lddejO6A7X8ekpjZ7m4OKPXNakZBa/KCQbIeRAhe6R2Cx0QW2I4e0AbmXmNdJGxu/Rd3FrX+K/H/h31y1gGMyi3Gjz4UoUTsKVzn3aSC4satbfeUCgYEAmpIo2ZO4hQ9THCOdgR5udXRG5s5JjGEct2QSxXntRyJm3RAkPA3/mEDVlI6X5UNIn7BKeThCFo5eM2bbFFF4sFeNV0W4uP8p9dsOnpwU7FzT++16WNiBopQpff72MNzjnt/k/KLlX/zV60zbMJ1R6McYsv0LP9Zuv9RAo8v8ZDU=
  sopCreateBy: 106575361457
wxpay:
  api-v3-key: qqwuMJcCPRhcNcmFlqb6ucxdHKqVxp9U
  appid: wxeeab25f4f176f1fc
  domain: https://api.mch.weixin.qq.com
  mch-id: 1600701071
  mch-serial-no: 4C19EAFA9E0AD598DDE3D7399B47C3D5C85EB7C9
  notify-domain: https://wx.nkjj88.com
  partnerKey: qqwuMJcCPRhcNcmFlqb6ucxdHKqVxp9U
  private-key-path: /www/wwwroot/apiclient_key.pem
  private-cert-path: /www/wwwroot/apiclient_cert.p12
wxmini:
  appid: wxeeab25f4f176f1fc
  secret: 0a0e0d0a0c0b0a09080706050403020100
  jscode2sessionUrl: https://api.weixin.qq.com/sns/jscode2session?appid={}&secret={}&js_code={}&grant_type=authorization_code