alipay.md 11 KB

支付宝支付技术文档

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 场景见 网页支付

2. 申请与配置

2.1 需要申请的内容

项目 说明
开放平台账号 支付宝开放平台 注册企业/个人开发者
应用 AppID 创建应用后获得(形如 2016xxxxxxxxxx
应用私钥 开发者本地生成,用于请求签名(RSA2/SHA256)
支付宝公钥 将应用公钥上传平台后,平台颁发,用于验签通知
商户账号 签约"电脑网站支付 / 手机网站支付 / App 支付"产品后获得收款能力
收款账户 支付宝账户,用于接收货款

开发阶段使用沙箱环境openapi-sandbox.dl.alipaydev.com),可申请测试 AppID 与测试账户,无需真实签约。

2.2 密钥生成

# 生成 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. 支付流程

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 安装与初始化

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',
});

4.2 统一下单(App 支付)

// 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;
}

4.3 异步通知回调(验签)

// 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');
  });

失败返回约定:支付宝要求回调返回 successfailure(纯文本)。返回 failure 会触发平台按策略重试(共 24 次,间隔递增)。

4.4 主动查询订单

// 用于客户端回前端后确认结果,或对账
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 退款(可选)

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 依赖与初始化

# 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>

5.2 拉起支付宝支付

// 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();
  }
}

5.3 客户端同步结果与异步通知的关系

来源 可靠性 用途
FlutterAlipay.pay 返回值 低(可能丢失/篡改) 仅做 UI 提示
支付宝异步通知 高(服务端验签) 订单状态的最终依据
主动查询 alipay.trade.query 兜底、对账

最佳实践:客户端收到 9000 后,必须再次请求后端 /v1/payment/{payment_id} 获取最终状态。

6. 接口定义

6.1 创建支付宝支付

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
}

对应前端模型 OtherPayCreatedReponseparams / payment_id / sandbox)。

6.2 查询支付状态

GET /v1/payment/{payment_id}

响应:

{ "success": true, "note": "支付成功" }

对应前端模型 PaymentStatus

6.3 异步通知

POST /v1/payment/notify/alipayapplication/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
金额不一致 前端提交金额被修改,务必服务端下单与回调双重校验