yanderong
2026-06-04 e8e076edd31310b5f00cef45a560d485d0b4e3fb
feat:司机APP充值对接微信支付
5个文件已添加
2个文件已修改
419 ■■■■■ 已修改文件
DriverOKTravel/guns-admin/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/pay/WechatPayConfig.java 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/properties/WxPayProperties.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/api/PayController.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/service/impl/PayServiceImpl.java 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/vo/WxPayAppVO.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DriverOKTravel/guns-admin/src/main/resources/application.yml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
DriverOKTravel/guns-admin/pom.xml
@@ -185,6 +185,13 @@
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.github.wechatpay-apiv3</groupId>
            <artifactId>wechatpay-java</artifactId>
            <version>0.2.17</version>
        </dependency>
    </dependencies>
    <build>
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/pay/WechatPayConfig.java
New file
@@ -0,0 +1,57 @@
package com.stylefeng.guns.config.pay;
import com.stylefeng.guns.config.properties.WxPayProperties;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.app.AppService;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StreamUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
/**
 * 微信支付配置
 *
 * @author Lux
 * @version 1.0.0
 * @since 2026/6/3 14:23
 */
@Configuration
@RequiredArgsConstructor
public class WechatPayConfig {
    private final WxPayProperties properties;
    private final ResourceLoader resourceLoader;
    @Bean
    public Config wxPayConfig() throws IOException {
        // 1. 读取 classpath 下的私钥文件内容(兼容 classpath: 前缀)
        Resource resource = resourceLoader.getResource(properties.getPrivateKeyPath());
        String privateKey;
        try (InputStream inputStream = resource.getInputStream()) {
            // 使用 Spring 的 StreamUtils 将 InputStream 所有字节读取到 byte[],兼容 Java 8
            byte[] keyBytes = StreamUtils.copyToByteArray(inputStream);
            privateKey = new String(keyBytes, StandardCharsets.UTF_8);
        }
        // 使用自动更新平台证书的RSA配置(从classpath读取私钥文件)
        return new RSAAutoCertificateConfig.Builder()
                        .merchantId(properties.getMchId())
                        .privateKey(privateKey)
                        .merchantSerialNumber(properties.getMerchantSerialNumber())
                        .apiV3Key(properties.getApiV3Key())
                        .build();
    }
    @Bean
    public AppService appService(Config wxPayConfig) {
        return new AppService.Builder()
                .config(wxPayConfig)
                .build();
    }
}
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/properties/WxPayProperties.java
New file
@@ -0,0 +1,30 @@
package com.stylefeng.guns.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
 * 微信支付配置信息
 *
 * @author Lux
 * @version 1.0.0
 * @since 2026/6/3 17:42
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "wxpay")
public class WxPayProperties {
    private String mchId;
    private String appId;
    private String apiV3Key;
    private String privateKeyPath;
    private String merchantSerialNumber;
    private String notifyUrl;
}
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/api/PayController.java
New file
@@ -0,0 +1,76 @@
package com.stylefeng.guns.modular.api;
import com.alibaba.fastjson.JSON;
import com.stylefeng.guns.modular.system.service.IDriverService;
import com.stylefeng.guns.modular.system.service.IPayService;
import com.stylefeng.guns.modular.system.util.ResultUtil;
import com.stylefeng.guns.modular.vo.WxPayAppVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Objects;
/**
 * 支付控制器
 *
 * @author Lux
 * @version 1.0.0
 * @since 2026/6/3 14:49
 */
