From e8e076edd31310b5f00cef45a560d485d0b4e3fb Mon Sep 17 00:00:00 2001
From: yanderong <307678080@qq.com>
Date: 星期四, 04 六月 2026 16:26:09 +0800
Subject: [PATCH] feat:司机APP充值对接微信支付

---
 DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/api/PayController.java                  |   76 ++++++++++
 DriverOKTravel/guns-admin/pom.xml                                                                          |    7 +
 DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/properties/WxPayProperties.java          |   30 ++++
 DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/service/impl/PayServiceImpl.java |  208 +++++++++++++++++++++++++++++
 DriverOKTravel/guns-admin/src/main/resources/application.yml                                               |   11 +
 DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/pay/WechatPayConfig.java                 |   57 ++++++++
 DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/vo/WxPayAppVO.java                      |   30 ++++
 7 files changed, 418 insertions(+), 1 deletions(-)

diff --git a/DriverOKTravel/guns-admin/pom.xml b/DriverOKTravel/guns-admin/pom.xml
index 7889809..154e3f1 100644
--- a/DriverOKTravel/guns-admin/pom.xml
+++ b/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>
diff --git a/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/pay/WechatPayConfig.java b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/pay/WechatPayConfig.java
new file mode 100644
index 0000000..8f74096
--- /dev/null
+++ b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/pay/WechatPayConfig.java
@@ -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();
+    }
+}
diff --git a/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/properties/WxPayProperties.java b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/properties/WxPayProperties.java
new file mode 100644
index 0000000..ad66f62
--- /dev/null
+++ b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/config/properties/WxPayProperties.java
@@ -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;
+}
diff --git a/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/api/PayController.java b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/api/PayController.java
new file mode 100644
index 0000000..23e3ce9
--- /dev/null
+++ b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/api/PayController.java
@@ -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;
+    }
+}
diff --git a/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/service/impl/PayServiceImpl.java b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/service/impl/PayServiceImpl.java
new file mode 100644
index 0000000..79495f7
--- /dev/null
+++ b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/system/service/impl/PayServiceImpl.java
@@ -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;
+    }
+
+}
diff --git a/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/vo/WxPayAppVO.java b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/vo/WxPayAppVO.java
new file mode 100644
index 0000000..cebe6ca
--- /dev/null
+++ b/DriverOKTravel/guns-admin/src/main/java/com/stylefeng/guns/modular/vo/WxPayAppVO.java
@@ -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;
+}
diff --git a/DriverOKTravel/guns-admin/src/main/resources/application.yml b/DriverOKTravel/guns-admin/src/main/resources/application.yml
index a90843d..e26d728 100644
--- a/DriverOKTravel/guns-admin/src/main/resources/application.yml
+++ b/DriverOKTravel/guns-admin/src/main/resources/application.yml
@@ -113,4 +113,13 @@
 
 ---
 #交通部推送数据功能开关
-pushMinistryOfTransport: false
\ No newline at end of file
+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  # 支付回调地址
\ No newline at end of file

--
Gitblit v1.7.1