PHP mysqli 提前5天显示 Payment Pending Notification
结论
现有代码无法准确实现“付款日期前 5 天开始显示通知”。
原因有两个:
- 代码计算的是当前日期加 7 天,不是加 5 天。
$duedate >= $paydate只能判断付款日期是否早于未来第 7 天,无法排除已经过期的记录。
如果付款日期是 2018-12-18,按照提前 5 天通知的规则,通知应从 2018-12-13 开始显示。
推荐做法:直接在 SQL 中筛选
假设数据库中的 pay_date 字段是 DATE 类型,并且只需要显示从今天起 5 天内到期的未付款记录,可以这样写:
<?php
$sql = "
SELECT id, pay_date
FROM payments
WHERE payment_status = 'pending'
AND pay_date BETWEEN CURDATE()
AND DATE_ADD(CURDATE(), INTERVAL 5 DAY)
ORDER BY pay_date ASC
";
$result = $mysqli->query($sql);
if ($result === false) {
throw new RuntimeException($mysqli->error);
}
while ($row = $result->fetch_assoc()) {
echo htmlspecialchars((string) $row['id'], ENT_QUOTES, 'UTF-8')
. ' Pending payment '
. htmlspecialchars($row['pay_date'], ENT_QUOTES, 'UTF-8')
. '<br>';
}
?>
其中:
pay_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 5 DAY)
表示付款日期在以下范围内:
今天 <= pay_date <= 今天 + 5 天
这个条件包含起止日期。因此,当付款日期为 2018-12-18 时,从 2018-12-13 到 2018-12-18 都会显示通知。
请把示例中的 payments、payment_status 等名称替换为实际使用的表名和字段名。
如果过期后仍然要显示 Pending
如果业务规则是“提前 5 天开始显示,付款完成后才停止”,就不应排除已经到期的记录:
<?php
$sql = "
SELECT id, pay_date
FROM payments
WHERE payment_status = 'pending'
AND pay_date <= DATE_ADD(CURDATE(), INTERVAL 5 DAY)
ORDER BY pay_date ASC
";
$result = $mysqli->query($sql);
if ($result === false) {
throw new RuntimeException($mysqli->error);
}
while ($row = $result->fetch_assoc()) {
echo htmlspecialchars((string) $row['id'], ENT_QUOTES, 'UTF-8')
. ' Pending payment '
. htmlspecialchars($row['pay_date'], ENT_QUOTES, 'UTF-8')
. '<br>';
}
?>
这个条件会查出未来 5 天内到期、今天到期,以及已经过期但状态仍为 pending 的付款。
因此,数据库中需要有付款状态字段,并在付款完成后将状态更新为 paid,否则通知会一直显示。
在 PHP 中判断日期
如果必须先读取全部记录,再由 PHP 判断,建议使用 DateTimeImmutable,不要直接比较日期字符串:
<?php
$sql = "
SELECT id, pay_date
FROM payments
WHERE payment_status = 'pending'
";
$result = $mysqli->query($sql);
if ($result === false) {
throw new RuntimeException($mysqli->error);
}
$today = new DateTimeImmutable('today');
$notificationEnd = $today->modify('+5 days');
while ($row = $result->fetch_assoc()) {
$payDate = DateTimeImmutable::createFromFormat(
'!Y-m-d',
$row['pay_date']
);
if ($payDate === false) {
continue;
}
if ($payDate >= $today && $payDate <= $notificationEnd) {
echo htmlspecialchars((string) $row['id'], ENT_QUOTES, 'UTF-8')
. ' Pending payment '
. htmlspecialchars($row['pay_date'], ENT_QUOTES, 'UTF-8')
. '<br>';
}
}
?>
这里同时设置了日期的上下限,只有尚未到期且将在 5 天内到期的记录才会显示。
原代码的问题
原代码的日期计算如下:
$duedate = date(
"Y-m-d",
mktime(0, 0, 0, date("m"), date("d") + 7)
);
它算出的是“今天之后 7 天”,至少应将 7 改为 5。但只改这个数字还不够,因为下面的条件:
if ($duedate >= $paydate)
同样会匹配已经过期的付款日期。例如,付款日期是昨天,它仍然小于 $duedate,通知依旧会显示。
只有当两边都严格采用 Y-m-d 格式时,字符串比较才可靠。使用 DateTimeImmutable,或者直接让数据库比较 DATE 字段,会更清楚,也不容易因日期格式不同而出错。
日期保存格式
用户输入的日期可能是:
18.12.2018
不要直接按这个格式保存并用于数据库日期运算。应先转换成标准格式:
$input = '18.12.2018';
$payDate = DateTimeImmutable::createFromFormat('!d.m.Y', $input);
if ($payDate === false) {
throw new InvalidArgumentException('付款日期格式无效');
}
$databaseValue = $payDate->format('Y-m-d');
// 2018-12-18
数据库字段建议使用:
pay_date DATE
不要使用 VARCHAR,否则无法可靠地使用 CURDATE()、DATE_ADD()、BETWEEN 和日期排序等功能。
注意事项
- 先确定通知是在“到期前 5 天至到期日”显示,还是在过期后继续显示。
- PHP 和 MySQL 应使用一致的时区,否则在接近午夜时可能出现一天的偏差。
- 已付款记录需要通过状态字段排除。
- 输出数据库内容时应使用
htmlspecialchars(),避免把异常内容直接写入 HTML。 - 如果
pay_date包含具体时间,应使用DATETIME,并根据业务需求改用NOW()。如果只按自然日判断,使用DATE和CURDATE()更合适。
备注:内容仅供参考。
