Electron 桌面软件集成二维码支付应选当面付还是订单码支付?
结论
这是线上软件会员收费,不属于典型的线下门店收款场景。选择支付产品时,应优先申请适用于线上服务的软件类支付能力,例如电脑网站支付、网页支付,或支付机构提供的线上订单支付产品。
如果希望直接在 Electron 程序内显示二维码,可以使用支持”创建订单并返回动态二维码”的接口,例如支付宝体系中的 alipay.trade.precreate。不过,该接口通常属于当面付能力。没有实体门店的软件商户能否接入,仍要以支付平台当前的准入规则和审核结果为准。
如果”订单码支付”指面向线下商户的固定订单码、门店码或收银台产品,它也未必适合销售软件 VIP。不能只看产品能否生成二维码,还要先确认它是否允许销售软件服务、数字化权益或会员服务。
没有实体店铺不代表一定不能申请支付服务,但以线下经营场所为主要审核条件的产品可能无法通过。网络经营场所地址、软件界面、官方网站、定价页面和服务协议能否作为经营证明,需要向对应支付平台确认。不要使用虚构的门店地址申请。
为什么不建议直接按”二维码收款”选产品
二维码只是支付页面的一种展示形式。选择产品时,更需要考虑交易场景和商户资质:
- 当面付通常用于收银台、餐饮和零售等线下场景。
- “订单码支付”在不同支付平台上的含义并不相同,有些仍属于线下门店产品。
- Electron 软件的 VIP 属于线上软件服务或数字化权益。
- 支付平台不仅审核接口申请,还会核查营业执照、经营范围、实际售卖内容、展示页面和售后机制。
有些线下产品在技术上也能返回二维码,但如果申报场景与实际经营模式不一致,仍可能遇到审核不通过、交易受限或被要求补充材料等问题。
可以按以下顺序处理:
- 等待营业执照登记完成。
- 在商户平台按照”软件开发、软件服务、会员服务”等真实业务申请。
- 向支付平台客服说明实际场景:Electron 桌面软件销售 VIP,用户在软件中下单并扫码付款,没有实体门店。
- 询问当前可以签约哪些线上支付产品,以及是否允许调用动态二维码下单接口。
- 根据最终获批的能力,决定在程序内展示二维码,还是跳转到系统浏览器完成支付。
如果线上支付产品只提供网页收银台,可以让 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 文件,几乎一定会被绕过。
备注:内容仅供参考。