| New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |