支付宝支付(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 场景见 网页支付。
| 项目 | 说明 |
|---|---|
| 开放平台账号 | 在 支付宝开放平台 注册企业/个人开发者 |
| 应用 AppID | 创建应用后获得(形如 2016xxxxxxxxxx) |
| 应用私钥 | 开发者本地生成,用于请求签名(RSA2/SHA256) |
| 支付宝公钥 | 将应用公钥上传平台后,平台颁发,用于验签通知 |
| 商户账号 | 签约"电脑网站支付 / 手机网站支付 / App 支付"产品后获得收款能力 |
| 收款账户 | 支付宝账户,用于接收货款 |
开发阶段使用沙箱环境(
openapi-sandbox.dl.alipaydev.com),可申请测试 AppID 与测试账户,无需真实签约。
# 生成 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)。
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} 查询最终状态
npm install alipay-sdk
// 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',
});
// src/services/alipay_service.js
const { v4: uuidv4 } = require('uuid');
/**
* 生成支付宝 App 支付调起参数(orderStr)
* @param {string} outTradeNo 商户订单号(唯一)
* @param {number} totalAmount 金额,单位:元
* @param {string} subject 订单标题
* @returns {Promise<string>} 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;
}
// 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 次,间隔递增)。
// 用于客户端回前端后确认结果,或对账
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, ... }
}
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, ... }
}
# pubspec.yaml
dependencies:
# 阿里官方无官方 Flutter 插件,常用社区插件 flutter_alipay
# flutter_alipay: ^2.3.0
iOS 需要在 Info.plist 配置 URL Scheme(alipay{AppID}):
<!-- ios/Runner/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>alipay2021xxxxxxxxxx</string>
</array>
<key>CFBundleURLName</key>
<string>alipay</string>
</dict>
</array>
// lib/service/pay_service.dart
import 'package:flutter_alipay/flutter_alipay.dart';
/// 发起支付宝支付
Future<void> 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();
}
}
| 来源 | 可靠性 | 用途 |
|---|---|---|
FlutterAlipay.pay 返回值 |
低(可能丢失/篡改) | 仅做 UI 提示 |
| 支付宝异步通知 | 高(服务端验签) | 订单状态的最终依据 |
主动查询 alipay.trade.query |
高 | 兜底、对账 |
最佳实践:客户端收到 9000 后,必须再次请求后端 /v1/payment/{payment_id} 获取最终状态。
POST /v1/payment/alipay/
请求:
{ "product_id": "p001", "source": "app" }
响应(200):
{
"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)。
GET /v1/payment/{payment_id}
响应:
{ "success": true, "note": "支付成功" }
对应前端模型
PaymentStatus。
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 |
sign,验签失败返回 failure。total_amount 必须与本地订单金额一致。out_trade_no 多次通知只处理一次;用订单状态机 + 唯一键(channel_trade_no)保证。success,否则平台会重复通知,造成重复发货风险。| 项 | 沙箱值 |
|---|---|
| 网关 | https://openapi-sandbox.dl.alipaydev.com/gateway.do |
| AppID | 沙箱应用(开放平台沙箱环境生成) |
| 买家账号 | 沙箱提供的测试支付宝账号(可在沙箱控制台查看) |
| 支付方式 | 登录沙箱支付宝 App 后可用虚拟余额 |
| 问题 | 原因/解决 |
|---|---|
验签失败 |
支付宝公钥配置错误,或回调字段被中间层改写(如 body 解析方式不对) |
isv.INVALID_PARAMETER |
参数格式错误,如金额必须为两位小数、product_code 错误 |
| App 内拉起后返回"应用不存在" | iOS URL Scheme / Android 包名签名与开放平台配置不一致 |
| 收不到异步通知 | 回调地址未配置为公网 HTTPS;或回调超时/响应非 success |
| 金额不一致 | 前端提交金额被修改,务必服务端下单与回调双重校验 |