yanderong
2026-06-09 b19474186df25762866bd9323c3f3ac8be4de9b9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package com.stylefeng.guns.modular.system.service.impl;
 
import com.alibaba.fastjson.JSON;
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.beans.factory.annotation.Value;
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;
 
    @Value("${callbackPath}")
    private String callbackPath;  //支付回调网关地址
 
    /**
     * 微信充值
     *
     * @param userId 充值用户
     * @param money 充值金额,单位为元
     */
    @Transactional
    public WxPayAppVO wechatRecharge(Integer userId, BigDecimal money) throws Exception {
 
        PrepayRequest prepayRequest = buildPrepayRequest(money);
        log.info("司机充值-微信支付下单-请求数据为:{}", JSON.toJSONString(prepayRequest));
        // 1. 调用微信API下单
        PrepayResponse response = appService.prepay(prepayRequest);
        log.info("司机充值-微信支付下单-返回数据为:{}", JSON.toJSONString(response));
        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(callbackPath+properties.getNotifyUrl());            // 回调地址
        Amount amount = new Amount();
        amount.setTotal(payAmount); // 金额:单位分(整数)
        amount.setCurrency("CNY"); // 币种
        prepayRequest.setAmount(amount);
        return prepayRequest;
    }
 
}