PAYATHON 2026

django获取支付宝支付qrcode并使用canvas绘制

支付老李

结论

支付宝接口返回的 qr_code 通常是用于生成二维码的字符串,不是可以直接交给 Canvas 绘制的图片数据。

一般按下面的流程处理:

  1. Django 后端调用 alipay.trade.precreate 创建支付宝预下单。
  2. 从支付宝响应中读取 qr_code。
  3. 后端以 JSON 格式向前端返回 qr_code、商户订单号等必要信息。
  4. 前端通过二维码库对 qr_code 编码,并绘制到 <canvas>。
  5. 支付结果以支付宝异步通知为准,不能只根据前端页面判断。

Canvas 不会自动把字符串转换成二维码,需要调用 QRCode.toCanvas() 等二维码编码方法。

Django 后端返回二维码内容

支付宝当面付预下单成功后,响应通常包含类似内容:

{
  "code": "10000",
  "msg": "Success",
  "out_trade_no": "ORDER_202609090001",
  "qr_code": "https://qr.alipay.com/xxxxxxxx"
}

前端生成二维码时,使用的原始内容就是其中的 qr_code。

下面假设项目中已经封装了支付宝客户端,并且 precreate() 返回解析后的字典。各个 Python SDK 的调用方式可能不同,具体写法应以所用 SDK 的文档为准。

# views.py
import json
import uuid
from decimal import Decimal, InvalidOperation

from django.http import JsonResponse
from django.views.decorators.http import require_POST

from .alipay_service import alipay_client


@require_POST
def create_alipay_order(request):
    try:
        body = json.loads(request.body or "{}")
        amount = Decimal(str(body.get("amount", "")))
    except (json.JSONDecodeError, InvalidOperation):
        return JsonResponse(
            {"success": False, "message": "请求参数格式错误"},
            status=400,
        )

    if amount <= 0:
        return JsonResponse(
            {"success": False, "message": "支付金额必须大于 0"},
            status=400,
        )

    # 实际项目中,金额应从数据库中的商品或订单信息计算,
    # 不应直接信任前端传入的 amount。
    out_trade_no = f"ORDER_{uuid.uuid4().hex}"

    try:
        result = alipay_client.precreate(
            out_trade_no=out_trade_no,
            total_amount=f"{amount:.2f}",
            subject="订单支付",
        )
    except Exception:
        # 这里应记录完整异常,但不要把密钥、签名或支付宝原始敏感信息返回前端。
        return JsonResponse(
            {"success": False, "message": "创建支付订单失败"},
            status=502,
        )

    if result.get("code") != "10000" or not result.get("qr_code"):
        return JsonResponse(
            {
                "success": False,
                "message": result.get("sub_msg") or result.get("msg") or "支付宝未返回二维码",
            },
            status=502,
        )

    return JsonResponse({
        "success": True,
        "out_trade_no": out_trade_no,
        "qr_code": result["qr_code"],
    })

支付宝调用的封装逻辑大致如下:

# alipay_service.py
class AlipayService:
    def precreate(self, out_trade_no, total_amount, subject):
        biz_content = {
            "out_trade_no": out_trade_no,
            "total_amount": total_amount,
            "subject": subject,
        }

        # 此处调用实际 SDK 的 alipay.trade.precreate 方法。
        # 返回值应先完成支付宝签名验证,再解析为字典。
        return call_alipay_precreate_api(biz_content)


alipay_client = AlipayService()

应用私钥、支付宝公钥配置和签名参数生成逻辑都必须放在服务端,不能暴露给浏览器。

前端使用 Canvas 绘制二维码

前端可以使用支持 Canvas 输出的二维码库,例如 qrcode。下面的代码假设页面已经正确加载该库,并且它的全局对象名为 QRCode。

<canvas id="alipay-qrcode" width="240" height="240"></canvas>
<p id="payment-message"></p>
async function createPayment() {
  const message = document.getElementById("payment-message");
  const canvas = document.getElementById("alipay-qrcode");

  message.textContent = "正在创建支付订单……";

  try {
    const response = await fetch("/api/alipay/precreate/", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-CSRFToken": getCookie("csrftoken")
      },
      body: JSON.stringify({
        amount: "99.00"
      })
    });

    const data = await response.json();

    if (!response.ok || !data.success) {
      throw new Error(data.message || "创建支付订单失败");
    }

    await QRCode.toCanvas(canvas, data.qr_code, {
      width: 240,
      margin: 2,
      errorCorrectionLevel: "M",
      color: {
        dark: "#000000",
        light: "#ffffff"
      }
    });

    message.textContent = `订单号:${data.out_trade_no},请使用支付宝扫码付款`;
  } catch (error) {
    const context = canvas.getContext("2d");
    context.clearRect(0, 0, canvas.width, canvas.height);
    message.textContent = error.message;
  }
}

