WinForm桌面软件注册收款应签约哪种支付宝支付接口?
明确结论
WinForm 软件在注册时显示支付宝二维码,由用户用手机支付宝扫码付款。这套流程最接近支付宝“当面付”的扫码支付模式,通常使用 alipay.trade.precreate 接口。
能否签约“当面付”,还要看支付宝对商户经营场景、经营类目和资质材料的审核。没有实体门店并不代表一定无法申请,但不能虚构门店,也不能提交与实际业务不符的资料。申请时应如实说明业务流程:公司销售桌面软件或软件授权,PC 客户端显示订单二维码,用户付款后在线获取注册码。
如果商户后台不允许这类经营场景签约“当面付”,可以改用“电脑网站支付”。WinForm 创建订单后,通过系统浏览器打开支付宝收银台,用户在网页中登录或扫码付款。WinForm 是桌面程序,不应按手机 App 申报“App 支付”。
支付宝可能调整产品名称、经营类目和准入要求,最终应以签约页面及支付宝商家客服的审核口径为准。
两种方案如何选择
方案一:当面付扫码支付
典型流程如下:
- WinForm 向企业自己的业务服务器提交购买请求。
- 服务器生成唯一订单号和待支付订单。
- 服务器调用
alipay.trade.precreate。 - 支付宝返回
qr_code。 - WinForm 将其绘制成二维码。
- 用户使用支付宝扫码付款。
- 服务器通过异步通知或
alipay.trade.query确认付款结果。 - 服务器生成注册码并返回给 WinForm。
这种方案不需要用户离开桌面软件,操作更直接。不过,纯线上软件授权是否符合“当面付”的签约条件,仍需支付宝审核确认。
方案二:电脑网站支付
如果无法签约“当面付”,可以使用电脑网站支付。WinForm 本身不必是网站,但支付请求需要由服务端创建,再通过默认浏览器打开支付宝收银台。
var paymentUrl = await CreatePaymentOnServerAsync(orderId);
Process.Start(new ProcessStartInfo
{
FileName = paymentUrl,
UseShellExecute = true
});
付款后,不能只根据浏览器的跳转结果发放注册码。业务服务器仍需验证支付宝异步通知的签名,或者主动查询订单状态。
推荐的系统结构
不要在 WinForm 中保存支付宝应用私钥并直接调用支付接口。桌面软件安装在客户电脑上,程序集容易被反编译。私钥泄露后,攻击者可能伪造请求或破坏交易流程。
建议采用以下结构:
WinForm 客户端
│
│ 创建订单、查询付款结果
▼
企业业务服务器
│
│ 使用应用私钥签名
▼
支付宝开放平台
支付宝异步通知
│
▼
企业业务服务器
│
├── 验签
├── 核对订单号、金额、收款方
├── 更新订单状态
└── 生成注册码
如果软件目前完全离线,也没有业务服务器,建议先增加一个最小化的支付与授权服务。客户端轮询支付宝、解析页面或判断二维码是否消失,都不能可靠证明订单已经付款。
当面付接口示例
下面是服务端调用 alipay.trade.precreate 时的核心业务参数。不同版本的支付宝 .NET SDK 可能使用不同的类名和初始化方式,具体写法应以对应版本的官方说明为准。
var bizContent = new
{
out_trade_no = order.OrderNo,
total_amount = order.Amount.ToString("0.00"),
subject = "桌面软件授权",
timeout_express = "15m"
};
// 服务端调用 alipay.trade.precreate。
// 返回成功后,将 response.qr_code 发送给 WinForm。
// WinForm 负责把 qr_code 内容绘制成二维码。
对应的请求数据大致如下:
{
"out_trade_no": "SW202609130001",
"total_amount": "199.00",
"subject": "桌面软件授权",
"timeout_express": "15m"
}
out_trade_no 在商户系统内必须唯一。金额应由服务端根据商品或授权方案确定,不能采用客户端上传的金额。
支付结果处理示例
服务器收到异步通知后,应先使用支付宝公钥或支付宝公钥证书验签,再核对订单信息:
public async Task<IActionResult> AlipayNotify()
{
var parameters = Request.Form
.ToDictionary(item => item.Key, item => item.Value.ToString());
if (!VerifyAlipaySignature(parameters))
return Content("failure");
string orderNo = parameters["out_trade_no"];
string tradeStatus = parameters["trade_status"];
string totalAmount = parameters["total_amount"];
string sellerId = parameters["seller_id"];
var order = await orderRepository.GetAsync(orderNo);
if (order == null)
return Content("failure");
if (decimal.Parse(
totalAmount,
System.Globalization.CultureInfo.InvariantCulture) != order.Amount)
return Content("failure");
if (sellerId != configuredSellerId)
return Content("failure");
if (tradeStatus is "TRADE_SUCCESS" or "TRADE_FINISHED")
{
// 必须实现幂等处理,重复通知不能重复发放授权。
await CompleteOrderAndIssueLicenseAsync(order);
}
return Content("success");
}
这段代码仅用于说明流程。VerifyAlipaySignature 应使用所选支付宝 SDK 提供的验签功能,不要自行拼接字符串,实现一套不完整的签名算法。
客户端可以定时查询自己的业务服务器,不要直接根据支付宝页面判断支付结果:
private async Task WaitForPaymentAsync(string orderNo)
{
while (true)
{
var result = await QueryOrderFromServerAsync(orderNo);
if (result.Status == "PAID")
{
ShowLicenseCode(result.LicenseCode);
return;
}
if (result.Status is "CLOSED" or "EXPIRED")
{
ShowPaymentExpired();
return;
}
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
如果异步通知延迟或丢失,服务端也可以调用 alipay.trade.query 主动确认交易状态,但查询频率不宜过高。
申请时应准备的材料
申请支付产品时,可以准备能够真实说明经营场景的材料,例如:
- 营业执照及企业支付宝账户资料;
- 软件官网、产品介绍页或下载页面;
- 软件主界面和购买流程截图;
- WinForm 显示支付二维码或跳转收银台的截图;
- 软件授权价格、服务内容及用户协议;
- 售后联系方式、退款规则和隐私政策;
- 软件著作权、销售授权等能够证明业务真实性的材料。
办公室可以作为经营地址,但不能包装成并不存在的线下门店。如果签约页面强制要求填写门店信息,而实际业务不符合要求,应联系支付宝商家客服,说明完整业务流程,由平台判断适用的经营类目和支付产品。
必须注意的问题
-
注册码必须由服务器生成。不要让客户端仅凭“支付成功”字符串计算注册码,否则很容易被绕过。
-
支付金额必须由服务器决定。客户端只能提交商品或套餐标识,服务器根据数据库中的价格创建订单。
-
异步通知必须验签,同时核对
app_id、seller_id、out_trade_no、total_amount和交易状态。 -
订单处理必须支持幂等。支付宝可能重复发送通知,同一订单只能生成一次有效授权。
-
不要在客户端保存应用私钥。支付宝私钥、数据库凭据和注册码生成密钥都应保存在服务器端。
-
二维码应设置有效期。订单过期后应关闭或重新创建,不能无限期重复使用同一个二维码。
-
申请前应确认数字商品类目的准入规则。软件授权、数字化服务等类目可能需要额外材料,也可能受到限制,具体以实际审核结果为准。
可以先按真实业务申请“当面付”,使用 alipay.trade.precreate 实现 WinForm 展码支付。如果支付宝审核认为该业务不属于当面交易,再改用电脑网站支付,通过浏览器完成收银台流程。无论选择哪种方式,下单、验签和注册码发放都应由企业服务器完成。
备注:内容仅供参考。
