PHP 中为 Payment API 动态构建多维 lines 数组
支付小周
结论
不要拼接 PHP 数组语法字符串,也不要使用 eval()。在循环中直接创建关联数组,再将每个商品数组追加到 $lines。调用 API 时传入:
"lines" => $lines
不要写成:
"lines" => [$echoOrders]
第二种写法传入的是一个只包含字符串的数组,并不是 Mollie 要求的商品行二维数组。
正确构建 lines
如果每件商品的数量都是 1,VAT 为 0%,可以这样写:
<?php
$lines = [];
foreach ($_SESSION['cart_products'] as $cartItem) {
$unitPrice = number_format(
(float) $cartItem['cat_item_prijs'],
2,
'.',
''
);
$lines[] = [
"sku" => (string) $cartItem['cat_item_code'],
"name" => (string) $cartItem['cat_item_titel_' . $lang],
"quantity" => 1,
"vatRate" => "0.00",
"unitPrice" => [
"currency" => "EUR",
"value" => $unitPrice,
],
"totalAmount" => [
"currency" => "EUR",
"value" => $unitPrice,
],
"vatAmount" => [
"currency" => "EUR",
"value" => "0.00",
],
];
}
生成的数据结构相当于:
[
[
"sku" => "ITEM-001",
"name" => "Product one",
"quantity" => 1,
// ...
],
[
"sku" => "ITEM-002",
"name" => "Product two",
"quantity" => 1,
// ...
],
]
然后将 $lines 直接放进请求参数:
$order = $mollie->orders->create([
"amount" => [
"value" => $orderTotal,
"currency" => "EUR",
],
"billingAddress" => [
"streetAndNumber" => $address,
"postalCode" => $postalcode,
"city" => $city,
"country" => $country,
"givenName" => $firstname,
"familyName" => $familyName,
"email" => $email,
],
"shippingAddress" => [
"streetAndNumber" => $addressa,
"postalCode" => $postalcodea,
"city" => $citya,
"country" => $countrya,
"givenName" => $firstnamea,
"familyName" => $familyNamea,
"email" => $email,
],
"metadata" => [
"order_id" => $orderId,
],
"locale" => "nl_BE",
"orderNumber" => strval($orderId),
"redirectUrl" => "{$protocol}://{$hostname}{$path}/thankyou.php?order_id={$orderId}",
"webhookUrl" => "https://www.cluster-park.com/dev/includes/webhook.php",
"lines" => $lines,
]);
同时计算数量和订单总额
如果购物车中保存了商品数量,就不应把 quantity 和订单金额写死。金额最好按整数分计算,以免遇到浮点数精度问题:
<?php
$lines = [];
$orderTotalCents = 0;
$titleKey = 'cat_item_titel_' . $lang;
foreach ($_SESSION['cart_products'] as $cartItem) {
$quantity = max(1, (int) ($cartItem['quantity'] ?? 1));
// 假设 cat_item_prijs 是以欧元表示的十进制价格。
$unitPriceCents = (int) round(
(float) $cartItem['cat_item_prijs'] * 100
);
$lineTotalCents = $unitPriceCents * $quantity;
$orderTotalCents += $lineTotalCents;
$unitPrice = number_format($unitPriceCents / 100, 2, '.', '');
$lineTotal = number_format($lineTotalCents / 100, 2, '.', '');
$lines[] = [
"sku" => (string) $cartItem['cat_item_code'],
"name" => (string) $cartItem[$titleKey],
"quantity" => $quantity,
"vatRate" => "0.00",
"unitPrice" => [
"currency" => "EUR",
"value" => $unitPrice,
],
"totalAmount" => [
"currency" => "EUR",
"value" => $lineTotal,
],
"vatAmount" => [
"currency" => "EUR",
"value" => "0.00",
],
];
}
$orderTotal = number_format($orderTotalCents / 100, 2, '.', '');
调用 API 时传入:
$order = $mollie->orders->create([
"amount" => [
"value" => $orderTotal,
"currency" => "EUR",
],
// 其他订单字段……
"lines" => $lines,
]);
原来的写法为什么会失败
下面这段代码生成的是字符串:
$echoOrders = implode($orders);
即使字符串内容看起来符合 PHP 数组语法,它也仍然只是普通文本。例如:
$echoOrders = '["sku" => "ITEM-001"]';
再把它写成:
"lines" => [$echoOrders]
得到的实际数据结构是:
[
'["sku" => "ITEM-001"]'
]
API 收到的第一条商品行是字符串,而不是包含 sku、name、quantity 等字段的对象,所以无法将其解析为订单行。
注意事项
- 不要使用
eval()执行拼接出来的数组字符串。这样做没有必要,还会带来代码注入风险。 amount.value、unitPrice.value、totalAmount.value等金额通常要使用带两位小数的字符串,例如"15.50",不能写成"15,50"。- 订单的
amount.value必须与所有订单行金额、折扣等数据一致,不能一直写死为"15.50"。 - 当
quantity大于1时,totalAmount.value通常不能仍然等于单价。计算时还要考虑折扣、VAT 和当前 Mollie API 的金额规则。 vatRate和vatAmount必须与真实税务数据一致。只有确实适用零税率时才能使用"0.00"。- 商品标题字段是动态生成的。使用前要确认
$lang属于允许的语言值,并检查相应的键是否存在。 - 不要让
$name同时表示商品名称和客户姓氏。如果循环覆盖了$name,billingAddress.familyName可能会变成最后一件商品的名称。可以分别使用$productName和$familyName。 - 调用 API 前要处理空购物车,以及缺少价格、SKU 或标题的商品,避免发送不完整的订单数据。
备注:内容仅供参考。
