React Native IAP iOS订阅expires_date_ms异常问题求助
我目前用的是react-native-iap 3.3.9版本和React Native 0.59.9版本,在iOS端检查订阅是否有效时遇到了问题:获取到的expires_date_ms总是小于当前时间,但我的明明是1个月期的订阅,导致判断逻辑一直认为订阅失效。
相关代码如下:
if (Platform.OS === 'ios') { try { console.log('In Checking') const availablePurchases = await RNIap.getAvailablePurchases(); console.log("Available Purchase", availablePurchases) const sortedAvailablePurchases = availablePurchases.sort( (a, b) => b.transactionDate - a.transactionDate ); if (sortedAvailablePurchases.length > 0) { const latestAvailableReceipt = sortedAvailablePurchases[0].transactionReceipt; const isTestEnvironment = __DEV__; const decodeReceipt = await RNIap.validateReceiptIos({ 'receipt-data': latestAvailableReceipt, 'password': '' }, isTestEnvironment); console.log('decodeReceipt',decodeReceipt) const { latest_receipt_info: latestReceiptInfo } = decodeReceipt; const isSubValid = !!latestReceiptInfo.find(receipt => { const expirationInMilliseconds = Number(receipt.expires_date_ms); const nowInMilliseconds = Date.now(); return expirationInMilliseconds > nowInMilliseconds; }); return isSubValid } else { return false; } } catch (err) { console.log("Got an error while getting purchases", err); return false; } }
求指导如何解决这个问题。
我之前在旧版本的react-native-iap上踩过类似的坑,结合你的代码和使用的版本,给你几个具体的排查和修复方向:
1. 先确认expires_date_ms的数值准确性
首先你可以在代码里打印出receipt.expires_date_ms和Date.now()的具体值,看看两者的差值是否合理。比如如果差值是几小时,大概率是时区问题——iOS返回的expires_date_ms是UTC时间戳,虽然Date.now()本质也是UTC时间,但如果设备时区设置异常,或者旧版本react-native-iap在解析时存在偏移,就会出现判断错误。
如果是时区问题,可以尝试用UTC时间来做对比:
// 获取当前UTC时间的时间戳 const nowUTC = Date.UTC( new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate(), new Date().getUTCHours(), new Date().getUTCMinutes(), new Date().getUTCSeconds() ); const expirationInMilliseconds = parseInt(receipt.expires_date_ms, 10); return expirationInMilliseconds > nowUTC;
2. 优化latest_receipt_info的遍历逻辑
苹果返回的latest_receipt_info包含了所有订阅交易记录(包括自动续期的),你当前用find查找未过期记录的逻辑没问题,但要注意:旧版本的react-native-iap返回的这个数组可能不是按过期时间排序的。
你可以先把记录按过期时间倒序排列,再取第一条判断,这样更稳妥:
// 先按过期时间从晚到早排序 const sortedReceipts = latestReceiptInfo.sort((a, b) => parseInt(b.expires_date_ms, 10) - parseInt(a.expires_date_ms, 10) ); // 只需要判断最新的一条是否未过期 const isSubValid = sortedReceipts.length > 0 && parseInt(sortedReceipts[0].expires_date_ms, 10) > Date.now();
3. 补全Receipt验证的必要参数
你当前传的password是空字符串,但如果是自动续期订阅,苹果要求必须传入App Store Connect里生成的共享密钥(Shared Secret),空密码会导致验证返回的receipt信息不完整,甚至返回旧的测试记录。
另外,用__DEV__判断测试环境也不太准确(比如TestFlight测试时__DEV__为false,但仍需走测试环境验证),可以改成手动控制环境:
const isTestEnvironment = true; // 根据实际测试/生产环境调整 const decodeReceipt = await RNIap.validateReceiptIos({ 'receipt-data': latestAvailableReceipt, 'password': '你的App共享密钥' // 从App Store Connect的"用户和访问"->"密钥"中生成 }, isTestEnvironment);
4. 排查react-native-iap旧版本的解析bug
react-native-iap 3.3.9是比较老旧的版本,可能存在receipt字段解析的问题:比如expires_date_ms被解析成了字符串而非数字,或者时间戳位数错误(苹果返回的是毫秒,但旧版本可能误处理成秒)。
你可以在代码里打印receipt.expires_date_ms的原始类型,确保转换正确:
console.log('expires_date_ms type:', typeof receipt.expires_date_ms); // 用parseInt转换比Number更可靠 const expirationInMilliseconds = parseInt(receipt.expires_date_ms, 10);
5. 沙盒测试环境的特殊处理
如果你在沙盒环境测试,要注意:
- 沙盒的自动续期订阅会加速过期(比如5分钟续一次),但有时候沙盒时间同步会有问题,建议重启设备并确保系统时间正确;
- 订阅过期后不会立即从本地
availablePurchases移除,必须依赖receipt验证的结果来判断有效性,不要只信任本地缓存。
内容的提问来源于stack exchange,提问作者Krrunal

