PAYATHON 2026

Electron 桌面软件集成二维码支付应选当面付还是订单码支付?

支付阿杰

结论

这是线上软件会员收费,不属于典型的线下门店收款场景。选择支付产品时,应优先申请适用于线上服务的软件类支付能力,例如电脑网站支付、网页支付,或支付机构提供的线上订单支付产品。

如果希望直接在 Electron 程序内显示二维码,可以使用支持”创建订单并返回动态二维码”的接口,例如支付宝体系中的 alipay.trade.precreate。不过,该接口通常属于当面付能力。没有实体门店的软件商户能否接入,仍要以支付平台当前的准入规则和审核结果为准。

如果”订单码支付”指面向线下商户的固定订单码、门店码或收银台产品,它也未必适合销售软件 VIP。不能只看产品能否生成二维码,还要先确认它是否允许销售软件服务、数字化权益或会员服务。

没有实体店铺不代表一定不能申请支付服务,但以线下经营场所为主要审核条件的产品可能无法通过。网络经营场所地址、软件界面、官方网站、定价页面和服务协议能否作为经营证明,需要向对应支付平台确认。不要使用虚构的门店地址申请。

为什么不建议直接按”二维码收款”选产品

二维码只是支付页面的一种展示形式。选择产品时,更需要考虑交易场景和商户资质:

  • 当面付通常用于收银台、餐饮和零售等线下场景。
  • “订单码支付”在不同支付平台上的含义并不相同,有些仍属于线下门店产品。
  • Electron 软件的 VIP 属于线上软件服务或数字化权益。
  • 支付平台不仅审核接口申请,还会核查营业执照、经营范围、实际售卖内容、展示页面和售后机制。

有些线下产品在技术上也能返回二维码,但如果申报场景与实际经营模式不一致,仍可能遇到审核不通过、交易受限或被要求补充材料等问题。

可以按以下顺序处理:

  1. 等待营业执照登记完成。
  2. 在商户平台按照”软件开发、软件服务、会员服务”等真实业务申请。
  3. 向支付平台客服说明实际场景:Electron 桌面软件销售 VIP,用户在软件中下单并扫码付款,没有实体门店。
  4. 询问当前可以签约哪些线上支付产品,以及是否允许调用动态二维码下单接口。
  5. 根据最终获批的能力,决定在程序内展示二维码,还是跳转到系统浏览器完成支付。

如果线上支付产品只提供网页收银台,可以让 Electron 调用系统默认浏览器打开支付页面。付款完成后,服务端仍可根据异步通知自动开通 VIP,无须强求在 Electron 窗口内展示原生二维码。

建议的接入架构

支付逻辑必须放在服务端,不能全部写入 Electron。

Electron 客户端
    │
    │ 1. 请求购买某个 VIP 套餐
    ▼
你的 Node.js/PHP 服务端
    │
    │ 2. 创建本地订单
    │ 3. 调用支付平台下单接口
    ▼
支付平台
    │
    │ 4. 返回二维码内容或收银台地址
    ▼
Electron 展示二维码
    │
    │ 用户扫码付款
    ▼
支付平台异步通知你的服务端
    │
    │ 5. 验签、核对订单、更新状态
    ▼
服务端开通 VIP
    │
    │ 6. Electron 查询订单状态
    ▼
客户端刷新会员权限

支付平台私钥、应用密钥和商户密钥绝对不能放进 Electron 安装包。Electron 中的 JavaScript、资源文件和网络请求都可能被分析,因此不能把客户端视为可信环境。

基本接入流程

1. 服务端创建订单

客户端只提交套餐编号,例如 vip_monthly。价格必须由服务端查询,不能采用客户端上传的金额。

服务端生成并保存唯一订单号:

{
  "order_no": "VIP202609130001",
  "user_id": 12345,
  "plan_id": "vip_monthly",
  "amount": "29.00",
  "status": "PENDING"
}

订单金额建议使用十进制字符串,或使用以”分”为单位的整数,避免 JavaScript 浮点数带来的计算误差。

2. 调用支付平台下单

如果获批的是支付宝当面付相关能力,常用接口为:

alipay.trade.precreate

请求一般包含商户订单号、订单金额、订单标题和异步通知地址。不同 SDK 的方法名和参数封装方式可能不同,应以当前官方文档和实际使用的 SDK 为准。

服务端可以向 Electron 返回以下业务结果:

{
  "orderNo": "VIP202609130001",
  "qrCode": "支付平台返回的二维码内容",
  "expiresAt": "2026-09-13T15:30:00+08:00"
}

qrCode 通常是二维码承载的字符串,不一定是图片。Electron 可以通过二维码组件把它渲染成图片。

3. 等待异步通知

支付成功后,支付平台会请求服务端配置的 notify_url。会员开通操作应由这个通知处理接口执行。

下面是结构示例。验签函数需要通过支付平台官方 SDK 实现:

import express from "express";

const app = express();

app.use(express.urlencoded({ extended: false }));

app.post("/payment/notify", async (req, res) => {
  const params = req.body;

  const signatureValid = verifyPaymentNotification(params);
  if (!signatureValid) {
    return res.status(400).send("failure");
  }

  const order = await findOrder(params.out_trade_no);
  if (!order) {
    return res.status(404).send("failure");
  }

  if (params.trade_status !== "TRADE_SUCCESS") {
    return res.send("success");
  }

  if (params.total_amount !== order.amount) {
    return res.status(400).send("failure");
  }

  if (params.app_id !== process.env.PAYMENT_APP_ID) {
    return res.status(400).send("failure");
  }

  await completeOrderAndGrantVip({
    orderNo: order.orderNo,
    transactionId: params.trade_no
  });

  return res.send("success");
});

verifyPaymentNotification 只是示意名称,不能用普通的字符串比较来代替验签。必须按照支付平台规定的签名算法、字符集和参数排序规则进行校验,最好直接使用官方 SDK。

4. Electron 查询订单状态

Electron 可以每隔两三秒查询一次自己的服务端:

async function waitForPayment(orderNo) {
  const timer = setInterval(async () => {
    const response = await fetch(
      `https://api.example.com/orders/${encodeURIComponent(orderNo)}`
    );

    const order = await response.json();

    if (order.status === "PAID") {
      clearInterval(timer);
      showVipActivated();
    }
  }, 3000);
}

轮询的作用只是及时更新界面,不能作为确认支付成功的依据。即使客户端伪造出”支付成功”状态,也不能因此获得会员权限。最终权限必须由服务端保存的订单状态决定。

如何安全开通 VIP

处理支付回调时,至少要检查以下内容:

  • 回调签名是否有效。
  • 商户应用 ID 和商户号是否属于自己。
  • 商户订单号是否存在。
  • 支付金额和币种是否与本地订单一致。
  • 订单对应的用户和套餐是否正确。
  • 交易状态是否确实表示支付成功。
  • 支付平台交易号是否已经绑定到其他订单。
  • 同一订单是否已经处理过。

会员开通和订单更新最好放在同一个数据库事务中,并做好幂等处理:

BEGIN;

SELECT status
FROM orders
WHERE order_no = ?
FOR UPDATE;

UPDATE orders
SET status = 'PAID',
    transaction_id = ?,
    paid_at = CURRENT_TIMESTAMP
WHERE order_no = ?
  AND status = 'PENDING';

UPDATE users
SET vip_expires_at = ?
WHERE id = ?;

COMMIT;

实际代码还需要检查订单状态是否更新成功。支付平台可能重复发送通知,同一个订单即使收到十次通知,也只能开通一次,不能把 VIP 时长重复累加十次。

审核前可以准备什么

没有实体办公地址时,应如实提交能够证明实际业务的材料,例如:

  • 营业执照及真实登记信息。
  • 软件官网或产品介绍页面。
  • Electron 程序的主要界面截图。
  • VIP 功能、价格和有效期说明。
  • 用户协议、会员服务协议和隐私政策。
  • 退款规则、客服联系方式和投诉渠道。
  • 软件下载安装方式或供审核使用的体验账号。
  • 业务模式说明,明确销售的是软件服务,不是实物商品。

如果申请页面强制要求门头照、店内照或收银台照片等线下材料,通常意味着这个申请入口面向实体经营场景。此时应联系平台客服,改用线上经营类目或相应产品,不要上传无关照片,也不要编造经营场所。

还需要注意的事项

VIP 套餐应明确说明有效期、是否自动续费以及退款条件。如果没有单独取得代扣或周期扣款能力,不要自行实现”自动续费”。

二维码订单应设置合理的过期时间。订单过期后应重新创建,不能无限期复用旧二维码。

异步通知地址必须是支付平台可以访问的 HTTPS 地址。开发阶段可以使用测试环境或沙箱,本机的 localhost 无法直接接收支付平台回调。

服务端还需要提供主动查单机制。如果异步通知暂时失败,可以调用支付平台的官方订单查询接口补查,但不能只依赖 Electron 客户端报告的付款结果。

会员权限最终应保存在服务端。Electron 登录后,从服务端获取当前会员状态和到期时间,并在调用付费接口时再次鉴权。只在本地写入一个 isVip: true 文件,几乎一定会被绕过。

备注:内容仅供参考。