# 异步通知处理 - PHP 示例

适用产品：AI 网页应用收款、AI 移动应用收款。AI 按量付费不使用异步通知。

```php
<?php

// 支付宝通知是 POST 表单，不是 JSON。
// $deps 接入项目真实 SDK 验签适配器、支付配置、订单仓储、通知事件仓储和脱敏日志。
function handleAlipayNotify(array $params, array $deps): void
{
    try {
        $config = $deps['paymentConfigRepository']->findByAppId($params['app_id'] ?? '');
        if ($config === null) {
            fail($deps, 'alipay notify app config not found', $params);
        }

        if (!verifyNotifyWithAlipaySdk($params, $deps['alipayNotifyVerifier'], $config)) {
            fail($deps, 'alipay notify signature check failed', $params);
        }

        $order = $deps['orderRepository']->findByOutTradeNo($params['out_trade_no'] ?? '');
        if ($order === null || !businessFieldsMatch($order, $params, $config)) {
            fail($deps, 'alipay notify business check failed', $params);
        }

        $inserted = $deps['notifyEventRepository']->insertOnce([
            'notify_id' => $params['notify_id'] ?? '',
            'trade_no' => $params['trade_no'] ?? '',
            'out_trade_no' => $params['out_trade_no'] ?? '',
            'trade_status' => $params['trade_status'] ?? '',
            'raw_params' => $params,
        ]);
        if (!$inserted) {
            respondPlainText('success');
        }

        if (isPaidTradeNotification($params)) {
            $deps['orderRepository']->markPaidIdempotently($order['out_trade_no'], $params['trade_no'] ?? '', $params);
        } else {
            $deps['orderRepository']->recordNonPaidTradeEvent($order['out_trade_no'], $params['trade_status'] ?? '', $params);
        }

        respondPlainText('success');
    } catch (Throwable $e) {
        safeLogNotifyError($deps, 'alipay notify unexpected exception', $params);
        respondPlainText('fail');
    }
}

function businessFieldsMatch(array $order, array $params, array $config): bool
{
    return ($params['app_id'] ?? '') === ($config['app_id'] ?? '')
        && ($params['out_trade_no'] ?? '') === $order['out_trade_no']
        && amountEquals($order['total_amount'], $params['total_amount'] ?? null)
        && expectedSellerMatches($config, $params['seller_id'] ?? null, $params['seller_email'] ?? null);
}

function isPaidTradeNotification(array $params): bool
{
    $paidStatus = ($params['trade_status'] ?? '') === 'TRADE_SUCCESS'
        || ($params['trade_status'] ?? '') === 'TRADE_FINISHED';
    return $paidStatus
        && empty($params['out_biz_no'])
        && empty($params['gmt_refund'])
        && empty($params['refund_fee']);
}

function amountEquals($expected, $actual): bool
{
    if ($actual === null) {
        return false;
    }
    $expectedAmount = normalizeAmountText($expected);
    $actualAmount = normalizeAmountText($actual);
    return $expectedAmount !== null && $expectedAmount === $actualAmount;
}

function normalizeAmountText($value): ?string
{
    $text = trim((string) $value);
    if (!preg_match('/^(\d+)(?:\.(\d{1,2}))?$/', $text, $matches)) {
        return null;
    }
    $yuan = ltrim($matches[1], '0');
    return ($yuan === '' ? '0' : $yuan) . '.' . str_pad($matches[2] ?? '', 2, '0');
}

function expectedSellerMatches(array $config, ?string $sellerId, ?string $sellerEmail): bool
{
    $expectedSellerId = firstNonBlank($config['seller_id'] ?? null, null);
    $expectedSellerEmail = firstNonBlank($config['seller_email'] ?? null, null);
    return ($expectedSellerId !== null && $sellerId === $expectedSellerId)
        || ($expectedSellerEmail !== null && $sellerEmail === $expectedSellerEmail);
}

function firstNonBlank(?string $primary, ?string $fallback): ?string
{
    if ($primary !== null && trim($primary) !== '') {
        return $primary;
    }
    if ($fallback !== null && trim($fallback) !== '') {
        return $fallback;
    }
    return null;
}

function verifyNotifyWithAlipaySdk(array $params, object $verifier, array $config): bool
{
    // 使用当前项目已安装的支付宝 SDK 验签能力校验 $params['sign']。
    // $verifier->verify($params, $config) 必须封装目标项目当前 SDK 实际通知验签 API；落地前先查已安装包文档或类型定义，不能手写临时 RSA 验签。
    return $verifier->verify($params, $config);
}

function fail(array $deps, string $message, array $params): void
{
    safeLogNotifyError($deps, $message, $params);
    respondPlainText('fail');
}

function respondPlainText(string $body): void
{
    header('Content-Type: text/plain; charset=UTF-8');
    echo $body;
    exit;
}

function safeLogNotifyError(array $deps, string $message, array $params): void
{
    try {
        if (isset($deps['logger'])) {
            $deps['logger']->warning($message, sanitizeNotifyParams($params));
        }
    } catch (Throwable $e) {
        // 日志失败不能影响支付宝收到纯文本 fail。
    }
}

function sanitizeNotifyParams(array $params): array
{
    unset($params['sign']);
    return $params;
}

// 在项目路由入口中由 DI 容器提供 $deps 后调用：
// handleAlipayNotify($_POST, $deps);
```

要点：

- 支付宝通知是 POST 表单，验签前必须保留完整 `$_POST` 参数。
- 验签必须调用当前项目支付宝 SDK 的实际验签 API，验签前不得丢字段或改值。
- `app_id`、`seller_id` / `seller_email` 从运行时支付配置校验，不从订单对象默认取值。
- 金额校验使用字符串或定点数规范化，不要用浮点数直接比较，也不要默认依赖未安装的 PHP 扩展。
- 只有付款成功状态且不是退款、关单、分账等事件时，才认定付款成功。
- 内存订单或内存幂等只能用于非生产 demo，生产必须使用持久化订单表和通知事件表。
- 处理成功后返回纯文本 `success`；异常、验签失败或业务校验失败返回 `fail`。
