PAYATHON 2026

如何将固定 billing amount 199 改为数据库指定金额?

支付阿杰

结论

$billing_cost = "SELECT ..." 只是把 SQL 语句赋给了一个字符串变量,并没有执行查询。把这个字符串写入 DECIMAL 类型的 billing_cost 字段时,MySQL 在非严格模式下可能会将它转换为 0.00。

正确的处理方式是根据当前记录的 a_id 查询 agents.agent_billing_amount,拿到金额后再写入 sold_product。

正确写法

下面的示例使用预处理语句,并假设 agents.a_id 对应 $magazine_listing['a_id']:

$billing_date = time() + 604800;
$agent_id = (int) $magazine_listing['a_id'];

/*
 * 查询当前 agent 的账单金额
 */
$billing_stmt = $mysqli->prepare(
    "SELECT `agent_billing_amount`
     FROM `agents`
     WHERE `a_id` = ?
     LIMIT 1"
);

if (!$billing_stmt) {
    throw new RuntimeException($mysqli->error);
}

$billing_stmt->bind_param("i", $agent_id);
$billing_stmt->execute();
$billing_stmt->bind_result($billing_cost);

if (!$billing_stmt->fetch()) {
    $billing_stmt->close();
    throw new RuntimeException(
        "No agent found for a_id: " . $agent_id
    );
}

$billing_stmt->close();

if ($billing_cost === null || !is_numeric($billing_cost)) {
    throw new RuntimeException(
        "Invalid billing amount for a_id: " . $agent_id
    );
}

/*
 * 豁免 agent 不收费,否则正常收费
 */
$charge = in_array($agent_id, $exempt_agents, true) ? 0 : 1;

/*
 * 插入 sold_product
 */
$insert_stmt = $mysqli->prepare(
    "INSERT INTO `sold_product`
        (
            `m_id`,
            `moveover_m_id`,
            `listing_id`,
            `a_id`,
            `agent_mls_id`,
            `billing_cost`,
            `billing_date`,
            `billed`,
            `charge`
        )
     VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?)"
);

if (!$insert_stmt) {
    throw new RuntimeException($mysqli->error);
}

$m_id = (int) $magazine_listing['m_id'];
$moveover_m_id = (int) $magazine_listing['moveover_m_id'];
$listing_id = (string) $data['L_ListingID'];
$agent_mls_id = (string) $magazine_listing['agent_mls_id'];

$insert_stmt->bind_param(
    "iisissii",
    $m_id,
    $moveover_m_id,
    $listing_id,
    $agent_id,
    $agent_mls_id,
    $billing_cost,
    $billing_date,
    $charge
);

$insert_stmt->execute();
$insert_id = $mysqli->insert_id;
$insert_stmt->close();

邮件中的金额仍可按下面的方式格式化:

number_format((float) $billing_cost, 2)

为什么会得到 0.00

下面这行代码不会查询数据库:

$billing_cost = "SELECT `agent_billing_amount` FROM `agents`";

此时,$billing_cost 中保存的是以下文本:

SELECT `agent_billing_amount` FROM `agents`

将它插入数值字段时,MySQL 会尝试把这段文本转换成数字。由于它不是合法的数字字符串,在非严格 SQL 模式下通常会被转换为 0,所以最终得到 0.00。如果启用了严格 SQL 模式,这项操作通常会直接报错。

sold_product.billing_cost 的默认值也无法避免这个问题。只有在插入时省略该字段,或者显式使用 DEFAULT,默认值才会生效。当前代码已经向 billing_cost 写入了一个值,因此 MySQL 不会使用它的默认值。

也可以直接使用 INSERT ... SELECT

如果插入前不需要单独使用 $billing_cost,可以让数据库直接从 agents 表复制金额:

INSERT INTO `sold_product`
(
    `m_id`,
    `moveover_m_id`,
    `listing_id`,
    `a_id`,
    `agent_mls_id`,
    `billing_cost`,
    `billing_date`,
    `billed`,
    `charge`
)
SELECT
    ?,
    ?,
    ?,
    a.`a_id`,
    ?,
    a.`agent_billing_amount`,
    ?,
    0,
    ?
FROM `agents` AS a
WHERE a.`a_id` = ?
LIMIT 1

这种写法只需要一次数据库操作,并且能保证金额取自同一条 agent 记录。如果后续邮件还要显示 $billing_cost,则仍需查询金额,或者在插入后重新读取这条记录。

注意事项

  • 查询中必须包含 WHERE a_id = ?,否则无法确定要使用哪位 agent 的金额。
  • 如果 a_id 不是整数,需要按照字段的实际类型调整 bind_param() 的类型字符。
  • 金额字段适合使用 DECIMAL(10,2) 等定点数类型,不要用 FLOAT 保存货币金额。
  • 缺少 agent 记录时,不建议悄悄回退到 199,否则很难发现数据关联问题。更安全的做法是停止插入并记录错误。
  • 如果 agent_billing_amount 允许为 NULL,需要明确对应的业务规则,例如拒绝生成账单或使用预设金额。
  • 原代码还有多处通过字符串拼接生成 SQL。listing 数据可能来自外部系统,最好将这些语句也改成预处理语句,避免 SQL 注入和引号转义问题。

备注:内容仅供参考。