PAYATHON 2026

StoreKit 购买触发 NSURLErrorDomain -1001、-1003 和 -1004

排障赵师傅

结论

这些错误来自 NSURLErrorDomain,属于网络错误,并非 StoreKit 私有错误码:

错误码常量含义
-1001NSURLErrorTimedOut请求超时
-1003NSURLErrorCannotFindHost无法解析或找到服务器主机
-1004NSURLErrorCannotConnectToHost找到了主机,但无法建立连接

出现这些错误,通常说明设备与 App Store 或 Sandbox 服务通信失败。代码签名、provisioning profile 和产品配置一般不会直接导致这几个错误。

错误码有明确含义,但仅凭它们无法断定 Sandbox 出现故障。DNS、网络路由、VPN、代理、设备状态或 Apple 服务端异常,都可能产生相同的错误。

addPayment: 本身不会同步返回错误

[[SKPaymentQueue defaultQueue] addPayment:aPayment] 的返回类型是 void。调用后,StoreKit 会异步处理交易,并通过 SKPaymentTransactionObserver 报告状态。

实际的失败信息通常出现在:

- (void)paymentQueue:(SKPaymentQueue *)queue
 updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions

失败交易的 transaction.error 可能属于 SKErrorDomain,底层网络错误则可能位于 NSUnderlyingErrorKey 中。排查时需要同时记录外层错误和底层错误,不能只看 error.code。

- (void)logStoreError:(NSError *)error {
    NSError *currentError = error;

    while (currentError != nil) {
        NSLog(@"domain=%@ code=%ld description=%@ userInfo=%@",
              currentError.domain,
              (long)currentError.code,
              currentError.localizedDescription,
              currentError.userInfo);

        currentError = currentError.userInfo[NSUnderlyingErrorKey];
    }
}

交易监听可以这样处理:

- (void)paymentQueue:(SKPaymentQueue *)queue
 updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
    for (SKPaymentTransaction *transaction in transactions) {
        switch (transaction.transactionState) {
            case SKPaymentTransactionStatePurchased:
                // 完成收据验证和内容发放后再结束交易。
                [queue finishTransaction:transaction];
                break;

            case SKPaymentTransactionStateFailed:
                [self logStoreError:transaction.error];

                if (![transaction.error.domain isEqualToString:SKErrorDomain] ||
                    transaction.error.code != SKErrorPaymentCancelled) {
                    // 提示用户当前购买失败,并允许稍后重试。
                }

                [queue finishTransaction:transaction];
                break;

            case SKPaymentTransactionStateRestored:
                // 恢复内容后结束交易。
                [queue finishTransaction:transaction];
                break;

            case SKPaymentTransactionStatePurchasing:
            case SKPaymentTransactionStateDeferred:
                // 仍在处理或等待批准,不要结束,也不要重复提交。
                break;
        }
    }
}

应用启动后应尽早注册交易观察者,不要等到用户点击购买时才注册:

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];

为什么产品请求成功,购买仍然会失败

SKProductsRequest 成功,说明设备当时能够获取商品元数据,传入的产品标识符也能被识别,但这不能证明后续交易服务可用。

购买过程还可能涉及:

  • App Store 账户或 Sandbox 测试账户认证;
  • 与交易服务器建立新的连接;
  • 处理现有的未完成交易;
  • 创建订单并更新交易队列;
  • 家庭或监护审批;
  • 收据生成和更新。

商品查询和支付交易使用的目标服务、连接时机与状态要求可能不同。即使商品请求成功,后续购买仍可能因 DNS、连接超时或服务端暂时不可用而失败。

商品信息也可能来自缓存,所以商品列表能够显示,并不能证明交易服务器此时可以访问。

建议的排查顺序

1. 检查完整错误链

先确认外层错误的 domain 和 code,再沿 NSUnderlyingErrorKey 查找底层错误。

可以用下面的辅助函数判断错误链中是否包含这三类网络错误:

static BOOL IsTransientStoreNetworkError(NSError *error) {
    NSError *currentError = error;

    while (currentError != nil) {
        if ([currentError.domain isEqualToString:NSURLErrorDomain]) {
            switch (currentError.code) {
                case NSURLErrorTimedOut:
                case NSURLErrorCannotFindHost:
                case NSURLErrorCannotConnectToHost:
                    return YES;

                default:
                    break;
            }
        }

        currentError = currentError.userInfo[NSUnderlyingErrorKey];
    }

    return NO;
}

记录错误时不要只保留数字。缺少错误域,单看 -1001 之类的数字无法确定其含义。

2. 按暂时性网络故障处理

遇到 -1001、-1003 或 -1004 时,可以保留用户当前页面,显示清楚的失败提示,并允许用户稍后重试。

不要在后台反复调用 addPayment:。自动重试可能产生重复的购买请求,也可能掩盖仍处于 Purchasing 或 Deferred 状态的交易。只有交易明确进入 Failed 状态后,才允许用户重新发起购买。

3. 检查设备和网络差异

如果问题只出现在一台设备上,可以检查:

  • 系统日期、时间和时区是否正确,最好启用自动设置;
  • 是否启用了 VPN、HTTP 代理、DNS 过滤或内容过滤软件;
  • 当前网络是否限制 Apple 服务;
  • App Store 或 Sandbox 测试账户是否可用;
  • 队列中是否有尚未处理的旧交易;
  • 重启设备后问题是否仍然存在;
  • 切换蜂窝网络和 Wi-Fi 后,错误是否随网络变化。

清理项目、重新安装 Xcode、重新生成证书或 provisioning profile,通常无法解决 NSURLErrorDomain 网络错误。

4. 判断 Sandbox 是否可能出现故障

如果多台测试设备在不同网络上同时出现相同错误,而生产环境或其他互联网请求正常,那么 Sandbox 服务异常的可能性较大。

这只是排除本地原因后得出的判断。-1001、-1003 和 -1004 无法单独证明 Apple Sandbox 已经宕机。应记录错误发生时间、设备系统版本、网络环境、外层 SKError 和完整的底层错误,供后续复现或提交反馈。

交易处理中的注意事项

  • 不要对处于 SKPaymentTransactionStatePurchasing 或 SKPaymentTransactionStateDeferred 状态的交易调用 finishTransaction:。
  • 已购买的交易应在收据验证、发放状态保存和内容交付全部完成后再结束。
  • 失败交易可以在记录错误后结束。用户主动取消购买时,不应显示网络故障提示。
  • 商品请求成功不能用于判断购买服务是否正常。
  • 对于消耗型商品,服务端发放逻辑应具备幂等性,避免重试或恢复流程造成重复发放。
  • Sandbox 可能出现间歇性波动。应用应把网络失败作为正常的运行时情况处理,使用户能够恢复或重试。

备注:内容仅供参考。