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