支付与支付回调修改

This commit is contained in:
hanrenchun 2025-12-03 08:49:50 +08:00
parent 9332621f5a
commit d467ca1d89
16 changed files with 584 additions and 291 deletions

View File

@ -54,5 +54,16 @@
<version>2.3.0</version> <version>2.3.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<version>3.5.1</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -2,6 +2,11 @@ package com.ruoyi.database.controller;
import com.alipay.api.internal.util.AlipaySignature; import com.alipay.api.internal.util.AlipaySignature;
import com.ruoyi.config.AlipayRYConfig; import com.ruoyi.config.AlipayRYConfig;
import com.ruoyi.database.domain.ParkingBillInfo;
import com.ruoyi.database.domain.ParkingBillPaymentInfo;
import com.ruoyi.database.service.ParkingBillInfoService;
import com.ruoyi.database.service.ParkingBillPaymentInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -13,8 +18,15 @@ import java.util.Map;
public class AlipayNotifyController { public class AlipayNotifyController {
private final AlipayRYConfig alipayConfig; private final AlipayRYConfig alipayConfig;
// 假设你有一个处理订单业务的服务
// private final OrderService orderService;
@Autowired
private ParkingBillPaymentInfoService parkingBillPaymentInfoService;
@Autowired
private ParkingBillInfoService parkingBillInfoService;
public AlipayNotifyController(AlipayRYConfig alipayConfig) { public AlipayNotifyController(AlipayRYConfig alipayConfig) {
this.alipayConfig = alipayConfig; this.alipayConfig = alipayConfig;
@ -53,27 +65,39 @@ public class AlipayNotifyController {
String tradeStatus = params.get("trade_status"); String tradeStatus = params.get("trade_status");
String outTradeNo = params.get("out_trade_no"); // 你的商户订单号 String outTradeNo = params.get("out_trade_no"); // 你的商户订单号
String tradeNo = params.get("trade_no"); // 支付宝交易号 String tradeNo = params.get("trade_no"); // 支付宝交易号
String gmtPayment = params.get("gmt_payment"); // 支付宝交易号
// 4. 判断交易状态 // 4. 判断交易状态
if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISH".equals(tradeStatus)) { if ("TRADE_SUCCESS".equals(tradeStatus) || "TRADE_FINISH".equals(tradeStatus)) {
// 支付成功 // 支付成功
ParkingBillPaymentInfo one = parkingBillPaymentInfoService.lambdaQuery()
// 4.1 幂等性处理查询本地订单是否已处理避免重复通知导致业务重复执行[citation:5] .eq(ParkingBillPaymentInfo::getOrderId, outTradeNo)
// if (orderService.isOrderPaid(outTradeNo)) { .last("limit 1")
// return "success"; .one();
// } if (one == null) {
return "failure";
// 4.2 校验金额重要防止金额被篡改[citation:5] }
// String totalAmount = params.get("total_amount"); one.setPayStatus(1);
// if (!orderService.verifyOrderAmount(outTradeNo, totalAmount)) { one.setPayTime(gmtPayment);
// return "failure"; one.setTransactionId(tradeNo);
// } boolean b = parkingBillPaymentInfoService.updateById(one);
if (!b){
// 4.3 更新本地订单状态为已支付 return "failure";
// orderService.updateOrderToPaid(outTradeNo, tradeNo); }
ParkingBillInfo parkingBillInfo = parkingBillInfoService.lambdaQuery()
.eq(ParkingBillInfo::getBillCode, one.getBillCode())
.last("limit 1")
.one();
if (parkingBillInfo == null) {
return "failure";
}
parkingBillInfo.setIsPay(1);
boolean b1 = parkingBillInfoService.updateById(parkingBillInfo);
if (!b1) {
return "failure";
}
System.out.println("订单支付成功: " + outTradeNo + ", 支付宝交易号: " + tradeNo); System.out.println("订单支付成功: " + outTradeNo + ", 支付宝交易号: " + tradeNo);
} }
// 5. 处理成功返回 'success' 字符串给支付宝支付宝将停止重复通知[citation:5] // 5. 处理成功返回 'success' 字符串给支付宝支付宝将停止重复通知[citation:5]
return "success"; return "success";
} catch (Exception e) { } catch (Exception e) {

View File

@ -1,54 +0,0 @@
package com.ruoyi.database.controller;
import com.ruoyi.database.domain.Order;
import com.ruoyi.database.domain.PayFees;
import com.ruoyi.database.service.OrderService;
import com.ruoyi.database.service.WechatMiniProgramPayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@Autowired
private WechatMiniProgramPayService wechatPayService;
@Autowired
private OrderService orderService;
/**
* 创建支付订单
*/
@PostMapping("/create")
public Map<String, Object> createPayment(@RequestBody PayFees payFees,
HttpServletRequest request) {
try {
// 1. 创建业务订单
orderService.createOrder(payFees);
// 2. 获取客户端IP
String clientIp = wechatPayService.getClientIpAddress(request);
// 3. 创建支付订单
// Map<String, Object> result = wechatPayService.createJsapiOrder(
// openid, orderId, amount, description, clientIp);
// return result;
} catch (Exception e) {
// System.err.println("创建支付订单异常: " + e.getMessage());
// e.printStackTrace();
//
// Map<String, Object> result = new HashMap<>();
// result.put("success", false);
// result.put("message", "系统异常: " + e.getMessage());
// return result;
}
return null;
}
}

View File

@ -1,35 +1,30 @@
package com.ruoyi.database.controller; package com.ruoyi.database.controller;
import cn.hutool.core.util.ReUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.model.LoginUserByPhone; import com.ruoyi.common.core.domain.model.LoginUserByPhone;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.bean.BeanUtils; import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.database.domain.*; import com.ruoyi.database.domain.*;
import com.ruoyi.database.mapper.ParkingBillInfoMapper; import com.ruoyi.database.domain.vo.ParkingBillInfoVO;
import com.ruoyi.database.service.CustomerPlateNoInfoService; import com.ruoyi.database.enums.PayType;
import com.ruoyi.database.service.ParkingBillInfoService; import com.ruoyi.database.service.*;
import com.ruoyi.database.service.ParkingBillPaymentInfoService;
import com.ruoyi.database.util.HaiKangApiUtils;
import com.ruoyi.database.util.LicensePlateValidator; import com.ruoyi.database.util.LicensePlateValidator;
import com.ruoyi.framework.web.service.TokenService; import com.ruoyi.framework.web.service.TokenService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Api(tags = "小程序接口") @Api(tags = "小程序接口")
@ -39,39 +34,30 @@ import java.util.stream.Collectors;
public class SmallProgramController extends BaseController { public class SmallProgramController extends BaseController {
private final ParkingBillInfoService parkingBillInfoService; private final ParkingBillInfoService parkingBillInfoService;
private final AlipayService alipayService;
private final WechatPhoneService wechatPhoneService;
private final AlipayPhoneService alipayPhoneService;
private final WechatMiniProgramPayService wechatMiniProgramPayService;
private final ParkingBillPaymentInfoService parkingBillPaymentInfoService; private final ParkingBillPaymentInfoService parkingBillPaymentInfoService;
private final CustomerPlateNoInfoService customerPlateNoInfoService; private final CustomerPlateNoInfoService customerPlateNoInfoService;
private final TokenService tokenService; private final TokenService tokenService;
private final HaiKangApiUtils haiKangApiUtils;
private final ObjectMapper objectMapper = new ObjectMapper();
@GetMapping("/getOrder") @GetMapping("/getOrder")
@ApiOperation("根据车牌号查询缴费订单") @ApiOperation("根据车牌号查询缴费订单")
@Transactional
public AjaxResult getOrder(String plateNo) { public AjaxResult getOrder(String plateNo) {
String parkingPaymentInfo = haiKangApiUtils.getParkingPaymentInfo(plateNo); ParkingBillInfo b = parkingBillInfoService.saveOrUpdateParkingBillInfo(plateNo);
if(parkingPaymentInfo==null){ if (b == null) {
return AjaxResult.error("未查询到停车信息"); return AjaxResult.error("未查询到订单");
} }else {
JSONObject jsonObject = new JSONObject(parkingPaymentInfo); ParkingBillInfoVO parkingBillInfoVO = new ParkingBillInfoVO();
String data = jsonObject.getJSONObject("data").toString(); BeanUtils.copyProperties(b, parkingBillInfoVO);
if(data==null){ parkingBillInfoVO.setTotalCostYuan(b.getTotalCostYuan().toString() + "");
return AjaxResult.error("未查询到停车信息"); parkingBillInfoVO.setPayMoneyYuan(b.getPayMoneyYuan().toString() + "");
} parkingBillInfoVO.setDeductMoneyYuan(b.getDeductMoneyYuan().toString() + "");
try { return AjaxResult.success(parkingBillInfoVO);
ParkingBill vehicleRecord = objectMapper.readValue(data, ParkingBill.class);
ParkingBillInfo parkingBillInfo = new ParkingBillInfo();
BeanUtils.copyProperties(vehicleRecord,parkingBillInfo);
parkingBillInfo.setPayMoneyYuan(vehicleRecord.getPayMoneyYuan());
parkingBillInfo.setDeductMoneyYuan(vehicleRecord.getDeductMoneyYuan());
parkingBillInfo.setTotalCostYuan(vehicleRecord.getTotalCostYuan());
LambdaUpdateWrapper<ParkingBillInfo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ParkingBillInfo::getBillCode,parkingBillInfo.getBillCode());
parkingBillInfoService.saveOrUpdate(parkingBillInfo,updateWrapper);
return AjaxResult.success(parkingBillInfo);
} catch (JsonProcessingException e) {
return AjaxResult.error("未查询到停车信息");
} }
} }
@ -109,8 +95,27 @@ public class SmallProgramController extends BaseController {
@ApiOperation("查询历史未缴费订单") @ApiOperation("查询历史未缴费订单")
public AjaxResult getHistoryNoPayOrder(HttpServletRequest request){ public AjaxResult getHistoryNoPayOrder(HttpServletRequest request){
LoginUserByPhone loginUserByPhone = tokenService.getLoginUserByPhone(request); LoginUserByPhone loginUserByPhone = tokenService.getLoginUserByPhone(request);
List<CustomerPlateNoInfo> list = customerPlateNoInfoService.lambdaQuery()
return AjaxResult.success(); .eq(CustomerPlateNoInfo::getPhone, loginUserByPhone.getPhone())
.list();
List<String> collect = list.stream().map(CustomerPlateNoInfo::getPlateNo).collect(Collectors.toList());
if (collect.isEmpty()){
return AjaxResult.success(collect);
}
List<ParkingBillInfo> list1 = parkingBillInfoService.lambdaQuery()
.in(ParkingBillInfo::getPlateNo, collect)
.eq(ParkingBillInfo::getIsPay, 0)
.list();
List<ParkingBillInfoVO> parkingBillInfoVOS = new ArrayList<>();
list1.forEach(b -> {
ParkingBillInfoVO parkingBillInfoVO = new ParkingBillInfoVO();
BeanUtils.copyProperties(b, parkingBillInfoVO);
parkingBillInfoVO.setTotalCostYuan(b.getTotalCostYuan().toString() + "");
parkingBillInfoVO.setPayMoneyYuan(b.getPayMoneyYuan().toString() + "");
parkingBillInfoVO.setDeductMoneyYuan(b.getDeductMoneyYuan().toString() + "");
parkingBillInfoVOS.add(parkingBillInfoVO);
});
return AjaxResult.success(parkingBillInfoVOS);
} }
@ -158,9 +163,110 @@ public class SmallProgramController extends BaseController {
@PostMapping("/payFees") @PostMapping("/payFees")
@ApiOperation("缴费") @ApiOperation("缴费")
@Transactional
public AjaxResult payFees(@RequestBody PayFees payFees,HttpServletRequest request){ public AjaxResult payFees(@RequestBody PayFees payFees,HttpServletRequest request){
LoginUserByPhone loginUserByPhone = tokenService.getLoginUserByPhone(request); LoginUserByPhone loginUserByPhone = tokenService.getLoginUserByPhone(request);
return AjaxResult.success();
ParkingBillInfo one = parkingBillInfoService.lambdaQuery()
.eq(ParkingBillInfo::getBillCode, payFees.getBillCode())
.last("limit 1")
.one();
if (one == null) {
return AjaxResult.error("订单查询失败");
}
one = parkingBillInfoService.saveOrUpdateParkingBillInfo(one.getPlateNo());
if (one == null) {
return AjaxResult.error("订单查询失败");
}
String generate = generate(loginUserByPhone.getPhone());
String openId = getOpenId(payFees);
String clientIp = wechatMiniProgramPayService.getClientIpAddress(request);
Map<String, Object> order = createOrder(payFees, generate, openId, one, clientIp);
ParkingBillPaymentInfo parkingBillPaymentInfo = new ParkingBillPaymentInfo();
parkingBillPaymentInfo.setBillCode(one.getBillCode());
parkingBillPaymentInfo.setPlateNo(one.getPlateNo());
parkingBillPaymentInfo.setPlateColor(one.getPlateColor());
parkingBillPaymentInfo.setOpenId(openId);
parkingBillPaymentInfo.setPayType(payFees.getPayType());
parkingBillPaymentInfo.setPhone(loginUserByPhone.getPhone());
parkingBillPaymentInfo.setPayStatus(0);
parkingBillPaymentInfo.setOrderId(generate);
boolean b = parkingBillPaymentInfoService.saveOrUpdate(parkingBillPaymentInfo);
if (b) {
return AjaxResult.success(order);
}else {
return AjaxResult.error("创建订单出错");
}
} }
/**
* 获取用户openid
* @param payFees
* @return
*/
private String getOpenId(PayFees payFees) {
PayType payType = PayType.getByCode(payFees.getPayType());
switch (payType) {
case WECHAT:
return wechatPhoneService.getOpenId(payFees.getCode());
case ALIPAY:
return alipayPhoneService.getAlipayUserId(payFees.getCode());
default:
throw new IllegalArgumentException("不支持的登录方式: " + payType);
}
}
/**
* 创建支付宝 微信 订单
* @param payFees
* @param orderId
* @param openId
* @param parkingBillInfo
* @param clientIp
* @return
*/
private Map<String, Object> createOrder(PayFees payFees,
String orderId,
String openId,
ParkingBillInfo parkingBillInfo,
String clientIp) {
PayType payType = PayType.getByCode(payFees.getPayType());
switch (payType) {
case WECHAT:
return wechatMiniProgramPayService.createJsapiOrder(
openId,orderId,parkingBillInfo.getPayMoney(),
"常客隆智慧停车缴费",clientIp);
case ALIPAY:
return alipayService.createOrder(orderId,
parkingBillInfo.getPayMoneyYuan().toString(),
"常客隆智慧停车缴费",openId);
default:
throw new IllegalArgumentException("不支持的登录方式: " + payType);
}
}
public static String generate(String phone) {
// 1. 参数校验
if (phone == null || phone.length() < 4) {
throw new IllegalArgumentException("Invalid phone number");
}
// 2. 获取手机尾号后4位更常见
String phoneSuffix = phone.substring(phone.length() - 4);
// 3. 使用更精确的时间戳
String timePart = Instant.now().toEpochMilli() + ""; // 毫秒时间戳
// 4. 增加更多随机性
int random = ThreadLocalRandom.current().nextInt(1000, 9999);
return phoneSuffix + timePart.substring(timePart.length() - 10) + random;
}
} }

View File

@ -3,7 +3,10 @@ package com.ruoyi.database.controller;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse; import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult; import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.service.WxPayService; import com.github.binarywang.wxpay.service.WxPayService;
import com.ruoyi.database.service.OrderService; import com.ruoyi.database.domain.ParkingBillInfo;
import com.ruoyi.database.domain.ParkingBillPaymentInfo;
import com.ruoyi.database.service.ParkingBillInfoService;
import com.ruoyi.database.service.ParkingBillPaymentInfoService;
import com.ruoyi.database.service.WechatMiniProgramPayService; import com.ruoyi.database.service.WechatMiniProgramPayService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -18,6 +21,12 @@ public class WxPayNotifyController {
@Autowired @Autowired
private WechatMiniProgramPayService wechatPayService; private WechatMiniProgramPayService wechatPayService;
@Autowired
private ParkingBillPaymentInfoService parkingBillPaymentInfoService;
@Autowired
private ParkingBillInfoService parkingBillInfoService;
/** /**
* 微信支付结果通知回调 * 微信支付结果通知回调
@ -43,25 +52,35 @@ public class WxPayNotifyController {
System.out.println("解析通知数据: orderId=" + orderId + System.out.println("解析通知数据: orderId=" + orderId +
", transactionId=" + transactionId + ", totalFee=" + totalFee); ", transactionId=" + transactionId + ", totalFee=" + totalFee);
ParkingBillPaymentInfo one = parkingBillPaymentInfoService.lambdaQuery()
.eq(ParkingBillPaymentInfo::getOrderId, orderId)
.last("limit 1")
.one();
if (one == null) {
return WxPayNotifyResponse.fail("处理失败");
}
one.setPayStatus(1);
one.setPayTime(timeEnd);
one.setTransactionId(transactionId);
boolean b = parkingBillPaymentInfoService.updateById(one);
if (!b){
return WxPayNotifyResponse.fail("处理失败");
}
// // 4. 幂等性处理检查订单是否已处理 ParkingBillInfo parkingBillInfo = parkingBillInfoService.lambdaQuery()
// if (orderService.isOrderPaid(orderId)) { .eq(ParkingBillInfo::getBillCode, one.getBillCode())
// System.out.println("订单已处理,直接返回成功"); .last("limit 1")
// return WxPayNotifyResponse.success("OK"); .one();
// } if (parkingBillInfo == null) {
// return WxPayNotifyResponse.fail("处理失败");
// // 5. 校验订单金额重要防止资金损失 }
// if (!orderService.verifyOrderAmount(orderId, totalFee)) { parkingBillInfo.setIsPay(1);
// System.out.println("订单金额校验失败"); boolean b1 = parkingBillInfoService.updateById(parkingBillInfo);
// return WxPayNotifyResponse.fail("订单金额不一致"); if (!b1){
// } return WxPayNotifyResponse.fail("处理失败");
// }
// // 6. 处理业务逻辑更新订单状态等
// orderService.updateOrderToPaid(orderId, transactionId);
System.out.println("支付通知处理成功"); System.out.println("支付通知处理成功");
System.out.println("========== 支付通知处理完成 =========="); System.out.println("========== 支付通知处理完成 ==========");
// 7. 返回成功响应必须返回success // 7. 返回成功响应必须返回success
return WxPayNotifyResponse.success("OK"); return WxPayNotifyResponse.success("OK");

View File

@ -1,49 +0,0 @@
package com.ruoyi.database.domain;
import com.ruoyi.database.enums.OrderStatus;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Order {
/**
* 商户订单号
*/
private String orderId;
/**
* 订单金额单位
*/
private Integer amount;
/**
* 订单描述
*/
private String description;
/**
* 用户openid
*/
private String openid;
/**
* 订单状态
*/
private OrderStatus status;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 支付时间
*/
private LocalDateTime payTime;
/**
* 微信支付订单号
*/
private String transactionId;
}

View File

@ -166,4 +166,8 @@ public class ParkingBillInfo {
private Date updateTime; private Date updateTime;
@ApiModelProperty("是否支付")
@Excel(name = "是否支付")
private Integer isPay;
} }

View File

@ -82,11 +82,11 @@ public class ParkingBillPaymentInfo {
private String openId; private String openId;
/** /**
* 支付宝用户ID * 支付方式
*/ */
@ApiModelProperty("支付宝用户ID") @ApiModelProperty("支付方式")
@Excel(name = "支付宝用户ID") @Excel(name = "支付方式")
private String alipayUserId; private String payType;
/** /**
* 手机号 * 手机号
@ -116,9 +116,17 @@ public class ParkingBillPaymentInfo {
*/ */
@ApiModelProperty("支付时间") @ApiModelProperty("支付时间")
@Excel(name = "支付时间") @Excel(name = "支付时间")
private Long payTime; private String payTime;
@TableField(exist = false) @TableField(exist = false)
private String payTimeCn; private String payTimeCn;
/**
* 商品订单id
*/
@ApiModelProperty("商品订单id")
@Excel(name = "商品订单id")
private String orderId;
} }

View File

@ -0,0 +1,96 @@
package com.ruoyi.database.domain.vo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.math.BigDecimal;
import java.util.Date;
/**
* 停车场账单表(ParkingBillInfo)实体类
*
* @author makejava
* @since 2025-12-01 15:37:06
*/
@Data
public class ParkingBillInfoVO {
/**
* 主键ID
*/
private Long id;
/**
* 账单编号
*/
private String billCode;
/**
* 停车场编码
*/
private String parkCode;
/**
* 停车场名称
*/
private String parkName;
/**
* 车牌号
*/
private String plateNo;
/**
* 车牌颜色
*/
private Integer plateColor;
private String plateColorCn;
/**
* 入场时间戳(毫秒)
*/
private Long enterTime;
private String enterTimeCn;
/**
* 离场时间戳(毫秒)
*/
private Long costTime;
private String costTimeCn;
/**
* 停车时长(分钟)
*/
private Integer parkPeriodTime;
private String totalCostYuan;
private String deductMoneyYuan;
private String payMoneyYuan;
private String inUnid;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}

View File

@ -82,9 +82,9 @@ public class AlipayPhoneService {
} }
/** /**
* 第一步使用授权码获取访问令牌 * 获取用户id
*/ */
private String getAccessToken(String authCode) { private String getOpenId(String authCode) {
try { try {
Map<String, String> params = buildBaseParams(); Map<String, String> params = buildBaseParams();
params.put("method", "alipay.system.oauth.token"); params.put("method", "alipay.system.oauth.token");
@ -113,7 +113,7 @@ public class AlipayPhoneService {
throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: 响应格式错误"); throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: 响应格式错误");
} }
String accessToken = (String) tokenResponse.get("access_token"); String accessToken = (String) tokenResponse.get("open_id");
if (accessToken == null || accessToken.trim().isEmpty()) { if (accessToken == null || accessToken.trim().isEmpty()) {
throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: access_token为空"); throw new BusinessException("ALIPAY_ACCESS_TOKEN_ERROR", "获取access_token失败: access_token为空");
} }
@ -127,59 +127,6 @@ public class AlipayPhoneService {
} }
} }
/**
* 第二步使用access_token获取用户信息包含手机号
*/
private String getUserPhoneNumber(String accessToken) {
try {
Map<String, String> params = buildBaseParams();
params.put("method", "alipay.user.info.share");
params.put("auth_token", accessToken);
log.info("获取用户信息请求参数: {}", params);
String sign = generateSign(params);
params.put("sign", sign);
String responseBody = callAlipayApiWithCharset(params);
log.info("获取用户信息响应: {}", responseBody);
Map<String, Object> responseMap = objectMapper.readValue(responseBody, Map.class);
Map<String, Object> userResponse = (Map<String, Object>) responseMap.get("alipay_user_info_share_response");
if (userResponse == null) {
Map<String, Object> errorResponse = (Map<String, Object>) responseMap.get("error_response");
if (errorResponse != null) {
String subCode = (String) errorResponse.get("sub_code");
String subMsg = (String) errorResponse.get("sub_msg");
throw new BusinessException("ALIPAY_USER_INFO_ERROR",
String.format("获取用户信息失败[%s]: %s", subCode, subMsg));
}
throw new BusinessException("ALIPAY_USER_INFO_ERROR", "获取用户信息失败: 响应格式错误");
}
// 提取手机号
String mobile = (String) userResponse.get("mobile");
if (mobile == null || mobile.trim().isEmpty()) {
// 有些用户可能没有绑定手机号
log.warn("用户未绑定手机号或手机号为空");
throw new BusinessException("ALIPAY_PHONE_EMPTY", "用户未绑定手机号");
}
// 验证手机号格式
if (!validatePhoneNumber(mobile)) {
log.warn("获取到的手机号格式可能不正确: {}", mobile);
}
log.info("成功获取用户手机号: {}", mobile);
return mobile;
} catch (Exception e) {
log.error("获取用户信息异常", e);
throw new BusinessException("ALIPAY_USER_INFO_ERROR", "获取用户信息异常: " + e.getMessage());
}
}
/** /**
* 修复后的API调用方法 - 正确处理charset参数 * 修复后的API调用方法 - 正确处理charset参数
*/ */

