PAYATHON 2026

Django 如何将购物车总金额传递给 Payment gateway

支付阿杰

结论

document.getElementById('{{ cart.get_total_price }}') 的用法不对。getElementById() 接收的是 HTML 元素的 id,而 {{ cart.get_total_price }} 渲染后只是金额,例如 49.90,不是元素 ID。

可以先把购物车总额放入模板上下文,再通过隐藏字段或 Django 的 json_script 安全地传给 JavaScript。前端金额只能用于显示,提交给支付网关的金额必须由服务器重新计算。否则,用户可以修改 JavaScript 或表单中的金额。

推荐实现

1. 在视图中传递金额

不要用 replace('.', '') 处理金额。这样会把 12.50 变成 1250,但支付接口需要的是 12.50 还是 1250,要看网关要求使用主货币单位还是最小货币单位。

from django.contrib.auth.decorators import login_required
from django.shortcuts import render


@login_required
def CartView(request):
    cart = Cart(request)
    total = cart.get_total_price()

    return render(
        request,
        "payment/payment_form.html",
        {
            "cart": cart,
            "total": format(total, "f"),
        },
    )

这里将 Decimal 金额格式化成普通的十进制字符串,例如 "12.50"。

2. 使用 json_script 输出金额

Django 提供的 json_script 可以安全地把数据传给 JavaScript:

<button id="submit" type="button">
    Pay ${{ total }}
</button>

{{ total|json_script:"cart-total" }}

模板渲染后会生成一个包含金额的 JSON 数据节点。

3. 在 JavaScript 中读取金额

document.addEventListener("DOMContentLoaded", () => {
  document.getElementById("submit").addEventListener("click", (event) => {
    event.preventDefault();

    const PBFKey = "xxxxxxxxxxxxxxxxxxxxxxxxxx";
    const txRef = String(
      Math.floor(Math.random() * 1000000000) + 1
    );

    const email = document.getElementById("email").value;
    const fullname = document.getElementById("fullName").value;
    const address1 = document.getElementById("custAdd").value;
    const address2 = document.getElementById("custAdd2").value;
    const country = document.getElementById("country").value;
    const state = document.getElementById("state").value;
    const postcode = document.getElementById("postCode").value;

    const amountText = JSON.parse(
      document.getElementById("cart-total").textContent
    );
    const amount = Number(amountText);

    if (!Number.isFinite(amount) || amount <= 0) {
      console.error("Invalid payment amount:", amountText);
      return;
    }

    getpaidSetup({
      PBFPubKey: PBFKey,
      customer_email: email,
      amount: amount,
      currency: "USD",
      txref: txRef,

      onclose: function () {},

      callback: function (response) {
        const flwRef = response.tx.flwRef;

        console.log(
          "This is the response returned after a charge",
          response
        );

        if (
          response.tx.chargeResponse === "00" ||
          response.tx.chargeResponse === "0"
        ) {
          // 将交易引用发送到服务器,由服务器向支付网关验证交易
        } else {
          // 跳转到支付失败页面
        }
      },
    });
  });
});

这段代码还修正了原代码中的变量覆盖问题:

var address1 = document.getElementById("postCode").value;

这行代码会覆盖前面保存的 address1,邮编应保存在单独的 postcode 变量中。

使用隐藏字段的简单写法

如果暂时不使用 json_script,也可以添加一个隐藏字段:

<input
  type="hidden"
  id="payment-amount"
  value="{{ total }}"
>

然后读取字段中的值:

const amount = Number(
  document.getElementById("payment-amount").value
);

不要把金额当作元素 ID:

// 错误
document.getElementById("{{ cart.get_total_price }}").value;

是否需要乘以 100

在没有确认支付网关要求的金额格式前,不要直接这样写:

amount: amount * 100

如果接口接收主货币单位,12.50 美元应提交为:

amount: 12.50

只有接口明确要求最小货币单位时,才提交 1250。此时最好在服务端使用 Decimal 转换,避免 JavaScript 浮点数产生误差:

from decimal import Decimal, ROUND_HALF_UP

total = cart.get_total_price()
amount_in_cents = int(
    (total * Decimal("100")).quantize(
        Decimal("1"),
        rounding=ROUND_HALF_UP,
    )
)

最终采用哪种格式,应以当前支付网关接口的参数说明为准。

必须注意支付安全

模板中的金额、隐藏字段和 JavaScript 变量都可以被用户修改。安全的支付流程应当是:

  1. 浏览器向 Django 提交结算请求。
  2. Django 根据当前购物车中的商品、数量和价格重新计算总额。
  3. Django 在服务端生成唯一交易编号并创建订单。
  4. 服务端将可信金额提交给支付网关,或生成支付初始化参数。
  5. 收到支付回调后,服务端使用网关提供的交易引用再次查询并验证。
  6. 确认交易状态、币种、金额和订单编号全部匹配后,才能把订单标记为已支付。

前端回调中的:

response.tx.chargeResponse

只能控制界面跳转,不能单独作为支付成功的最终依据。支付金额也不能以浏览器提交的值为准。

备注:内容仅供参考。