PAYATHON 2026

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() 更合适。

备注:内容仅供参考。