View File

@ -9,11 +9,15 @@ import com.alipay.api.response.AlipayTradeCreateResponse;
import com.ruoyi.config.AlipayRYConfig; import com.ruoyi.config.AlipayRYConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service @Service
public class AlipayService { public class AlipayService {
private final AlipayRYConfig alipayRYConfig; private final AlipayRYConfig alipayRYConfig;
public AlipayService(AlipayRYConfig alipayRYConfig) { public AlipayService(AlipayRYConfig alipayRYConfig) {
this.alipayRYConfig = alipayRYConfig; this.alipayRYConfig = alipayRYConfig;
} }
@ -27,7 +31,7 @@ public class AlipayService {
* @param buyerId 支付宝用户ID相当于OpenID * @param buyerId 支付宝用户ID相当于OpenID
* @return 支付订单字符串用于小程序前端调起支付 * @return 支付订单字符串用于小程序前端调起支付
*/ */
public String createOrder(String orderId, String amount, String subject, String buyerId) { public Map<String, Object> createOrder(String orderId, String amount, String subject, String buyerId) {
try { try {
AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig()); AlipayClient alipayClient = new DefaultAlipayClient(getAlipayConfig());
// 构造请求参数以调用接口 // 构造请求参数以调用接口
@ -54,7 +58,12 @@ public class AlipayService {
if (response.isSuccess()) { if (response.isSuccess()) {
System.out.println("支付宝预下单成功,交易号: " + response.getTradeNo()); System.out.println("支付宝预下单成功,交易号: " + response.getTradeNo());
// 返回给前端的订单字符串用于调起支付 // 返回给前端的订单字符串用于调起支付
return response.getOutTradeNo(); Map<String, Object> maps = new HashMap<>();
maps.put("success", response.isSuccess());
maps.put("tradeNO", response.getTradeNo());
maps.put("outTradeNo", response.getOutTradeNo());
return maps;
// return response.getOutTradeNo();
// 注意在实际开发中你可能需要返回一个包含更多信息的对象或重新构造一个支付参数字符串 // 注意在实际开发中你可能需要返回一个包含更多信息的对象或重新构造一个支付参数字符串
// 具体返回格式需与前端调用 my.tradePay 时所需的参数保持一致 // 具体返回格式需与前端调用 my.tradePay 时所需的参数保持一致
} else { } else {

View File

@ -1,50 +0,0 @@
package com.ruoyi.database.service;
import java.util.Date;
import com.ruoyi.database.domain.Order;
import com.ruoyi.database.domain.ParkingBillInfo;
import com.ruoyi.database.domain.ParkingBillPaymentInfo;
import com.ruoyi.database.domain.PayFees;
import com.ruoyi.database.enums.OrderStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
@RequiredArgsConstructor
public class OrderService {
private final ParkingBillPaymentInfoService parkingBillPaymentInfoService;
private final ParkingBillInfoService parkingBillInfoService;
/**
* 创建订单
*/
public void createOrder(PayFees payFees) {
ParkingBillInfo one = parkingBillInfoService.lambdaQuery()
.eq(ParkingBillInfo::getBillCode, payFees.getBillCode())
.last("limit 1")
.one();
if (one == null) {
throw new RuntimeException("未查询到订单");
}
System.out.println("收到创建支付订单请求: orderId=" + payFees.getBillCode() +
", amount=" + one.getTotalCostYuan());
// ParkingBillPaymentInfo parkingBillPaymentInfo = new ParkingBillPaymentInfo();
// parkingBillPaymentInfo.setBillCode(payFees.getBillCode());
// parkingBillPaymentInfo.setPlateNo(one.getPlateNo());
// parkingBillPaymentInfo.setPlateColor(one.getPlateColor());
// parkingBillPaymentInfo.setOpenId("");
// parkingBillPaymentInfo.setAlipayUserId("");
// parkingBillPaymentInfo.setPhone();
// parkingBillPaymentInfo.setPayStatus(0);
// orderCache.put(orderId, order);
}
}

View File

@ -11,4 +11,11 @@ import com.ruoyi.database.domain.ParkingBillInfo;
*/ */
public interface ParkingBillInfoService extends IService<ParkingBillInfo> { public interface ParkingBillInfoService extends IService<ParkingBillInfo> {
/**
* 车在停车场内时
* 根据车牌查询并更新停车账单
* @param plateNo 车牌
*/
public ParkingBillInfo saveOrUpdateParkingBillInfo(String plateNo);
} }

View File

@ -1,10 +1,19 @@
package com.ruoyi.database.service.impl; package com.ruoyi.database.service.impl;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.database.domain.ParkingBill;
import com.ruoyi.database.domain.ParkingBillInfo; import com.ruoyi.database.domain.ParkingBillInfo;
import com.ruoyi.database.mapper.ParkingBillInfoMapper; import com.ruoyi.database.mapper.ParkingBillInfoMapper;
import com.ruoyi.database.service.ParkingBillInfoService; import com.ruoyi.database.service.ParkingBillInfoService;
import com.ruoyi.database.util.HaiKangApiUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
/** /**
@ -14,6 +23,42 @@ import org.springframework.stereotype.Service;
* @since 2025-12-01 15:37:06 * @since 2025-12-01 15:37:06
*/ */
@Service @Service
@RequiredArgsConstructor
public class ParkingBillInfoServiceImpl extends ServiceImpl<ParkingBillInfoMapper, ParkingBillInfo> implements ParkingBillInfoService { public class ParkingBillInfoServiceImpl extends ServiceImpl<ParkingBillInfoMapper, ParkingBillInfo> implements ParkingBillInfoService {
private final HaiKangApiUtils haiKangApiUtils;
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public ParkingBillInfo saveOrUpdateParkingBillInfo(String plateNo) {
String parkingPaymentInfo = haiKangApiUtils.getParkingPaymentInfo(plateNo);
if(parkingPaymentInfo==null){
return null;
}
JSONObject jsonObject = new JSONObject(parkingPaymentInfo);
String data = jsonObject.getJSONObject("data").toString();
if(data==null){
return null;
}
try {
ParkingBill vehicleRecord = objectMapper.readValue(data, ParkingBill.class);
ParkingBillInfo parkingBillInfo = new ParkingBillInfo();
BeanUtils.copyProperties(vehicleRecord,parkingBillInfo);
parkingBillInfo.setPayMoneyYuan(vehicleRecord.getPayMoneyYuan());
parkingBillInfo.setDeductMoneyYuan(vehicleRecord.getDeductMoneyYuan());
parkingBillInfo.setTotalCostYuan(vehicleRecord.getTotalCostYuan());
LambdaUpdateWrapper<ParkingBillInfo> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(ParkingBillInfo::getBillCode,parkingBillInfo.getBillCode());
boolean b = saveOrUpdate(parkingBillInfo, updateWrapper);
if (b) {
return parkingBillInfo;
}else {
return null;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -0,0 +1,170 @@
package com.ruoyi.database.util;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class QRCodeGenerator {
/**
* 生成二维码并保存到本地
* @param content 二维码内容
* @param width 宽度
* @param height 高度
* @param filePath 保存路径
*/
public static void generateQRCode(String content, int width, int height, String filePath) {
try {
// 设置二维码参数
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 1); // 设置白边
// 生成二维码矩阵
BitMatrix bitMatrix = new MultiFormatWriter().encode(
content,
BarcodeFormat.QR_CODE,
width,
height,
hints
);
// 方法1使用MatrixToImageWriter直接保存最简单
String format = filePath.substring(filePath.lastIndexOf(".") + 1);
MatrixToImageWriter.writeToPath(bitMatrix, format, Paths.get(filePath));
System.out.println("二维码已保存到: " + new File(filePath).getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 生成带logo的二维码
*/
public static void generateQRCodeWithLogo(String content, int width, int height,
String filePath, String logoPath) {
try {
// 1. 生成基础二维码
Map<EncodeHintType, Object> hints = new HashMap<>();
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
hints.put(EncodeHintType.MARGIN, 1);
BitMatrix bitMatrix = new MultiFormatWriter().encode(
content, BarcodeFormat.QR_CODE, width, height, hints);
// 2. 转换为BufferedImage
BufferedImage qrImage = MatrixToImageWriter.toBufferedImage(bitMatrix);
// 3. 添加logo
if (logoPath != null && new File(logoPath).exists()) {
BufferedImage logoImage = ImageIO.read(new File(logoPath));
qrImage = addLogo(qrImage, logoImage);
}
// 4. 保存图片
String format = filePath.substring(filePath.lastIndexOf(".") + 1);
ImageIO.write(qrImage, format, new File(filePath));
System.out.println("带Logo的二维码已保存到: " + new File(filePath).getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 添加Logo到二维码中心
*/
private static BufferedImage addLogo(BufferedImage qrImage, BufferedImage logoImage) {
// 计算logo尺寸二维码的1/5
int logoWidth = logoImage.getWidth() > qrImage.getWidth() * 2 / 10 ?
qrImage.getWidth() * 2 / 10 : logoImage.getWidth();
int logoHeight = logoImage.getHeight() > qrImage.getHeight() * 2 / 10 ?
qrImage.getHeight() * 2 / 10 : logoImage.getHeight();
// 缩放logo
BufferedImage scaledLogo = new BufferedImage(logoWidth, logoHeight, BufferedImage.TYPE_INT_ARGB);
scaledLogo.getGraphics().drawImage(logoImage.getScaledInstance(logoWidth, logoHeight,
java.awt.Image.SCALE_SMOOTH), 0, 0, null);
// 计算logo位置居中
int x = (qrImage.getWidth() - logoWidth) / 2;
int y = (qrImage.getHeight() - logoHeight) / 2;
// 将logo绘制到二维码上
qrImage.getGraphics().drawImage(scaledLogo, x, y, null);
return qrImage;
}
/**
* 根据您提供的坐标数据生成二维码
*/
public static void generateFromCoordinates(String filePath) {
try {
// 假设这是您的坐标数据
String[] coordinates = {
"10:1",
"11:1",
"12:1"
// 这里应该包含完整的坐标数据
};
// 解析坐标数据
int size = 1080; // 二维码尺寸
BufferedImage image = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
// 白色背景
for (int x = 0; x < size; x++) {
for (int y = 0; y < size; y++) {
image.setRGB(x, y, 0xFFFFFF); // 白色
}
}
// 根据坐标绘制黑色模块
for (String coord : coordinates) {
String[] parts = coord.split(":");
if (parts.length == 2) {
int row = Integer.parseInt(parts[0].trim());
int col = Integer.parseInt(parts[1].trim());
if (row < size && col < size) {
image.setRGB(col, row, 0x000000); // 黑色
}
}
}
// 保存图片
ImageIO.write(image, "PNG", new File(filePath));
System.out.println("根据坐标生成的二维码已保存到: " + new File(filePath).getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 示例1生成普通二维码
String content = "C:\\Users\\28758\\Desktop\\222.png";
String filePath = "C:\\Users\\28758\\Desktop\\666.png";
// 生成二维码
generateQRCode(content, 1080, 640, filePath);
// 示例2生成带logo的二维码
String logoPath = "C:\\Users\\28758\\Desktop\\222.png";
String qrWithLogoPath = "C:\\Users\\28758\\Desktop\\777.png";
generateQRCodeWithLogo(content, 1080, 640, qrWithLogoPath, logoPath);
}
}

View File

@ -111,7 +111,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
// 过滤请求 // 过滤请求
.authorizeRequests() .authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问 // 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login","/loginByPhone", "/register", "/captchaImage").permitAll() .antMatchers("/login","/loginByPhone","/alipay/notify","/wxpay/notify", "/register", "/captchaImage").permitAll()
// 静态资源可匿名访问 // 静态资源可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll() .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll() .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()