PAYATHON 2026

React Native Square In-App Payment 报 unexpected 错误

支付老李

结论

这个 code: unexpected 更像是 JavaScript 回调使用不当导致的,而不是银行卡信息有误。

示例中主要有两个问题:

  1. 将 onCardNonceRequestSuccess、onCardEntryCancel 作为回调传递时,可能会丢失 this。
  2. 调用 completeCardEntry 时使用了 this.onCardEntryComplete()。这样会立即执行该函数,再把返回值 undefined 传给 SDK,而 SDK 需要的是函数本身。

修正代码

可以把回调定义为箭头函数,并将函数引用传给 completeCardEntry:

async componentDidMount() {
  this._loadCreditCardInfo();

  try {
    await SQIPCore.setSquareApplicationId('MY_SANDBOX_APP_ID');

    if (Platform.OS === 'ios') {
      await SQIPCardEntry.setIOSCardEntryTheme({
        saveButtonFont: {
          size: 25,
        },
        saveButtonTitle: 'Add 💳 ',
        keyboardAppearance: 'Light',
        saveButtonTextColor: {
          r: 255,
          g: 0,
          b: 125,
          a: 0.5,
        },
      });
    }
  } catch (error) {
    console.error('Square initialization failed:', error);
  }
}

onCardEntryComplete = () => {
  // Update UI after the completeCardEntry animation finishes
};

onCardNonceRequestSuccess = async (cardDetails) => {
  try {
    console.log('Square card details:', cardDetails);

    // 正式业务中,应先把 cardDetails.nonce 发送到自己的服务端完成支付。
    // 服务端确认成功后,再通知卡片输入页面完成。
    await SQIPCardEntry.completeCardEntry(
      this.onCardEntryComplete
    );
  } catch (error) {
    console.error('Card nonce processing failed:', error);

    await SQIPCardEntry.showCardNonceProcessingError(
      error && error.message
        ? error.message
        : 'Unable to process payment'
    );
  }
};

onCardEntryCancel = () => {
  // Handle cancellation
};

onStartCardEntry = async () => {
  const cardEntryConfig = {
    collectPostalCode: true,
  };

  try {
    await SQIPCardEntry.startCardEntryFlow(
      cardEntryConfig,
      this.onCardNonceRequestSuccess,
      this.onCardEntryCancel
    );
  } catch (error) {
    console.error('Unable to start card entry flow:', error);
  }
};

两种写法的区别在于:

// 错误:立即调用函数,传入的是返回值
this.onCardEntryComplete()

// 正确:传入函数引用
this.onCardEntryComplete

箭头函数也能让回调中的 this 始终指向当前 React Native 组件。也可以选择在构造函数中显式绑定:

constructor(props) {
  super(props);

  this.onCardNonceRequestSuccess =
    this.onCardNonceRequestSuccess.bind(this);
  this.onCardEntryComplete =
    this.onCardEntryComplete.bind(this);
  this.onCardEntryCancel =
    this.onCardEntryCancel.bind(this);
}

选择其中一种方式即可,不需要重复绑定。

正确的支付流程

触发 onCardNonceRequestSuccess,只能说明客户端已经成功生成卡片 nonce。此时资金尚未扣除,银行卡余额、风控结果和付款状态也还没有完成验证。

正常流程如下:

  1. 客户端取得 cardDetails.nonce。
  2. 将 nonce 发送到自己的后端。
  3. 后端通过 Square 的支付 API 创建支付。
  4. 后端确认支付成功后,客户端调用 completeCardEntry。
  5. 如果后端支付失败,则调用 showCardNonceProcessingError,让用户留在卡片输入页面,并显示容易理解的错误信息。

示意代码:

onCardNonceRequestSuccess = async (cardDetails) => {
  try {
    const response = await fetch('https://example.com/api/payments', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        nonce: cardDetails.nonce,
      }),
    });

    const result = await response.json();

    if (!response.ok) {
      throw new Error(result.message || 'Payment failed');
    }

    await SQIPCardEntry.completeCardEntry(
      this.onCardEntryComplete
    );
  } catch (error) {
    console.error('Payment processing error:', error);

    await SQIPCardEntry.showCardNonceProcessingError(
      error.message || 'Payment failed'
    );
  }
};

仍然报错时的排查顺序

1. 输出完整错误对象

只读取 ex.message,可能会漏掉原生 SDK 返回的其他错误字段:

catch (error) {
  console.error('Square error object:', error);
  console.error('Square error message:', error?.message);
  console.error('Square error code:', error?.code);
}

还要检查 Xcode 控制台。如果 React Native 只显示 unexpected,原生控制台通常会提供更接近根因的信息。

2. 检查环境是否一致

如果使用:

SQIPCore.setSquareApplicationId('MY_SANDBOX_APP_ID')

后端也必须使用同一 Square 账户下的 Sandbox 凭证和 Sandbox location。以下配置不能混用:

  • Sandbox application ID 与生产 access token
  • 生产 application ID 与 Sandbox access token
  • 不同应用或账户下的 location ID

3. 使用对应环境的测试卡

Sandbox 环境应使用 Square 提供的测试卡数据。真实银行卡信息不一定能在 Sandbox 中按照生产支付方式处理。也不要为了测试真实付款而直接切换到生产环境。

4. 初始化完成后再开放支付按钮

setSquareApplicationId 是异步调用。如果用户能在初始化完成前点击支付按钮,可能会触发难以判断原因的原生错误。可以通过状态记录初始化是否完成:

state = {
  squareReady: false,
};

async componentDidMount() {
  try {
    await SQIPCore.setSquareApplicationId('MY_SANDBOX_APP_ID');
    this.setState({ squareReady: true });
  } catch (error) {
    console.error('Square initialization failed:', error);
  }
}

启动卡片页面前先检查该状态:

if (!this.state.squareReady) {
  return;
}

5. 暂时移除自定义主题

主题通常不是最先需要怀疑的部分。不过在排查时,可以先移除 setIOSCardEntryTheme,只保留初始化和卡片输入流程。如果基础流程可以正常运行,再逐项恢复主题配置,以便区分支付流程错误和 iOS 原生主题参数问题。

注意事项

只有在后端成功处理 nonce 后,才应调用 completeCardEntry,不能在拿到 nonce 后无条件调用。否则界面可能已经显示支付流程完成,但实际付款尚未创建,或者已经失败。

Square In-App Payments SDK 1.3.1 是较早的版本。如果修正回调并统一 Sandbox 环境后,问题仍然可以稳定复现,就需要核对当前项目所用 React Native、Xcode、iOS 和 Square SDK 的兼容范围。不过,升级依赖前应先解决回调引用和 this 绑定问题,因为这两个问题本身就可能触发 unexpected 这类桥接错误。

备注:内容仅供参考。