python实现微信支付宝付款聚合码程序

聚合码(又称“聚合支付码”、“一码多付”)是指用一个二维码同时支持多个支付渠道(如微信、支付宝等)的收款码。顾客用不同 App 扫同一个码,系统会自动识别并跳转到对应的支付页面,无需商家摆放多个二维码。

聚合码本质上是一个URL(例如 https://pay.example.com/merchant)。当用户扫码时,服务端根据请求来源的 User-Agent 或 Referer 判断是微信还是支付宝,然后重定向到对应的收款链接。

本文介绍python实现微信支付宝付款二合一聚合码。

运行截图:

运行此程序前,请确保 Python 环境已安装以下第三方库:

pip install qrcode pillow pyzbar

使用的库及其作用

tkinter(标准库) 创建图形界面窗口、按钮、输入框、画布等控件

qrcode 根据URL生成二维码图像(PIL Image对象)

Pillow (PIL) 图像处理:旋转、裁剪、粘贴、缩放、绘制文字和矩形、格式转换等

pyzbar 从上传的二维码图片中解码出原始链接(可选,若未安装则提示手动输入)

io, os, tempfile(标准库) 文件路径处理、临时文件支持(本程序中实际未深度使用,但为扩展保留)

【Python第三方模块(库、包)安装、卸载与查看及常见问题解决 https://blog.csdn.net/cnds123/article/details/104393385 

使用 Python 生成二维码 https://blog.csdn.net/cnds123/article/details/123158166 】

源码如下(参考自网络):

"""
二合一支付码生成器 - Python + tkinter 版本
将微信和支付宝收款二维码物理融合为一个二维码
"""

import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import qrcode
from PIL import Image, ImageTk, ImageDraw, ImageFont
import io
import os
import tempfile

# ──────────────────────────────────────────────
#  核心引擎:生成 & 合并二维码
# ──────────────────────────────────────────────
class QRCodeEngine:
    """二维码生成与合并引擎"""

    @staticmethod
    def generate_qr(url: str, size: int = 400) -> Image.Image:
        """根据 URL 生成二维码 PIL Image"""
        qr = qrcode.QRCode(
            version=None,
            error_correction=qrcode.constants.ERROR_CORRECT_H,
            box_size=10,
            border=4,
        )
        qr.add_data(url)
        qr.make(fit=True)
        img = qr.make_image(fill_color="black", back_color="white")
        img = img.convert("RGB")
        img = img.resize((size, size), Image.LANCZOS)
        return img

    @staticmethod
    def merge_qrcodes(
        wechat_url: str,
        alipay_url: str,
        size: int = 400,
        title: str = "扫码支付",
    ) -> Image.Image:
        """
        合并微信和支付宝二维码

        原理:
        1. 微信二维码作为底层完整绘制
        2. 支付宝二维码旋转180°后放置到右下角(无定位图案区域)
        3. 这样微信扫码读取主码,支付宝扫码识别右下角旋转后的码
        """
        # 生成两个二维码
        wechat_img = QRCodeEngine.generate_qr(wechat_url, size)
        alipay_img = QRCodeEngine.generate_qr(alipay_url, size)

        # 支付宝二维码旋转 180°
        alipay_rotated = alipay_img.rotate(180)

        # 右下角区域:取支付宝二维码的右下角 1/4
        half = size // 2
        alipay_piece = alipay_rotated.crop((0, 0, half, half))

        # 将支付宝小块贴到微信二维码的右下角
        merged = wechat_img.copy()
        merged.paste(alipay_piece, (half, half))

        return merged

    @staticmethod
    def create_final_image(
        qr_img: Image.Image,
        title: str = "扫码支付",
    ) -> Image.Image:
        """
        创建最终输出的完整图片,包含:
        - 顶部品牌区域(微信 + 支付宝标识)
        - 中间二维码
        - 底部提示文字
        """
        qr_size = qr_img.width
        canvas_w = qr_size
        top_h = 80
        bottom_h = 60
        qr_display_size = int(qr_size * 0.6)  # 二维码显示区域
        padding = (qr_size - qr_display_size) // 2

        total_h = top_h + qr_display_size + padding + bottom_h
        canvas = Image.new("RGB", (canvas_w, total_h), "white")
        draw = ImageDraw.Draw(canvas)

        # ── 顶部品牌区域 ──
        draw.rectangle([0, 0, canvas_w, top_h], fill="white")

        # 微信图标 (绿色方块)
        icon_size = 60
        icon_gap = 20
        total_icons_w = icon_size * 2 + icon_gap
        start_x = (canvas_w - total_icons_w) // 2
        icon_y = (top_h - icon_size) // 2

        draw.rounded_rectangle(
            [start_x, icon_y, start_x + icon_size, icon_y + icon_size],
            radius=12, fill="#07C160"
        )
        draw.rounded_rectangle(
            [start_x + icon_size + icon_gap, icon_y,
             start_x + icon_size * 2 + icon_gap, icon_y + icon_size],
            radius=12, fill="#1677FF"
        )

        # 图标文字
        try:
            font_icon = ImageFont.truetype("msyh.ttc", 28)
        except Exception:
            font_icon = ImageFont.load_default()
        draw.text(
            (start_x + icon_size // 2, icon_y + icon_size // 2),
            "W", fill="white", font=font_icon, anchor="mm"
        )
        draw.text(
            (start_x + icon_size + icon_gap + icon_size // 2, icon_y + icon_size // 2),
            "A", fill="white", font=font_icon, anchor="mm"
        )

        # ── 二维码区域 ──
        qr_y = top_h
        draw.rectangle(
            [padding, qr_y, padding + qr_display_size, qr_y + qr_display_size],
            fill="white", outline="#EEEEEE", width=1
        )

        # 缩放二维码并粘贴
        qr_display = qr_img.resize((qr_display_size, qr_display_size), Image.LANCZOS)
        canvas.paste(qr_display, (padding, qr_y))

        # ── 底部提示 ──
        bottom_y = qr_y + qr_display_size + padding
        try:
            font_bottom = ImageFont.truetype("msyh.ttc", 16)
        except Exception:
            font_bottom = ImageFont.load_default()
        draw.text(
            (canvas_w // 2, bottom_y + bottom_h // 2),
            "付款时请核实账户是否正确",
            fill="#333333", font=font_bottom, anchor="mm"
        )

        return canvas


# ──────────────────────────────────────────────
#  tkinter GUI 主界面
# ──────────────────────────────────────────────
class WeAliPayApp:
    def __init__(self, root: tk.Tk):
        self.root = root
        self.root.title("微信支付宝二合一支付码生成器")
        self.root.geometry("960x720")
        self.root.minsize(800, 600)
        self.root.configure(bg="#F5F5F5")

        self.merged_qr: Image.Image | None = None
        self.final_image: Image.Image | None = None

        self._setup_styles()
        self._build_menu()          # 新增:构建菜单栏
        self._build_ui()

    # ── 样式 ──
    def _setup_styles(self):
        style = ttk.Style()
        style.theme_use("clam")
        style.configure("Primary.TButton", font=("微软雅黑", 11, "bold"),
                        background="#165DFF", foreground="white", borderwidth=0,
                        padding=10)
        style.map("Primary.TButton", background=[("active", "#0040C9")])
        style.configure("Green.TButton", font=("微软雅黑", 10),
                        background="#00B42A", foreground="white", borderwidth=0,
                        padding=8)
        style.map("Green.TButton", background=[("active", "#00871A")])
        style.configure("TLabel", font=("微软雅黑", 10), background="#F5F5F5")
        style.configure("Title.TLabel", font=("微软雅黑", 20, "bold"),
                        background="#F5F5F5", foreground="#1A1A1A")

    # ── 新增菜单栏 ──
    def _build_menu(self):
        menubar = tk.Menu(self.root)
        self.root.config(menu=menubar)

        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="帮助", menu=help_menu)
        help_menu.add_command(label="使用说明", command=self._show_help)
        help_menu.add_command(label="关于", command=self._show_about)
        help_menu.add_separator()
        help_menu.add_command(label="常见问题", command=self._show_faq)

    # ── 帮助内容:使用说明 ──
    def _show_help(self):
        help_text = (
            "📖 使用说明\n\n"
            "1️⃣ 获取收款链接\n"
            "   【微信】打开微信 → 我 → 服务 → 收付款 → 二维码收款 → 保存收款码\n"
            "          → 用微信扫一扫扫描保存的图片 → 点击右上角「…」→ 复制链接\n"
            "   【支付宝】打开支付宝 → 首页「收钱」→ 保存图片\n"
            "            → 用支付宝扫一扫扫描该图片 → 点击「…」→ 复制链接\n\n"
            "2️⃣ 填入链接\n"
            "   将复制的链接粘贴到对应输入框,或点击「上传」按钮选择二维码图片自动解析。\n\n"
            "3️⃣ 选择合并模式\n"
            "   推荐使用「重叠模式 (overlay)」,识别率最高。\n"
            "   其他模式(分割/棋盘)仅作技术演示,实际扫码成功率较低。\n\n"
            "4️⃣ 生成与保存\n"
            "   点击「✨ 生成二合一支付码」,预览无误后下载 PNG 或 JPG 格式。\n\n"
            "⚠️ 注意事项\n"
            "   • 生成的二维码抗污损能力比原始二维码稍弱,请保持右下角区域清洁。\n"
            "   • 确保两个链接均为有效的个人收款码或合规商家码。\n"
            "   • 禁止用于任何违法或违反支付平台规则的行为。\n"
            "   • 本工具纯本地运行,不会上传任何数据,请放心使用。"
        )
        messagebox.showinfo("使用说明", help_text)

    # ── 关于对话框 ──
    def _show_about(self):
        about_text = (
            "微信支付宝二合一支付码生成器\n"
            "版本:1.0\n"
            "基于 Python + tkinter 开发\n"
            "核心原理:利用二维码 H 级纠错能力,\n"
            "         将支付宝码旋转后嵌入微信码右下角。\n\n"
            "仅供学习与合法用途,请勿滥用。\n"
            "© 2026.05"
        )
        messagebox.showinfo("关于", about_text)

    # ── 常见问题 ──
    def _show_faq(self):
        faq_text = (
            "❓ 常见问题\n\n"
            "Q: 为什么我上传二维码图片后没有自动填入链接?\n"
            "A: 需要安装 pyzbar 库才能自动解析。\n"
            "   请运行:pip install pyzbar\n"
            "   如果已安装但仍然失败,可能是图片质量不佳,请手动输入链接。\n\n"
            "Q: 生成的二维码顾客扫不出来怎么办?\n"
            "A: 请确保使用「重叠模式」;检查打印是否清晰、右下角区域有无污损;\n"
            "   也可以尝试增大二维码尺寸(400 或 500)。\n\n"
            "Q: 微信和支付宝的链接是否长期有效?\n"
            "A: 个人收款码链接是长期有效的,但如果更换收款码需要重新生成。\n\n"
            "Q: 我可以商用这个生成的图片吗?\n"
            "A: 如果您的微信和支付宝收款码是合规商家码,可以商用;\n"
            "   若使用个人码进行经营性收款,违反央行规定,请自行承担风险。\n\n"
            "Q: 生成的图片会泄露我的信息吗?\n"
            "A: 不会。程序完全在本地运行,不联网,不存储任何数据。"
        )
        messagebox.showinfo("常见问题", faq_text)

    # ── UI 构建 ──
    def _build_ui(self):
        # 主容器
        main_frame = tk.Frame(self.root, bg="#F5F5F5")
        main_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=15)

        # 标题栏
        header = tk.Frame(main_frame, bg="#F5F5F5")
        header.pack(fill=tk.X, pady=(0, 15))
        tk.Label(header, text="💳 二合一支付码生成器",
                 font=("微软雅黑", 22, "bold"), bg="#F5F5F5", fg="#1A1A1A").pack(side=tk.LEFT)

        # 左右分栏
        panes = tk.PanedWindow(main_frame, orient=tk.HORIZONTAL, bg="#E0E0E0", sashwidth=3)
        panes.pack(fill=tk.BOTH, expand=True)

        left_panel = tk.Frame(panes, bg="white", bd=0, highlightthickness=0)
        right_panel = tk.Frame(panes, bg="white", bd=0, highlightthickness=0)

        panes.add(left_panel, width=420)
        panes.add(right_panel, width=500)

        self._build_left_panel(left_panel)
        self._build_right_panel(right_panel)

    # ── 左侧:输入区 ──
    def _build_left_panel(self, parent: tk.Frame):
        pad = {"padx": 20, "pady": 5}

        # 滚动容器
        canvas = tk.Canvas(parent, bg="white", highlightthickness=0)
        scrollbar = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=canvas.yview)
        scroll_frame = tk.Frame(canvas, bg="white")

        scroll_frame.bind("<Configure>", lambda e: canvas.configure(scrollregion=canvas.bbox("all")))
        canvas.create_window((0, 0), window=scroll_frame, anchor="nw")
        canvas.configure(yscrollcommand=scrollbar.set)

        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 绑定鼠标滚轮
        def _on_mousewheel(event):
            canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")
        canvas.bind_all("<MouseWheel>", _on_mousewheel)

        # ── 收款信息标题 ──
        tk.Label(scroll_frame, text="📋 收款信息", font=("微软雅黑", 14, "bold"),
                 bg="white", fg="#333").pack(anchor=tk.W, **pad)

        # 微信收款链接
        tk.Label(scroll_frame, text="微信收款链接【若上传的收款二维码图片解析失败,可手动输入链接】", bg="white",
                 fg="#555").pack(anchor=tk.W, padx=20, pady=(15, 2))
        wx_frame = tk.Frame(scroll_frame, bg="white")
        wx_frame.pack(fill=tk.X, padx=20)
        self.wechat_url_var = tk.StringVar()
        tk.Entry(wx_frame, textvariable=self.wechat_url_var, font=("微软雅黑", 10),
                 relief=tk.SOLID, bd=1).pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=4)
        tk.Button(wx_frame, text="上传", bg="#07C160", fg="white", font=("微软雅黑", 9),
                  relief=tk.FLAT, padx=10, cursor="hand2",
                  command=lambda: self._upload_qrcode("wechat")).pack(side=tk.RIGHT, padx=(5, 0))

        # 微信提示
        tk.Label(scroll_frame, text="打开微信 → 收付款 → 二维码收款 → 保存图片 → 扫一扫识别 → 复制链接",
                 bg="white", fg="#999", font=("微软雅黑", 8)).pack(anchor=tk.W, padx=20, pady=(2, 10))

        # 支付宝收款链接
        tk.Label(scroll_frame, text="支付宝收款链接【若上传的收款二维码图片解析失败,可手动输入链接】", bg="white",
                 fg="#555").pack(anchor=tk.W, padx=20, pady=(5, 2))
        ali_frame = tk.Frame(scroll_frame, bg="white")
        ali_frame.pack(fill=tk.X, padx=20)
        self.alipay_url_var = tk.StringVar()
        tk.Entry(ali_frame, textvariable=self.alipay_url_var, font=("微软雅黑", 10),
                 relief=tk.SOLID, bd=1).pack(side=tk.LEFT, fill=tk.X, expand=True, ipady=4)
        tk.Button(ali_frame, text="上传", bg="#1677FF", fg="white", font=("微软雅黑", 9),
                  relief=tk.FLAT, padx=10, cursor="hand2",
                  command=lambda: self._upload_qrcode("alipay")).pack(side=tk.RIGHT, padx=(5, 0))

        # 支付宝提示
        tk.Label(scroll_frame, text="打开支付宝 → 收钱 → 保存图片 → 扫一扫识别 → 复制链接",
                 bg="white", fg="#999", font=("微软雅黑", 8)).pack(anchor=tk.W, padx=20, pady=(2, 10))

        # ── 分隔线 ──
        ttk.Separator(scroll_frame, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=20, pady=10)

        # ── 样式设置 ──
        tk.Label(scroll_frame, text="🎨 样式设置", font=("微软雅黑", 14, "bold"),
                 bg="white", fg="#333").pack(anchor=tk.W, **pad)

        # 二维码尺寸
        tk.Label(scroll_frame, text="二维码尺寸", bg="white",
                 fg="#555").pack(anchor=tk.W, padx=20, pady=(10, 2))
        self.size_var = tk.StringVar(value="400")
        size_combo = ttk.Combobox(scroll_frame, textvariable=self.size_var,
                                  values=["300", "400", "500"],
                                  state="readonly", font=("微软雅黑", 10))
        size_combo.pack(fill=tk.X, padx=20, ipady=2)

        # 合并模式
        tk.Label(scroll_frame, text="合并模式【推荐 重叠模式(overlay)】", bg="white",
                 fg="#555").pack(anchor=tk.W, padx=20, pady=(10, 2))
        self.mode_var = tk.StringVar(value="overlay")
        mode_combo = ttk.Combobox(scroll_frame, textvariable=self.mode_var,
                                  values=["overlay 重叠模式", "split 分割模式", "checkerboard 棋盘模式"],
                                  state="readonly", font=("微软雅黑", 10))
        mode_combo.pack(fill=tk.X, padx=20, ipady=2)

        # 自定义标题
        tk.Label(scroll_frame, text="自定义标题", bg="white",
                 fg="#555").pack(anchor=tk.W, padx=20, pady=(10, 2))
        self.title_var = tk.StringVar(value="扫码支付")
        tk.Entry(scroll_frame, textvariable=self.title_var, font=("微软雅黑", 10),
                 relief=tk.SOLID, bd=1).pack(fill=tk.X, padx=20, ipady=4)

        # ── 生成按钮 ──
        tk.Button(scroll_frame, text="✨ 生成二合一支付码",
                  bg="#165DFF", fg="white", font=("微软雅黑", 13, "bold"),
                  relief=tk.FLAT, cursor="hand2", pady=12,
                  command=self._generate).pack(fill=tk.X, padx=20, pady=(25, 8))

        tk.Label(scroll_frame, text="注意:收款信息仅用于生成,不会被存储",
                 bg="white", fg="#AAA", font=("微软雅黑", 8)).pack(pady=(0, 15))

    # ── 右侧:预览区 ──
    def _build_right_panel(self, parent: tk.Frame):
        tk.Label(parent, text="👁️ 预览与下载", font=("微软雅黑", 14, "bold"),
                 bg="white", fg="#333").pack(anchor=tk.W, padx=20, pady=(15, 10))

        # 预览画布
        self.preview_canvas = tk.Canvas(parent, bg="#FAFAFA", highlightthickness=1,
                                        highlightbackground="#E0E0E0")
        self.preview_canvas.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 10))

        # 占位提示
        self.preview_canvas.create_text(
            230, 200, text="请输入微信和支付宝收款链接\n然后点击「生成二合一支付码」",
            font=("微软雅黑", 11), fill="#BBB", justify=tk.CENTER
        )

        # 下载按钮区域
        btn_frame = tk.Frame(parent, bg="white")
        btn_frame.pack(fill=tk.X, padx=20, pady=(0, 15))

        tk.Button(btn_frame, text="📥 下载 PNG",
                  bg="#00B42A", fg="white", font=("微软雅黑", 11),
                  relief=tk.FLAT, cursor="hand2", pady=8,
                  command=lambda: self._download("png")).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        tk.Button(btn_frame, text="📥 下载 JPG",
                  bg="#FF7D00", fg="white", font=("微软雅黑", 11),
                  relief=tk.FLAT, cursor="hand2", pady=8,
                  command=lambda: self._download("jpg")).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))

    # ── 上传二维码 ──
    def _upload_qrcode(self, target: str):
        path = filedialog.askopenfilename(
            title="选择二维码图片",
            filetypes=[("图片文件", "*.png *.jpg *.jpeg *.bmp"), ("所有文件", "*.*")]
        )
        if not path:
            return

        try:
            from PIL import Image as PILImage
            img = PILImage.open(path)
            # 尝试用 pyzbar 解析二维码
            try:
                from pyzbar.pyzbar import decode
                results = decode(img)
                if results:
                    url = results[0].data.decode("utf-8")
                    if target == "wechat":
                        self.wechat_url_var.set(url)
                    else:
                        self.alipay_url_var.set(url)
                    messagebox.showinfo("识别成功", f"已识别链接:\n{url}")
                else:
                    messagebox.showwarning("未识别", "未能从图片中识别出二维码,请手动输入链接。")
            except ImportError:
                messagebox.showinfo("提示",
                    "未安装 pyzbar 库,无法自动解析二维码。\n"
                    "请手动输入链接,或运行: pip install pyzbar")
        except Exception as e:
            messagebox.showerror("错误", f"读取图片失败: {e}")

    # ── 生成 ──
    def _generate(self):
        wechat_url = self.wechat_url_var.get().strip()
        alipay_url = self.alipay_url_var.get().strip()
        title = self.title_var.get().strip() or "扫码支付"

        if not wechat_url or not alipay_url:
            messagebox.showwarning("提示", "请输入微信和支付宝收款链接")
            return

        try:
            size = int(self.size_var.get())
        except ValueError:
            size = 400

        mode_raw = self.mode_var.get()
        # 目前以重叠模式为主,其他模式可扩展
        if "split" in mode_raw:
            mode = "split"
        elif "checkerboard" in mode_raw:
            mode = "checkerboard"
        else:
            mode = "overlay"

        # 显示加载状态
        self.preview_canvas.delete("all")
        self.preview_canvas.create_text(
            230, 200, text="⏳ 正在生成,请稍候...",
            font=("微软雅黑", 12), fill="#999"
        )
        self.root.update_idletasks()

        try:
            if mode == "overlay":
                merged = QRCodeEngine.merge_qrcodes(wechat_url, alipay_url, size, title)
            elif mode == "split":
                merged = self._merge_split(wechat_url, alipay_url, size)
            else:
                merged = self._merge_checkerboard(wechat_url, alipay_url, size)

            self.merged_qr = merged
            self.final_image = QRCodeEngine.create_final_image(merged, title)

            self._show_preview()
        except Exception as e:
            self.preview_canvas.delete("all")
            self.preview_canvas.create_text(
                230, 200, text=f"❌ 生成失败\n{str(e)[:60]}",
                font=("微软雅黑", 11), fill="#E74C3C", justify=tk.CENTER
            )

    def _merge_split(self, wx_url: str, ali_url: str, size: int) -> Image.Image:
        """分割模式:左右各一半"""
        wx_img = QRCodeEngine.generate_qr(wx_url, size)
        ali_img = QRCodeEngine.generate_qr(ali_url, size)
        half = size // 2
        merged = Image.new("RGB", (size, size), "white")
        merged.paste(wx_img.crop((0, 0, half, size)), (0, 0))
        merged.paste(ali_img.crop((half, 0, size, size)), (half, 0))
        return merged

    def _merge_checkerboard(self, wx_url: str, ali_url: str, size: int) -> Image.Image:
        """棋盘模式:8x8 格子交替"""
        wx_img = QRCodeEngine.generate_qr(wx_url, size)
        ali_img = QRCodeEngine.generate_qr(ali_url, size)
        merged = Image.new("RGB", (size, size), "white")
        grid = 8
        cell = size // grid
        for r in range(grid):
            for c in range(grid):
                src = wx_img if (r + c) % 2 == 0 else ali_img
                box = (c * cell, r * cell, (c + 1) * cell, (r + 1) * cell)
                merged.paste(src.crop(box), box)
        return merged

    # ── 预览显示 ──
    def _show_preview(self):
        if self.final_image is None:
            return

        self.preview_canvas.delete("all")
        cw = self.preview_canvas.winfo_width()
        ch = self.preview_canvas.winfo_height()

        if cw < 10:
            cw = 480
        if ch < 10:
            ch = 500

        # 缩放适应画布
        img = self.final_image.copy()
        iw, ih = img.size
        scale = min((cw - 20) / iw, (ch - 20) / ih, 1.0)
        new_w, new_h = int(iw * scale), int(ih * scale)
        img = img.resize((new_w, new_h), Image.LANCZOS)

        self._preview_tk = ImageTk.PhotoImage(img)
        x = (cw - new_w) // 2
        y = (ch - new_h) // 2
        self.preview_canvas.create_image(x, y, anchor=tk.NW, image=self._preview_tk)

    # ── 下载 ──
    def _download(self, fmt: str):
        if self.final_image is None:
            messagebox.showwarning("提示", "请先生成二维码")
            return

        path = filedialog.asksaveasfilename(
            title=f"保存为 {fmt.upper()}",
            defaultextension=f".{fmt}",
            filetypes=[(f"{fmt.upper()} 文件", f"*.{fmt}")]
        )
        if not path:
            return

        try:
            if fmt == "jpg":
                self.final_image.convert("RGB").save(path, "JPEG", quality=95)
            else:
                self.final_image.save(path, "PNG")
            messagebox.showinfo("成功", f"已保存到:\n{path}")
        except Exception as e:
            messagebox.showerror("错误", f"保存失败: {e}")


# ──────────────────────────────────────────────
#  入口
# ──────────────────────────────────────────────
def main():
    root = tk.Tk()
    app = WeAliPayApp(root)

    # 窗口居中
    root.update_idletasks()
    sw = root.winfo_screenwidth()
    sh = root.winfo_screenheight()
    w = root.winfo_width()
    h = root.winfo_height()
    x = (sw - w) // 2
    y = (sh - h) // 2
    root.geometry(f"+{x}+{y}")

    root.mainloop()


if __name__ == "__main__":
    main()

更多推荐