# 支付宝支付技术文档 ## 1. 概述 支付宝支付(Alipay)是面向中国大陆用户的国民级支付方式。本文档覆盖三种主流场景: | 场景 | 接口 | 适用端 | 说明 | | --- | --- | --- | --- | | **App 支付** | `alipay.trade.app.pay` | iOS / Android App | 拉起支付宝 App 完成支付,返回原 App | | **手机网站支付** | `alipay.trade.wap.pay` | 移动端 H5 / Web | 在 H5 页面跳转支付宝完成支付 | | **PC 网站支付** | `alipay.trade.page.pay` | PC Web | 生成收银台页面,支持扫码或登录支付 | 本项目(Flutter App)使用 **App 支付**(`alipay.trade.app.pay`)。Web 场景见 [网页支付](web_payment.md)。 ## 2. 申请与配置 ### 2.1 需要申请的内容 | 项目 | 说明 | | --- | --- | | 开放平台账号 | 在 [支付宝开放平台](https://open.alipay.com) 注册企业/个人开发者 | | 应用 AppID | 创建应用后获得(形如 `2016xxxxxxxxxx`) | | 应用私钥 | 开发者本地生成,用于请求签名(RSA2/SHA256) | | 支付宝公钥 | 将应用公钥上传平台后,平台颁发,用于验签通知 | | 商户账号 | 签约"电脑网站支付 / 手机网站支付 / App 支付"产品后获得收款能力 | | 收款账户 | 支付宝账户,用于接收货款 | > 开发阶段使用**沙箱环境**(`openapi-sandbox.dl.alipaydev.com`),可申请测试 AppID 与测试账户,无需真实签约。 ### 2.2 密钥生成 ```bash # 生成 RSA 密钥对(PKCS8) openssl genrsa -out app_private_key.pem 2048 openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem # 得到应用公钥后上传开放平台,换取支付宝公钥 alipay_public_key.pem ``` > 签名算法固定 `RSA2`(SHA256withRSA)。私钥格式需与 SDK 配置的 `keyType` 一致(PKCS1/PKCS8)。 ## 3. 支付流程 ```mermaid sequenceDiagram participant App as Flutter App participant Backend as 业务后端 participant Alipay as 支付宝服务端 App->>Backend: POST /v1/payment/alipay/ {product_id} Backend->>Backend: 创建订单、计算金额(服务端为准) Backend->>Alipay: alipay.trade.app.pay(签名) Alipay-->>Backend: 返回 orderStr(签名串) Backend-->>App: { params: orderStr, payment_id } App->>Alipay: 拉起支付宝App(FlutterAlipay.pay(orderStr)) Alipay->>Backend: 异步通知(notify_url, 验签) Backend->>Backend: 验签 + 幂等更新订单 + 发货 Alipay-->>App: 支付结果回调(客户端同步结果,仅作参考) App->>Backend: GET /v1/payment/{payment_id} 查询最终状态 ``` ## 4. 后端集成(Node.js/Express) ### 4.1 安装与初始化 ```bash npm install alipay-sdk ``` ```javascript // src/services/alipay_service.js const AlipaySdk = require('alipay-sdk').default; const fs = require('fs'); // 单例初始化 const alipaySdk = new AlipaySdk({ appId: process.env.ALIPAY_APP_ID, // 应用 ID privateKey: fs.readFileSync(process.env.ALIPAY_APP_PRIVATE_KEY, 'ascii'), // 应用私钥 alipayPublicKey: fs.readFileSync(process.env.ALIPAY_PUBLIC_KEY, 'ascii'), // 支付宝公钥 keyType: 'PKCS8', // 沙箱环境打开下面一行,生产注释掉 // endpoint: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do', // 生产环境使用证书方式(推荐): // alipayRootCertPath: '/path/alipayRootCert.crt', // alipayPublicCertPath: '/path/alipayCertPublicKey_RSA2.crt', // appCertPath: '/path/appCertPublicKey.crt', }); ``` ### 4.2 统一下单(App 支付) ```javascript // src/services/alipay_service.js const { v4: uuidv4 } = require('uuid'); /** * 生成支付宝 App 支付调起参数(orderStr) * @param {string} outTradeNo 商户订单号(唯一) * @param {number} totalAmount 金额,单位:元 * @param {string} subject 订单标题 * @returns {Promise} orderStr */ async function createAppPayment({ outTradeNo, totalAmount, subject }) { const orderStr = await alipaySdk.sdkExecute('alipay.trade.app.pay', { // 异步通知回调地址(公网可达 HTTPS) notifyUrl: process.env.ALIPAY_NOTIFY_URL, bizContent: { out_trade_no: outTradeNo, product_code: 'FAST_INSTANT_TRADE_PAY', // App 支付固定值 total_amount: totalAmount.toFixed(2), subject, }, }); return orderStr; } ``` ### 4.3 异步通知回调(验签) ```javascript // src/controllers/payment_controller.js const express = require('express'); const router = express.Router(); /** * 支付宝异步通知回调 * 注意:通知为 application/x-www-form-urlencoded,不能用 express.json() */ router.post('/v1/payment/notify/alipay', express.urlencoded({ extended: true }), async (req, res) => { // 1. 校验签名(不通过直接返回 failure) const params = req.body; const isSignOk = alipaySdk.checkNotifySign(params); if (!isSignOk) { console.warn('[alipay] 验签失败', params); return res.send('failure'); } // 2. 幂等处理:用 out_trade_no 加锁 / 查订单状态判断 const { out_trade_no, trade_status, total_amount, trade_no } = params; const order = await orderRepo.findById(out_trade_no); // 3. 校验金额(防篡改) if (order && Number(order.amount) !== Number(total_amount)) { return res.send('failure'); } // 4. 交易成功判定:TRADE_SUCCESS / TRADE_FINISHED if ((trade_status === 'TRADE_SUCCESS' || trade_status === 'TRADE_FINISHED') && order.status === 'PENDING') { await orderService.markPaid(out_trade_no, { channel: 'alipay', channelTradeNo: trade_no, // 支付宝交易号 }); // 5. 触发发货/发放权益(幂等) await fulfillmentService.deliver(order.id); } // 6. 必须回执 "success",否则支付宝会重试通知 res.send('success'); }); ``` > **失败返回约定**:支付宝要求回调返回 `success` 或 `failure`(纯文本)。返回 `failure` 会触发平台按策略重试(共 24 次,间隔递增)。 ### 4.4 主动查询订单 ```javascript // 用于客户端回前端后确认结果,或对账 async function queryTrade(outTradeNo) { const result = await alipaySdk.exec('alipay.trade.query', { bizContent: { out_trade_no: outTradeNo }, }); return result; // { trade_status, trade_no, total_amount, ... } } ``` ### 4.5 退款(可选) ```javascript async function refund({ outTradeNo, refundAmount, refundReason }) { const result = await alipaySdk.exec('alipay.trade.refund', { bizContent: { out_trade_no: outTradeNo, refund_amount: refundAmount.toFixed(2), refund_reason: refundReason, }, }); return result; // { code, msg, refund_fee, ... } } ``` ## 5. 前端集成(Flutter) ### 5.1 依赖与初始化 ```yaml # pubspec.yaml dependencies: # 阿里官方无官方 Flutter 插件,常用社区插件 flutter_alipay # flutter_alipay: ^2.3.0 ``` iOS 需要在 `Info.plist` 配置 URL Scheme(`alipay{AppID}`): ```xml CFBundleURLTypes CFBundleURLSchemes alipay2021xxxxxxxxxx CFBundleURLName alipay ``` ### 5.2 拉起支付宝支付 ```dart // lib/service/pay_service.dart import 'package:flutter_alipay/flutter_alipay.dart'; /// 发起支付宝支付 Future alipay() async { try { // 1. 后端统一下单,返回 orderStr final created = await APIServer().createAlipayPayment( productId: 'your_product_id', source: paymentSource(), ); paymentId = created.paymentId; // 2. 用 orderStr 拉起支付宝 final result = await FlutterAlipay.pay(created.params); // orderStr // result.status: 9000 成功 | 8000 处理中 | 4000 失败 | 6001 用户取消 | 6002 网络错误 if (result.status == 9000) { // 同步结果为成功,仍以后端查询为准 final resp = await APIServer().queryPaymentStatus(paymentId); if (resp.success) { showSuccessMessage(resp.note ?? '支付成功'); } } else if (result.status == 8000) { // 结果确认中,主动查询 await _retryQueryPaymentStatus(paymentId); } else { showErrorMessage('支付失败或取消: ${result.status}'); } } on Exception catch (e) { showErrorMessageEnhanced(context, e); } finally { _closePaymentLoading(); } } ``` ### 5.3 客户端同步结果与异步通知的关系 | 来源 | 可靠性 | 用途 | | --- | --- | --- | | `FlutterAlipay.pay` 返回值 | 低(可能丢失/篡改) | 仅做 UI 提示 | | 支付宝异步通知 | 高(服务端验签) | **订单状态的最终依据** | | 主动查询 `alipay.trade.query` | 高 | 兜底、对账 | **最佳实践**:客户端收到 9000 后,必须再次请求后端 `/v1/payment/{payment_id}` 获取最终状态。 ## 6. 接口定义 ### 6.1 创建支付宝支付 `POST /v1/payment/alipay/` 请求: ```json { "product_id": "p001", "source": "app" } ``` 响应(200): ```json { "params": "method=alipay.trade.app.pay&app_id=2016xxx&sign=...&biz_content=%7B...%7D", "payment_id": "pay_8f3a2b", "sandbox": false } ``` > 对应前端模型 `OtherPayCreatedReponse`(`params` / `payment_id` / `sandbox`)。 ### 6.2 查询支付状态 `GET /v1/payment/{payment_id}` 响应: ```json { "success": true, "note": "支付成功" } ``` > 对应前端模型 `PaymentStatus`。 ### 6.3 异步通知 `POST /v1/payment/notify/alipay`(`application/x-www-form-urlencoded`) | 字段 | 说明 | | --- | --- | | `out_trade_no` | 商户订单号 | | `trade_no` | 支付宝交易号 | | `trade_status` | `WAIT_BUYER_PAY` / `TRADE_CLOSED` / `TRADE_SUCCESS` / `TRADE_FINISHED` | | `total_amount` | 订单金额(元) | | `sign` | RSA2 签名 | | `sign_type` | `RSA2` | ## 7. 安全注意事项 1. **应用私钥只存后端**,切勿写入前端或提交到 Git。 2. **回调必须验签**:使用支付宝公钥验证 `sign`,验签失败返回 `failure`。 3. **回调校验金额**:`total_amount` 必须与本地订单金额一致。 4. **回调处理需幂等**:同一 `out_trade_no` 多次通知只处理一次;用订单状态机 + 唯一键(`channel_trade_no`)保证。 5. **回调必须响应 `success`**,否则平台会重复通知,造成重复发货风险。 6. **仅凭客户端结果不发货**:以异步通知为准,客户端结果仅作展示。 ## 8. 沙箱与测试 | 项 | 沙箱值 | | --- | --- | | 网关 | `https://openapi-sandbox.dl.alipaydev.com/gateway.do` | | AppID | 沙箱应用(开放平台沙箱环境生成) | | 买家账号 | 沙箱提供的测试支付宝账号(可在沙箱控制台查看) | | 支付方式 | 登录沙箱支付宝 App 后可用虚拟余额 | ## 9. 常见问题 | 问题 | 原因/解决 | | --- | --- | | `验签失败` | 支付宝公钥配置错误,或回调字段被中间层改写(如 body 解析方式不对) | | `isv.INVALID_PARAMETER` | 参数格式错误,如金额必须为两位小数、product_code 错误 | | App 内拉起后返回"应用不存在" | iOS URL Scheme / Android 包名签名与开放平台配置不一致 | | 收不到异步通知 | 回调地址未配置为公网 HTTPS;或回调超时/响应非 `success` | | 金额不一致 | 前端提交金额被修改,务必服务端下单与回调双重校验 |