React Native Square In-App Payment 报 unexpected 错误
结论
这个 code: unexpected 更像是 JavaScript 回调使用不当导致的,而不是银行卡信息有误。
示例中主要有两个问题:
- 将
onCardNonceRequestSuccess、onCardEntryCancel作为回调传递时,可能会丢失this。 - 调用
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。此时资金尚未扣除,银行卡余额、风控结果和付款状态也还没有完成验证。
正常流程如下:
- 客户端取得
cardDetails.nonce。 - 将 nonce 发送到自己的后端。
- 后端通过 Square 的支付 API 创建支付。
- 后端确认支付成功后,客户端调用
completeCardEntry。 - 如果后端支付失败,则调用
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 这类桥接错误。
备注:内容仅供参考。