@Slf4j
@Controller
@RequestMapping("/api/pay")
@Api(tags = "龙江易支付")
@RequiredArgsConstructor
public class PayController {
    private final IPayService payService;
    private final IDriverService driverService;
    @ResponseBody
    @PostMapping("/wechat/recharge")
    @ApiOperation(value = "微信充值操作")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "Authorization", value = "Bearer +token", required = true, dataType = "String", paramType = "header", defaultValue = "Bearer eyJhbGciOiJIUzUxMiJ9....."),
            @ApiImplicitParam(value = "充值金额", name = "money", required = true, dataType = "double"),
    })
    public ResultUtil wechatRecharge(BigDecimal money, HttpServletRequest request) throws Exception {
        Integer uid = driverService.getUserIdFormRedis(request);
        log.info("司机【{}】-微信充值【{}】-下单开始", uid, money);
        if (Objects.isNull(uid)) {
            return ResultUtil.tokenErr();
        }
        if(Objects.isNull(money)) {
            return ResultUtil.paranErr("充值金额不能为空");
        }
        WxPayAppVO wxPayAppVO = payService.wechatRecharge(uid, money);
        log.info("司机【{}】-微信充值-开下结束,返回前端数据为:{}", uid, JSON.toJSONString(wxPayAppVO));
        return ResultUtil.success(wxPayAppVO);
    }
    /**
     * 司机端充值余额 微信回调
     */
    @ResponseBody
    @PostMapping("/wechat/notify")
    public Map<String,Object> wechatNotify(HttpServletRequest request) {
        try {
            return payService.wechatNotify(request);
        } catch (Exception e) {
            log.error("司机余额充值-微信回调异常:", e);
        }
        return null;
    }
}
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/service/impl/PayServiceImpl.java
New file
@@ -0,0 +1,208 @@
package com.stylefeng.guns.modular.system.service.impl;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.stylefeng.guns.config.properties.WxPayProperties;
import com.stylefeng.guns.modular.system.model.Driver;
import com.stylefeng.guns.modular.system.model.TransactionDetails;
import com.stylefeng.guns.modular.system.service.IDriverService;
import com.stylefeng.guns.modular.system.service.IPayService;
import com.stylefeng.guns.modular.system.service.ITransactionDetailsService;
import com.stylefeng.guns.modular.system.util.RedisUtil;
import com.stylefeng.guns.modular.system.util.UUIDUtil;
import com.stylefeng.guns.modular.vo.WxPayAppVO;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.cipher.Signer;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.app.model.Transaction;
import com.wechat.pay.java.service.payments.app.AppService;
import com.wechat.pay.java.service.payments.app.model.Amount;
import com.wechat.pay.java.service.payments.app.model.PrepayRequest;
import com.wechat.pay.java.service.payments.app.model.PrepayResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
/**
 * 支付服务
 *
 * @author Lux
 * @version 1.0.0
 * @since 2026/6/3 15:25
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class PayServiceImpl implements IPayService {
    private final WxPayProperties properties;
    private final Config wxPayConfig;
    private final AppService appService;
    private final ITransactionDetailsService transactionDetailsService;
    private final IDriverService driverService;
    private final RedisUtil redisUtil;
    /**
     * 微信充值
     *
     * @param userId 充值用户
     * @param money 充值金额
     */
    @Transactional
    public WxPayAppVO wechatRecharge(Integer userId, BigDecimal money) throws Exception {
        PrepayRequest prepayRequest = buildPrepayRequest(money);
        // 1. 调用微信API下单
        PrepayResponse response = appService.prepay(prepayRequest);
        String prepayId = response.getPrepayId();
        if(StringUtils.isEmpty(prepayId)) {
            return null;
        }
        // 2. 保存数据 新增扣除使用费记录
        TransactionDetails transactionDetails = new TransactionDetails();
        transactionDetails.setCode(prepayRequest.getOutTradeNo());
        transactionDetails.setUserId(userId);
        transactionDetails.setInsertTime(new Date());
        transactionDetails.setRemark("充值");
        transactionDetails.setMoney(money.doubleValue());  // 将money转化为double类型
        transactionDetails.setState(1);
        transactionDetails.setType(1);
        transactionDetails.setUserType(2);
        transactionDetails.setOrderType(5); // 余额充值
        transactionDetailsService.insert(transactionDetails);
        // 3. 对 App 拉起支付所需的参数进行二次签名
        return buildAppPayParams(prepayId);
    }
    /**
     * 微信支付成功,回调服务
     */
    @Transactional
    public Map<String,Object> wechatNotify(HttpServletRequest request) throws IOException {
        String body = new BufferedReader(request.getReader()).lines().collect(Collectors.joining("\n"));
        RequestParam requestParam = new RequestParam.Builder()
                        .serialNumber(request.getHeader("Wechatpay-Serial"))
                        .nonce(request.getHeader("Wechatpay-Nonce"))
                        .signature(request.getHeader("Wechatpay-Signature"))
                        .timestamp(request.getHeader("Wechatpay-Timestamp"))
                        .body(body)
                        .build();
        log.info("司机充值-回调业务处理开始-回调参数为:{}, body为:{}", requestParam, body);
        NotificationConfig notificationConfig = (NotificationConfig) wxPayConfig;
        NotificationParser notificationParser = new NotificationParser(notificationConfig);
        Transaction transaction = notificationParser.parse(requestParam, Transaction.class);
        String outTradeNo = transaction.getOutTradeNo(); // 商户订单号
        String transactionId = transaction.getTransactionId(); // 微信订单唯一标识
        // 幂等处理
        String existTransactionId = redisUtil.getValue(outTradeNo);
        if(StringUtils.isNotEmpty(existTransactionId) && existTransactionId.equals(transactionId)) { // 已经存在改条数据的处理则直接返回
            return notifyResult();
        } else {
            redisUtil.setStrValue(outTradeNo, transactionId, 60 * 60);  //设置60分钟过期
        }
        // 支付成功
        if (Transaction.TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
            TransactionDetails transactionDetails = transactionDetailsService.selectOne(new EntityWrapper<TransactionDetails>().eq("code", outTradeNo));
            if (Objects.nonNull(transactionDetails)) {
                if (transactionDetails.getPayState()==2) { // 已支付
                    return notifyResult();
                }
                Driver driver = driverService.selectById(transactionDetails.getUserId());
                if (Objects.nonNull(driver)) {
                    driver.setBalance(driver.getBalance() + transactionDetails.getMoney());
                    driverService.updateById(driver);
                }
                transactionDetails.setPayState(2);
                transactionDetailsService.updateById(transactionDetails);
            }
        }
        redisUtil.remove(outTradeNo); // 处理完逻辑删除
        log.info("司机充值-回调业务处理结束");
        return notifyResult();
    }
    private Map<String,Object> notifyResult() {
        Map<String,Object> result = new HashMap<>();
        result.put("code","SUCCESS");
        result.put("message","成功");
        return result;
    }
    /**
     * 为 App 端生成拉起微信支付所需的全部参数
     * @param prepayId 从下单接口获得的预支付ID
     * @return Map<String, String> 包含 appId、partnerId、prepayId、nonceStr、timeStamp、package、sign 等
     */
    public WxPayAppVO buildAppPayParams(String prepayId) throws Exception {
        // 生成随机字符串(32位以内)
        String nonceStr = generateOrderNo();
        // 当前秒级时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        // 构建待签名的字符串(严格按照顺序拼接,每行以\n结尾); 格式:Appid\n时间戳\n随机字符串\n预支付交易会话标识\n
        String messageStr = String.format("%s\n%s\n%s\n%s\n",
                properties.getAppId(),               // 移动应用的AppID
                timestamp,          // 时间戳,秒级
                nonceStr,           // 随机字符串
                prepayId            // 预支付ID
        );
        // 使用商户API证书私钥进行签名(RSASSA-PKCS1-v1_5 with SHA256)
        Signer signer = wxPayConfig.createSigner();
        String sign = signer.sign(messageStr).getSign();
        // 最终返回给App的参数
        return WxPayAppVO.builder()
                .appId(properties.getAppId())
                .partnerId(properties.getMchId()) // 注意这里是 partnerid
                .prepayId(prepayId)
                .packageValue("Sign=WXPay") // 固定值
                .nonceStr(nonceStr)
                .timeStamp(String.valueOf(timestamp))
                .sign(sign)
                .build();
    }
    /**
     * 创建商户系统内部订单,生成唯一订单号
     */
    private String generateOrderNo() throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return sdf.format(new Date()) + UUIDUtil.getRandomCode(3);
    }
    private PrepayRequest buildPrepayRequest(BigDecimal money) throws Exception {
        // 1、创建商户系统内部订单,生成唯一订单号,需要写入数据库待支付
        String outTradeNo =  generateOrderNo();
        // 2、构建微信支付下单请求
        int payAmount = money.multiply(new BigDecimal("100")).intValue(); // 充值金额元转位分
        PrepayRequest prepayRequest = new PrepayRequest();
        prepayRequest.setAppid(properties.getAppId());
        prepayRequest.setMchid(properties.getMchId());
        prepayRequest.setDescription("用户充值");          // 商品描述,不超过127个字符
        prepayRequest.setOutTradeNo(outTradeNo);          // 商户订单号
        prepayRequest.setNotifyUrl(properties.getNotifyUrl());            // 回调地址
        Amount amount = new Amount();
        amount.setTotal(payAmount); // 金额:单位分(整数)
        amount.setCurrency("CNY"); // 币种
        prepayRequest.setAmount(amount);
        return prepayRequest;
    }
}
DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/vo/WxPayAppVO.java
New file
@@ -0,0 +1,30 @@
package com.stylefeng.guns.modular.vo;
import lombok.Builder;
import lombok.Data;
/**
 *  微信支付APP掉支付参数
 *
 * @author Lux
 * @version 1.0.0
 * @since 2026/6/3 18:37
 */
@Data
@Builder
public class WxPayAppVO {
    private String appId;
    private String partnerId;
    private String prepayId;
    private String timeStamp;
    private String nonceStr;
    private String packageValue;
    private String sign;
}
DriverOKTravel/guns-admin/src/main/resources/application.yml
@@ -113,4 +113,13 @@
---
#交通部推送数据功能开关
pushMinistryOfTransport: false
pushMinistryOfTransport: false
---
wxpay:
  mchId: 1699677534 # 微信支付商户号
  appId: wx64683135451e4c0d # 开放平台移动应用的AppID
  apiV3Key: GE519nBbA7XkjfND9dhaxztHF1SKndzW # APIv3密钥
  privateKeyPath: classpath:apiclient_key.pem # 私钥文件路径
  merchantSerialNumber: 74EEE9D1118435740621F2C7C5B4861F32968422 # 商户证书序列号
  notifyUrl: http://221.182.45.100:7777/driver/api/pay/wechat/notify  # 支付回调地址