PAYATHON 2026

PHP 中如何将 price * qty 正确传入函数

支付阿杰

结论

setIdealAmount() 并没有把金额改成 0。错误栈显示调用的是 setIdealAmount(0),说明在进入该方法之前,$iAmount 就已经是 0 了。

通常是因为支付表单提交后,当前请求里没有有效的 $_POST['qty']。如果页面经历了两次提交,前一次请求中的 POST 数据不会自动带到下一次请求。空值、字符串 "0",以及在旧版 PHP 中无法正确转换为数字的字符串,与 2999 相乘后都可能得到 0。

因此,需要在当前请求中重新获取并严格验证数量,再由服务器计算金额。更稳妥的方式是从 session、购物车或数据库读取数量和单价,不要直接采用客户端提交的总金额。

先检查实际收到的数量

计算金额前,可以临时输出相关变量:

var_dump($_POST['qty'] ?? null);
var_dump($qty ?? null);

如果结果是 NULL、空字符串、"0",或者请求中根本没有 qty 字段,那么问题出在金额计算之前。

POST 数据只属于当前 HTTP 请求。例如:

  1. 商品页面将 qty 提交到确认页面;
  2. 确认页面再次提交银行选择到支付页面;
  3. 第二次请求只会包含当前表单中的字段;
  4. 如果该表单没有 qty,之前的 $_POST['qty'] 不会继续存在。

验证数量后再计算

数量必须是大于 0 的整数:

$qty = filter_input(
    INPUT_POST,
    'qty',
    FILTER_VALIDATE_INT,
    [
        'options' => [
            'min_range' => 1,
            'max_range' => 100
        ]
    ]
);

if ($qty === false || $qty === null) {
    throw new InvalidArgumentException('Invalid quantity.');
}

$unitPrice = 2999; // cents
$iAmount = $unitPrice * $qty;

$oIdeal->setIdealAmount($iAmount);

这里要使用严格比较:

$qty === false || $qty === null

filter_input() 在验证失败时可能返回 false,字段不存在时则可能返回 null。其中的 max_range 应按店铺允许购买的最大数量调整。

如果不使用 filter_input(),也可以直接验证:

if (
    !isset($_POST['qty']) ||
    is_array($_POST['qty']) ||
    filter_var($_POST['qty'], FILTER_VALIDATE_INT) === false
) {
    throw new InvalidArgumentException('Invalid quantity.');
}

$qty = (int) $_POST['qty'];

if ($qty <= 0 || $qty > 100) {
    throw new InvalidArgumentException('Quantity is out of range.');
}

$iAmount = 2999 * $qty;
$oIdeal->setIdealAmount($iAmount);

只写 (int) $_POST['qty'] 不能代替验证。"abc"、空字符串等内容可能被转换成 0,反而掩盖数据本身的问题。

跨页面保存数量

如果数量来自前一个页面,而支付前还要提交一次银行选择表单,可以用 session 保存购物车数据:

// 商品数量验证成功后
session_start();
$_SESSION['cart_qty'] = $qty;

然后在支付页面读取并验证:

session_start();

$qty = filter_var(
    $_SESSION['cart_qty'] ?? null,
    FILTER_VALIDATE_INT,
    [
        'options' => [
            'min_range' => 1,
            'max_range' => 100
        ]
    ]
);

if ($qty === false) {
    throw new RuntimeException('Cart quantity is missing or invalid.');
}

$unitPrice = 2999;
$iAmount = $unitPrice * $qty;

$oIdeal->setIdealAmount($iAmount);

另一种做法是在第二个表单中加入隐藏字段:

<input type="hidden" name="qty" value="<?= htmlspecialchars((string) $qty, ENT_QUOTES, 'UTF-8') ?>">

不过,访客仍然可以修改隐藏字段,所以服务器必须再次验证。真实支付流程中,最好只传递商品或购物车标识,再由服务器从数据库或购物车记录中读取数量和单价。

改进金额方法的参数检查

使用整数 cents 表示支付金额是正确的。还可以让方法明确拒绝非正整数:

public function setIdealAmount($intIdealAmount)
{
    if (
        filter_var($intIdealAmount, FILTER_VALIDATE_INT) === false ||
        (int) $intIdealAmount <= 0
    ) {
        throw new InvalidArgumentException(
            'Invalid ideal amount, please check.'
        );
    }

    $this->idealAmount = (int) $intIdealAmount;

    return $this;
}

如果项目的 PHP 版本支持标量类型声明,也可以写成:

public function setIdealAmount(int $intIdealAmount): self
{
    if ($intIdealAmount <= 0) {
        throw new InvalidArgumentException(
            'Invalid ideal amount, please check.'
        );
    }

    $this->idealAmount = $intIdealAmount;

    return $this;
}

能否使用 int 和 : self,取决于项目运行的 PHP 版本。

支付金额不要由客户端决定

不要直接接收客户端传来的单价或最终金额,例如:

$iAmount = $_POST['amount'];

否则,访客可以修改请求并以更低的金额发起支付。可以改为:

$productId = $_POST['product_id'] ?? null;

// 根据 $productId 从数据库读取真实单价
$unitPrice = 2999;

// 数量经过严格验证后,由服务器计算
$iAmount = $unitPrice * $qty;

$oIdeal->setIdealAmount($iAmount);

需要修复的并不是乘法表达式。2999 * $qty 本身没有问题,问题在于执行乘法时,$qty 不是预期的正整数。检查当前支付请求是否包含 qty,或者改用 session、购物车和数据库保存交易数据,就能将正确的总金额传给 setIdealAmount()。

备注:内容仅供参考。