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 请求。例如:
- 商品页面将
qty提交到确认页面; - 确认页面再次提交银行选择到支付页面;
- 第二次请求只会包含当前表单中的字段;
- 如果该表单没有
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()。
备注:内容仅供参考。
