@@ -356,6 +356,217 @@ function SubscriptionFlow({
356356 ) : null }
357357 </ View >
358358
359+ { /* Subscription Upgrade Detection - iOS renewalInfo */ }
360+ { ( ( ) => {
361+ if ( Platform . OS !== 'ios' || activeSubscriptions . length === 0 ) {
362+ return null ;
363+ }
364+
365+ const upgradablePurchases = availablePurchases . filter ( ( p ) => {
366+ const iosPurchase = p as PurchaseIOS ;
367+ const pendingProductId =
368+ iosPurchase . renewalInfoIOS ?. pendingUpgradeProductId ;
369+
370+ // Show upgrade card if there's a pending upgrade product that's different
371+ // from the current product. In production, you might want to also check
372+ // willAutoRenew, but Apple Sandbox behavior can be inconsistent.
373+ return (
374+ pendingProductId &&
375+ pendingProductId !== p . productId &&
376+ activeSubscriptions . some ( ( sub ) => sub . productId === p . productId )
377+ ) ;
378+ } ) ;
379+
380+ if ( upgradablePurchases . length === 0 ) {
381+ return null ;
382+ }
383+
384+ return (
385+ < View style = { styles . upgradeDetectionCard } >
386+ < Text style = { styles . upgradeDetectionTitle } >
387+ 🎉 Subscription Upgrade Detected
388+ </ Text >
389+ { upgradablePurchases . map ( ( purchase , idx ) => {
390+ const iosPurchase = purchase as PurchaseIOS ;
391+ const renewalInfo = iosPurchase . renewalInfoIOS ;
392+ const currentProduct = subscriptions . find (
393+ ( s ) => s . id === purchase . productId ,
394+ ) ;
395+ const upgradeProduct = subscriptions . find (
396+ ( s ) => s . id === renewalInfo ?. pendingUpgradeProductId ,
397+ ) ;
398+
399+ return (
400+ < View key = { idx } style = { styles . upgradeInfoBox } >
401+ < View style = { styles . upgradeRow } >
402+ < Text style = { styles . upgradeLabel } > Current:</ Text >
403+ < Text style = { styles . upgradeValue } >
404+ { currentProduct ?. title || purchase . productId }
405+ </ Text >
406+ </ View >
407+ < View style = { styles . upgradeArrow } >
408+ < Text style = { styles . upgradeArrowText } > ⬇️</ Text >
409+ </ View >
410+ < View style = { styles . upgradeRow } >
411+ < Text style = { styles . upgradeLabel } > Upgrading to:</ Text >
412+ < Text
413+ style = { [ styles . upgradeValue , styles . highlightText ] }
414+ >
415+ { upgradeProduct ?. title ||
416+ renewalInfo ?. pendingUpgradeProductId ||
417+ 'Unknown' }
418+ </ Text >
419+ </ View >
420+ { iosPurchase . expirationDateIOS ? (
421+ < View style = { styles . upgradeRow } >
422+ < Text style = { styles . upgradeLabel } > Upgrade Date:</ Text >
423+ < Text style = { styles . upgradeValue } >
424+ { new Date (
425+ iosPurchase . expirationDateIOS ,
426+ ) . toLocaleDateString ( ) }
427+ </ Text >
428+ </ View >
429+ ) : null }
430+ { renewalInfo ?. willAutoRenew !== undefined ? (
431+ < View style = { styles . upgradeRow } >
432+ < Text style = { styles . upgradeLabel } > Auto-Renew:</ Text >
433+ < Text
434+ style = { [
435+ styles . upgradeValue ,
436+ renewalInfo . willAutoRenew
437+ ? styles . activeStatus
438+ : styles . cancelledStatus ,
439+ ] }
440+ >
441+ { renewalInfo . willAutoRenew
442+ ? '✅ Enabled'
443+ : '⚠️ Disabled' }
444+ </ Text >
445+ </ View >
446+ ) : null }
447+ < Text style = { styles . upgradeNote } >
448+ 💡 Your subscription will automatically upgrade when the
449+ current period ends.
450+ { renewalInfo ?. willAutoRenew === false
451+ ? ' Note: Auto-renew is currently disabled.'
452+ : '' }
453+ </ Text >
454+
455+ { /* Show renewalInfo details */ }
456+ < TouchableOpacity
457+ style = { styles . viewRenewalInfoButton }
458+ onPress = { ( ) => {
459+ Alert . alert (
460+ 'Renewal Info Details' ,
461+ JSON . stringify ( renewalInfo , null , 2 ) ,
462+ [ { text : 'OK' } ] ,
463+ ) ;
464+ } }
465+ >
466+ < Text style = { styles . viewRenewalInfoButtonText } >
467+ 📋 View renewalInfo
468+ </ Text >
469+ </ TouchableOpacity >
470+ </ View >
471+ ) ;
472+ } ) }
473+ </ View >
474+ ) ;
475+ } ) ( ) }
476+
477+ { /* Subscription Cancellation Detection - iOS renewalInfo */ }
478+ { ( ( ) => {
479+ if ( Platform . OS !== 'ios' ) {
480+ return null ;
481+ }
482+
483+ const cancelledPurchases = availablePurchases . filter ( ( p ) => {
484+ const iosPurchase = p as PurchaseIOS ;
485+ return (
486+ iosPurchase . renewalInfoIOS ?. willAutoRenew === false &&
487+ ! iosPurchase . renewalInfoIOS ?. pendingUpgradeProductId &&
488+ activeSubscriptions . some ( ( sub ) => sub . productId === p . productId )
489+ ) ;
490+ } ) ;
491+
492+ if ( cancelledPurchases . length === 0 ) {
493+ return null ;
494+ }
495+
496+ return (
497+ < View style = { styles . cancellationDetectionCard } >
498+ < Text style = { styles . cancellationDetectionTitle } >
499+ ⚠️ Subscription Cancelled
500+ </ Text >
501+ { cancelledPurchases . map ( ( purchase , idx ) => {
502+ const iosPurchase = purchase as PurchaseIOS ;
503+ const renewalInfo = iosPurchase . renewalInfoIOS ;
504+ const currentProduct = subscriptions . find (
505+ ( s ) => s . id === purchase . productId ,
506+ ) ;
507+ const preferredProduct = subscriptions . find (
508+ ( s ) => s . id === renewalInfo ?. autoRenewPreference ,
509+ ) ;
510+
511+ return (
512+ < View key = { idx } style = { styles . cancellationInfoBox } >
513+ < View style = { styles . upgradeRow } >
514+ < Text style = { styles . upgradeLabel } > Product:</ Text >
515+ < Text style = { styles . upgradeValue } >
516+ { currentProduct ?. title || purchase . productId }
517+ </ Text >
518+ </ View >
519+ { iosPurchase . expirationDateIOS ? (
520+ < View style = { styles . upgradeRow } >
521+ < Text style = { styles . upgradeLabel } > Expires:</ Text >
522+ < Text
523+ style = { [ styles . upgradeValue , styles . expiredText ] }
524+ >
525+ { new Date (
526+ iosPurchase . expirationDateIOS ,
527+ ) . toLocaleDateString ( ) }
528+ </ Text >
529+ </ View >
530+ ) : null }
531+ { renewalInfo ?. pendingUpgradeProductId &&
532+ renewalInfo . pendingUpgradeProductId !==
533+ purchase . productId ? (
534+ < View style = { styles . upgradeRow } >
535+ < Text style = { styles . upgradeLabel } > Next Renewal:</ Text >
536+ < Text style = { styles . upgradeValue } >
537+ { preferredProduct ?. title ||
538+ renewalInfo . autoRenewPreference ||
539+ 'None' }
540+ </ Text >
541+ </ View >
542+ ) : null }
543+ < Text style = { styles . cancellationNote } >
544+ 💡 Your subscription will not auto-renew. You'll have
545+ access until the expiration date.
546+ </ Text >
547+
548+ { /* Show renewalInfo details */ }
549+ < TouchableOpacity
550+ style = { styles . viewRenewalInfoButton }
551+ onPress = { ( ) => {
552+ Alert . alert (
553+ 'Renewal Info Details' ,
554+ JSON . stringify ( renewalInfo , null , 2 ) ,
555+ [ { text : 'OK' } ] ,
556+ ) ;
557+ } }
558+ >
559+ < Text style = { styles . viewRenewalInfoButtonText } >
560+ 📋 View renewalInfo
561+ </ Text >
562+ </ TouchableOpacity >
563+ </ View >
564+ ) ;
565+ } ) }
566+ </ View >
567+ ) ;
568+ } ) ( ) }
569+
359570 < View style = { styles . subscriptionActionButtons } >
360571 < TouchableOpacity
361572 style = { styles . refreshButton }
@@ -823,10 +1034,11 @@ function SubscriptionFlowContainer() {
8231034 } , [ connected , fetchProducts , getAvailablePurchases ] ) ;
8241035
8251036 useEffect ( ( ) => {
826- if ( connected ) {
1037+ if ( connected && subscriptions . length > 0 ) {
1038+ // Wait until subscriptions are loaded before checking status
8271039 void handleRefreshStatus ( ) ;
8281040 }
829- } , [ connected , handleRefreshStatus ] ) ;
1041+ } , [ connected , subscriptions . length , handleRefreshStatus ] ) ;
8301042
8311043 useEffect ( ( ) => {
8321044 ExpoIapConsole . log (
@@ -1403,4 +1615,110 @@ const styles = StyleSheet.create({
14031615 fontSize : 16 ,
14041616 fontWeight : '600' ,
14051617 } ,
1618+ upgradeDetectionCard : {
1619+ backgroundColor : '#fff5e6' ,
1620+ borderRadius : 12 ,
1621+ padding : 16 ,
1622+ marginTop : 16 ,
1623+ borderWidth : 2 ,
1624+ borderColor : '#ff9800' ,
1625+ } ,
1626+ upgradeDetectionTitle : {
1627+ fontSize : 16 ,
1628+ fontWeight : '700' ,
1629+ color : '#e65100' ,
1630+ marginBottom : 12 ,
1631+ } ,
1632+ upgradeInfoBox : {
1633+ backgroundColor : '#fff' ,
1634+ borderRadius : 8 ,
1635+ padding : 12 ,
1636+ marginTop : 8 ,
1637+ } ,
1638+ upgradeRow : {
1639+ flexDirection : 'row' ,
1640+ justifyContent : 'space-between' ,
1641+ alignItems : 'center' ,
1642+ paddingVertical : 6 ,
1643+ } ,
1644+ upgradeLabel : {
1645+ fontSize : 14 ,
1646+ fontWeight : '500' ,
1647+ color : '#666' ,
1648+ } ,
1649+ upgradeValue : {
1650+ fontSize : 14 ,
1651+ fontWeight : '600' ,
1652+ color : '#333' ,
1653+ flex : 1 ,
1654+ textAlign : 'right' ,
1655+ } ,
1656+ highlightText : {
1657+ color : '#ff9800' ,
1658+ fontWeight : '700' ,
1659+ } ,
1660+ upgradeArrow : {
1661+ alignItems : 'center' ,
1662+ paddingVertical : 8 ,
1663+ } ,
1664+ upgradeArrowText : {
1665+ fontSize : 24 ,
1666+ } ,
1667+ upgradeNote : {
1668+ fontSize : 12 ,
1669+ color : '#666' ,
1670+ fontStyle : 'italic' ,
1671+ marginTop : 12 ,
1672+ lineHeight : 18 ,
1673+ backgroundColor : '#f5f5f5' ,
1674+ padding : 8 ,
1675+ borderRadius : 6 ,
1676+ } ,
1677+ viewRenewalInfoButton : {
1678+ marginTop : 12 ,
1679+ paddingVertical : 10 ,
1680+ paddingHorizontal : 16 ,
1681+ backgroundColor : '#007AFF' ,
1682+ borderRadius : 8 ,
1683+ alignItems : 'center' ,
1684+ } ,
1685+ viewRenewalInfoButtonText : {
1686+ color : '#fff' ,
1687+ fontSize : 14 ,
1688+ fontWeight : '600' ,
1689+ } ,
1690+ cancellationDetectionCard : {
1691+ backgroundColor : '#fff3cd' ,
1692+ borderRadius : 12 ,
1693+ padding : 16 ,
1694+ marginTop : 16 ,
1695+ borderWidth : 2 ,
1696+ borderColor : '#ffc107' ,
1697+ } ,
1698+ cancellationDetectionTitle : {
1699+ fontSize : 16 ,
1700+ fontWeight : '700' ,
1701+ color : '#856404' ,
1702+ marginBottom : 12 ,
1703+ } ,
1704+ cancellationInfoBox : {
1705+ backgroundColor : '#fff' ,
1706+ borderRadius : 8 ,
1707+ padding : 12 ,
1708+ marginTop : 8 ,
1709+ } ,
1710+ expiredText : {
1711+ color : '#dc3545' ,
1712+ fontWeight : '700' ,
1713+ } ,
1714+ cancellationNote : {
1715+ fontSize : 12 ,
1716+ color : '#856404' ,
1717+ fontStyle : 'italic' ,
1718+ marginTop : 12 ,
1719+ lineHeight : 18 ,
1720+ backgroundColor : '#fffbf0' ,
1721+ padding : 8 ,
1722+ borderRadius : 6 ,
1723+ } ,
14061724} ) ;
0 commit comments