function getCookie(name) {
  const cookies = document.cookie ? document.cookie.split(";") : [];

  for (const item of cookies) {
    const cookie = item.trim();

    if (cookie.startsWith(`${name}=`)) {
      return decodeURIComponent(cookie.slice(name.length + 1));
    }
  }

  return "";
}

createPayment();

负责二维码编码的是这一行:

QRCode.toCanvas(canvas, data.qr_code);

下面的写法则不能生成二维码:

const context = canvas.getContext("2d");
context.fillText(data.qr_code, 0, 20);

fillText() 只会把字符串绘制成普通文字,不会将它转换成二维码的黑白矩阵。

如果后端返回的是二维码图片

如果后端已经生成 PNG 格式的二维码,并返回 Base64 图片,前端可以把它当作普通图片绘制到 Canvas:

function drawBase64Image(canvas, base64Data) {
  const context = canvas.getContext("2d");
  const image = new Image();

  image.onload = function () {
    context.clearRect(0, 0, canvas.width, canvas.height);
    context.drawImage(image, 0, 0, canvas.width, canvas.height);
  };

  image.onerror = function () {
    console.error("二维码图片加载失败");
  };

  image.src = base64Data;
}

后端可以返回以下格式:

{
  "success": true,
  "qrcode_image": "data:image/png;base64,iVBORw0KGgoAAA..."
}

不过,支付宝已经返回 qr_code 字符串时,通常不必再由后端生成图片。直接把字符串交给前端生成二维码,传输的数据更少,处理也更简单。

支付状态不能由 Canvas 判断

二维码成功显示,只能说明预下单已经完成,不能证明用户已经付款。

完整的支付流程如下:

浏览器请求创建订单
        ↓
Django 创建本地订单
        ↓
Django 调用 alipay.trade.precreate
        ↓
浏览器绘制 qr_code
        ↓
用户扫码并付款
        ↓
支付宝异步通知 Django
        ↓
Django 验证签名、金额、商户号和订单号
        ↓
更新本地订单状态
        ↓
前端查询本地订单状态并显示支付结果

前端可以定时查询 Django 提供的订单状态接口:

function watchPaymentStatus(outTradeNo) {
  const timer = setInterval(async () => {
    try {
      const response = await fetch(
        `/api/orders/${encodeURIComponent(outTradeNo)}/status/`
      );
      const data = await response.json();

      if (data.status === "paid") {
        clearInterval(timer);
        document.getElementById("payment-message").textContent = "支付成功";
      }

      if (data.status === "closed") {
        clearInterval(timer);
        document.getElementById("payment-message").textContent = "订单已关闭";
      }
    } catch (error) {
      console.error("查询支付状态失败", error);
    }
  }, 2000);
}

该接口应读取本地数据库中的订单状态。这个状态可以由通过验签的支付宝异步通知更新,也可以由后端主动调用支付宝订单查询接口确认。

注意事项

  • 支付金额应由后端根据商品、订单和优惠规则计算,不能信任前端提交的金额。
  • out_trade_no 必须唯一,并保存到本地订单表。
  • 调用支付宝接口后,需要检查 code、sub_code、msg 和 sub_msg,不能只看 HTTP 状态码。
  • 支付宝响应和异步通知都需要验证签名。
  • 处理异步通知时,还要核对订单号、金额、应用身份和收款方信息,不能只验签而不检查业务数据。
  • 支付宝可能重复发送异步通知,因此订单更新必须支持幂等处理。
  • 二维码需要设置合理的过期时间。订单关闭或超时后,应停止展示二维码。
  • 前端不能接收或保存私钥、应用密钥等服务端配置。
  • 二维码周围需要保留空白边距。缩放模糊、遮挡或过于复杂的背景都可能影响扫码。
  • 正式环境应使用 HTTPS,日志中不要记录完整签名、密钥或敏感支付参数。

备注:内容仅供参考。