diff --git a/apps/mobile/App.js b/apps/mobile/App.js index dfb7e0ada..d9ea5af3f 100644 --- a/apps/mobile/App.js +++ b/apps/mobile/App.js @@ -33,7 +33,7 @@ async function loadDefaultNotes() { try { const isCreated = await MMKV.getItem('defaultNoteCreated'); if (isCreated) return; - const notes = await http.get('https://app.notesnook.com/notes/index.json'); + const notes = await http.get('https://app.notesnook.com/notes/index_14.json'); if (!notes) return; for (let note of notes) { const content = await http.get(note.mobileContent); diff --git a/apps/mobile/AppRootEvents.js b/apps/mobile/AppRootEvents.js index 20b5e066d..e857b635c 100644 --- a/apps/mobile/AppRootEvents.js +++ b/apps/mobile/AppRootEvents.js @@ -5,6 +5,7 @@ import {Appearance, AppState, Linking, Platform} from 'react-native'; import RNExitApp from 'react-native-exit-app'; import * as RNIap from 'react-native-iap'; import {enabled} from 'react-native-privacy-snapshot'; +import {abs} from 'react-native-reanimated'; import SplashScreen from 'react-native-splash-screen'; import { clearAllStores, @@ -250,8 +251,8 @@ export const AppRootEvents = React.memo( }; const onAccountStatusChange = async userStatus => { - console.log('account status', userStatus, PremiumService.get()); if (!PremiumService.get() && userStatus.type === 5) { + PremiumService.subscriptions.clear(); eSendEvent(eOpenProgressDialog, { title: 'Notesnook Pro', paragraph: `Your Notesnook Pro subscription has been successfully activated.`, @@ -375,11 +376,8 @@ export const AppRootEvents = React.memo( }; const onSuccessfulSubscription = async subscription => { - const receipt = subscription.transactionReceipt; - if (prevTransactionId === subscription.transactionId) { - return; - } - await processReceipt(receipt); + await PremiumService.subscriptions.set(subscription); + await PremiumService.subscriptions.verify(subscription); }; const onSubscriptionError = async error => { @@ -389,46 +387,6 @@ export const AppRootEvents = React.memo( message: error.message, context: 'local', }); - - if (Platform.OS === 'ios') { - await RNIap.clearTransactionIOS(); - } - }; - - const processReceipt = async receipt => { - if (receipt) { - if (Platform.OS === 'ios') { - let user = await db.user.getUser(); - if (!user) return; - fetch('https://payments.streetwriters.co/apple/verify', { - method: 'POST', - body: JSON.stringify({ - receipt_data: receipt, - user_id: user.id, - }), - headers: { - 'Content-Type': 'application/json', - }, - }) - .then(async r => { - let text = await r.text(); - console.log(r.ok, text); - if (!r.ok) { - if (text === 'Receipt already expired.') { - console.log('RNIap.clearTransactionIOS'); - await RNIap.clearTransactionIOS(); - } - return; - } - console.log('Success', 'RNIap.finishTransactionIOS'); - await RNIap.finishTransactionIOS(prevTransactionId); - await RNIap.clearTransactionIOS(); - }) - .catch(e => { - console.log(e, 'ERROR'); - }); - } - } }; const onAppStateChanged = async state => { diff --git a/apps/mobile/ios/Notesnook.xcodeproj/project.pbxproj b/apps/mobile/ios/Notesnook.xcodeproj/project.pbxproj index 17eae66ed..e857f9cce 100644 --- a/apps/mobile/ios/Notesnook.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/Notesnook.xcodeproj/project.pbxproj @@ -1036,7 +1036,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1420; + CURRENT_PROJECT_VERSION = 1430; DEVELOPMENT_TEAM = 53CWBG3QUC; ENABLE_BITCODE = NO; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; @@ -1108,7 +1108,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.4.3; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1136,7 +1136,7 @@ CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1420; + CURRENT_PROJECT_VERSION = 1430; DEVELOPMENT_TEAM = 53CWBG3QUC; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; HEADER_SEARCH_PATHS = ( @@ -1207,7 +1207,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.4.3; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1363,7 +1363,7 @@ CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1420; + CURRENT_PROJECT_VERSION = 1430; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 53CWBG3QUC; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1436,7 +1436,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.4.3; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share; @@ -1466,7 +1466,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1420; + CURRENT_PROJECT_VERSION = 1430; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 53CWBG3QUC; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -1539,7 +1539,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.4.3; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 06c564e4e..f50d889fc 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -265,7 +265,7 @@ PODS: - React - react-native-splash-screen (3.2.0): - React - - react-native-webview (11.4.0): + - react-native-webview (11.6.4): - React-Core - React-perflogger (0.64.2) - React-RCTActionSheet (0.64.2): @@ -659,7 +659,7 @@ SPEC CHECKSUMS: react-native-safe-area-context: b6e0e284002381d2ff29fa4fff42b4d8282e3c94 react-native-sodium: c7587732667e1cdb7d0d77c2aa2e98420aad2036 react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865 - react-native-webview: 4dfb534e9600b87fe667c5ca4fe09149383218b9 + react-native-webview: 1a19adb5578cdf7f005b7961dcc50c1c6b70f41b React-perflogger: 25373e382fed75ce768a443822f07098a15ab737 React-RCTActionSheet: af7796ba49ffe4ca92e7277a5d992d37203f7da5 React-RCTAnimation: 6a2e76ab50c6f25b428d81b76a5a45351c4d77aa diff --git a/apps/mobile/src/services/PremiumService.js b/apps/mobile/src/services/PremiumService.js index 6dcc3c6e0..9df524de4 100644 --- a/apps/mobile/src/services/PremiumService.js +++ b/apps/mobile/src/services/PremiumService.js @@ -37,6 +37,8 @@ async function setPremiumStatus() { if (!get()) { await RNIap.initConnection(); products = await RNIap.getSubscriptions(itemSkus); + } else { + await subscriptions.clear(); } } } @@ -159,6 +161,102 @@ const showVerifyEmailDialog = () => { }); }; +const subscriptions = { + get: async () => { + let _subscriptions = await MMKV.getItem('subscriptionsIOS'); + if (!_subscriptions) return []; + return JSON.parse(_subscriptions); + }, + set: async subscription => { + let _subscriptions = await MMKV.getItem('subscriptionsIOS'); + if (_subscriptions) { + _subscriptions = JSON.parse(_subscriptions); + } else { + _subscriptions = []; + } + let index = _subscriptions.findIndex( + s => s.transactionId === transactionId, + ); + if (index === -1) { + _subscriptions.unshift(subscription); + } else { + _subscriptions[index] = subscription; + } + await MMKV.setItem('subscriptionsIOS', JSON.stringify(_subscriptions)); + }, + remove: async transactionId => { + let _subscriptions = await MMKV.getItem('subscriptionsIOS'); + if (_subscriptions) { + _subscriptions = JSON.parse(_subscriptions); + } else { + _subscriptions = []; + } + let index = _subscriptions.findIndex( + s => s.transactionId === transactionId, + ); + if (index !== -1) { + _subscriptions.splice(index); + await MMKV.setItem('subscriptionsIOS', JSON.stringify(_subscriptions)); + } + }, + verify: async subscription => { + console.log( + 'verifying: ', + subscription.transactionId, + new Date(subscription.transactionDate).toLocaleString(), + ); + + if (subscription.transactionReceipt) { + if (Platform.OS === 'ios') { + let user = await db.user.getUser(); + if (!user) return; + let requestData = { + method: 'POST', + body: JSON.stringify({ + receipt_data: subscription.transactionReceipt, + user_id: user.id, + }), + headers: { + 'Content-Type': 'application/json', + }, + }; + try { + let result = await fetch( + 'https://payments.streetwriters.co/apple/verify', + requestData, + ); + let text = await result.text(); + if (!result.ok) { + if (text === 'Receipt already expired.') { + await subscriptions.clear(subscription); + console.log('clearing because expired'); + } + return; + } + } catch (e) { + console.log('subscription error', e); + } + } + } + }, + clear: async _subscription => { + let _subscriptions = await subscriptions.get(); + let subscription = null; + if (_subscription) { + subscription = _subscription; + console.log('got id to clear'); + } else { + subscription = _subscriptions.length > 0 ? _subscriptions[0] : null; + } + if (subscription) { + await RNIap.finishTransactionIOS(subscription.transactionId); + await RNIap.clearTransactionIOS(); + await subscriptions.remove(subscription.transactionId); + console.log('clearing subscriptions'); + } + }, +}; + export default { verify, setPremiumStatus, @@ -167,4 +265,5 @@ export default { showVerifyEmailDialog, getProducts, getUser, + subscriptions, }; diff --git a/apps/mobile/src/views/Settings/index.js b/apps/mobile/src/views/Settings/index.js index 225dbbaeb..ccc412a3a 100644 --- a/apps/mobile/src/views/Settings/index.js +++ b/apps/mobile/src/views/Settings/index.js @@ -73,6 +73,7 @@ import { import {hexToRGBA, RGB_Linear_Shade} from '../../utils/ColorUtils'; import {db} from '../../utils/DB'; import { + eCloseProgressDialog, eOpenLoginDialog, eOpenPremiumDialog, eOpenProgressDialog, @@ -88,6 +89,7 @@ import {pv, SIZE} from '../../utils/SizeUtils'; import Storage from '../../utils/storage'; import {sleep, timeConverter} from '../../utils/TimeUtils'; import ToggleSwitch from 'toggle-switch-react-native'; +import * as RNIap from 'react-native-iap'; let menuRef = createRef(); @@ -703,7 +705,10 @@ const SettingsUserSection = () => { }; const manageSubscription = () => { - if (user.subscription.type === SUBSCRIPTION_STATUS.PREMIUM_CANCELLED) { + if ( + user.subscription.type === SUBSCRIPTION_STATUS.PREMIUM_CANCELLED && + Platform.OS === 'android' + ) { if (user.subscription.provider === 3) { ToastEvent.show({ heading: 'Subscribed on web', @@ -912,12 +917,9 @@ const SettingsUserSection = () => { SUBSCRIPTION_STATUS.PREMIUM_CANCELLED ? 'Manage subscription from desktop app' : user.subscription.type === - SUBSCRIPTION_STATUS.PREMIUM_CANCELLED - ? `Resubscribe from ${ - Platform.OS === 'ios' - ? 'App Store' - : 'Google Playstore' - }` + SUBSCRIPTION_STATUS.PREMIUM_CANCELLED && + Platform.OS === 'android' + ? `Resubscribe from Google Playstore` : user.subscription.type === SUBSCRIPTION_STATUS.PREMIUM_EXPIRED ? `Resubscribe to Notesnook Pro (${ @@ -1058,15 +1060,52 @@ const SettingsUserSection = () => { }, desc: 'Try force sync to resolve issues with syncing.', }, - ].map(item => ( - - ))} + { + name: 'Subscription not activated?', + func: async () => { + eSendEvent(eOpenProgressDialog, { + title: 'Loading subscriptions', + paragraph: `Please wait while we fetch your subscriptions.`, + }); + let subscriptions = await RNIap.getPurchaseHistory(); + subscriptions.sort( + (a, b) => b.transactionDate - a.transactionDate, + ); + let currentSubscription = subscriptions[0]; + eSendEvent(eOpenProgressDialog, { + title: 'Notesnook Pro', + paragraph: `You subscribed to Notesnook Pro on ${new Date( + currentSubscription.transactionDate, + ).toLocaleString()}. Verify this subscription?`, + action: async () => { + eSendEvent(eOpenProgressDialog, { + title: 'Verifying subscription', + paragraph: `Please wait while we verify your subscription.`, + }); + await PremiumService.subscriptions.verify( + currentSubscription, + ); + eSendEvent(eCloseProgressDialog); + }, + icon: 'information-outline', + actionText: 'Verify', + noProgress: true, + }); + }, + desc: 'Verify your subscription to Notesnook Pro', + }, + ].map(item => + item.name === 'Subscription not activated?' && + (Platform.OS !== 'ios' || PremiumService.get()) ? null : ( + + ), + )} ) : null}