diff --git a/desktop/app/issue.go b/desktop/app/issue.go index 8185236da..141bc86be 100644 --- a/desktop/app/issue.go +++ b/desktop/app/issue.go @@ -69,7 +69,7 @@ func (reporter *issueReporter) sendIssueReport(msg *issueMessage) error { } subscriptionLevel := "free" ctx := context.Background() - if isPro, _ := IsProUser(ctx, reporter.proClient, settings.GetUserID()); isPro { + if isPro, _ := IsProUser(ctx, reporter.proClient, settings); isPro { subscriptionLevel = "pro" } var osVersion string diff --git a/desktop/app/pro.go b/desktop/app/pro.go index a097c2be1..1e886e8a8 100644 --- a/desktop/app/pro.go +++ b/desktop/app/pro.go @@ -86,17 +86,17 @@ func (app *App) IsProUser(ctx context.Context) (isPro bool, ok bool) { if err != nil { return false, false } - return IsProUser(ctx, app.proClient, app.settings.GetUserID()) + return IsProUser(ctx, app.proClient, app.settings) } -func IsProUser(ctx context.Context, proClient pro.ProClient, userId int64) (isPro bool, ok bool) { +func IsProUser(ctx context.Context, proClient pro.ProClient, uc common.UserConfig) (isPro bool, ok bool) { isActive := func(user *protos.User) bool { return user != nil && user.UserStatus == "active" } - user, found := GetUserDataFast(ctx, userId) + user, found := GetUserDataFast(ctx, uc.GetUserID()) if !found { ctx := context.Background() - resp, err := proClient.UserData(ctx) + resp, err := fetchUserDataWithClient(ctx, proClient, uc) if err != nil { return false, false } @@ -105,6 +105,24 @@ func IsProUser(ctx context.Context, proClient pro.ProClient, userId int64) (isPr return isActive(user), true } +func fetchUserDataWithClient(ctx context.Context, proClient pro.ProClient, uc common.UserConfig) (*pro.UserDataResponse, error) { + userID := uc.GetUserID() + log.Debugf("Fetching user status with device ID '%v', user ID '%v' and proToken %v", + uc.GetDeviceID(), userID, uc.GetToken()) + resp, err := proClient.UserData(ctx) + if err != nil { + return nil, err + } + setUserData(ctx, userID, resp.User) + log.Debugf("User %d is '%v'", userID, resp.User.UserStatus) + return resp, nil +} + +func setUserData(ctx context.Context, userID int64, user *protos.User) { + log.Debugf("Storing user data for user %v", userID) + userData.save(ctx, userID, user) +} + // isActive determines whether the given status is an active status func isActive(status string) bool { return status == "active" diff --git a/desktop/lib.go b/desktop/lib.go index 9bfe7f84d..e7e02891a 100644 --- a/desktop/lib.go +++ b/desktop/lib.go @@ -208,7 +208,6 @@ func plans() *C.char { } paymentMethodsResponse := &proclient.PaymentMethodsResponse{} err := json.Unmarshal(plans, paymentMethodsResponse) - log.Debugf("DEBUG: cache payment methods found : %v", paymentMethodsResponse.Plans) plansByte, err := json.Marshal(paymentMethodsResponse.Plans) if err != nil { return sendError(errors.New("error fetching payment methods: %v", err)) @@ -238,7 +237,6 @@ func paymentMethodsV4() *C.char { if err != nil { return sendError(err) } - log.Debugf("DEBUG: cache payment methods: %v", paymentMethodsResponse.Providers) b, _ := json.Marshal(paymentMethodsResponse) return C.CString(string(b)) } @@ -255,6 +253,28 @@ func getUserData() (*protos.User, error) { return user, nil } +// this method is reposible for checking if the user has updated plan or bought plans +// +//export hasPlanUpdatedOrBuy +func hasPlanUpdatedOrBuy() *C.char { + //Get the cached user data + log.Debugf("DEBUG: Checking if user has updated plan or bought new plan") + uc := userConfig(a.Settings()) + cahcheUserData, isOldFound := app.GetUserDataFast(context.Background(), uc.GetUserID()) + //Get latest user data + resp, err := proClient.UserData(context.Background()) + if err != nil { + return sendError(err) + } + if isOldFound { + if cahcheUserData.Expiration < resp.User.Expiration { + // New data has a later expiration + return C.CString(string("true")) + } + } + return C.CString(string("false")) +} + //export devices func devices() *C.char { user, err := getUserData() @@ -459,7 +479,7 @@ func acceptedTermsVersion() *C.char { func proUser() *C.char { ctx := context.Background() // refresh user data when home page is loaded on desktop - go proClient.UserData(ctx) + go getUserData() uc := a.Settings() if isProUser, ok := app.IsProUserFast(ctx, uc); isProUser && ok { return C.CString("true") diff --git a/lib/account/account.dart b/lib/account/account.dart index 34f23a9c7..6bdcfd77e 100644 --- a/lib/account/account.dart +++ b/lib/account/account.dart @@ -31,7 +31,7 @@ class AccountMenu extends StatelessWidget { borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16))), builder: (context) { - return FollowUs(); + return const FollowUs(); }, ); } diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index f0c9a3353..55d58dab0 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -479,6 +479,11 @@ class SessionModel extends Model { return methodChannel.invokeMethod('updatePaymentPlans'); } + Future hasUpdatePlansOrBuy() async { + return compute(ffiHasPlanUpdateOrBuy,''); + } + + Plan planFromJson(Map item) { print("called plans $item"); final locale = Localization.locale; diff --git a/lib/ffi.dart b/lib/ffi.dart index 8feb9ee52..118b2be98 100644 --- a/lib/ffi.dart +++ b/lib/ffi.dart @@ -47,9 +47,10 @@ Future ffiUserData() async { // checkAPIError throws a PlatformException if the API response contains an error void checkAPIError(result, errorMessage) { - if(result is String){ - final errorMessageMap = jsonDecode(result); - throw PlatformException(code:errorMessageMap.toString(), message: errorMessage); + if (result is String) { + final errorMessageMap = jsonDecode(result); + throw PlatformException( + code: errorMessageMap.toString(), message: errorMessage); } if (result.error != "") { throw PlatformException(code: result.error, message: errorMessage); @@ -80,6 +81,12 @@ Future ffiRemoveDevice(String deviceId) async { return; } +FutureOr ffiHasPlanUpdateOrBuy(dynamic context) { + final json = _bindings.hasPlanUpdatedOrBuy().cast().toDartString(); + print('Result of hasPlanUpdatedOrBuy: $json'); + return json == 'true' ? true : throw NoPlansUpdate("No Plans update"); +} + Pointer ffiDevices() => _bindings.devices().cast(); Pointer ffiDevelopmentMode() => _bindings.developmentMode().cast(); @@ -117,7 +124,9 @@ Pointer ffiCheckUpdates() => _bindings.checkUpdates().cast(); Pointer ffiPlans() => _bindings.plans().cast(); Pointer ffiPaymentMethods() => _bindings.paymentMethodsV3().cast(); -Pointer ffiPaymentMethodsV4() => _bindings.paymentMethodsV4().cast(); + +Pointer ffiPaymentMethodsV4() => + _bindings.paymentMethodsV4().cast(); Pointer ffiDeviceLinkingCode() => _bindings.deviceLinkingCode().cast(); @@ -186,3 +195,11 @@ final NativeLibrary _bindings = NativeLibrary(_dylib); void loadLibrary() { _bindings.start(); } + +//Custom exception for handling error + +class NoPlansUpdate implements Exception { + String message; + + NoPlansUpdate(this.message); +} diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 059cf32c0..e87f5fced 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -4,6 +4,7 @@ import 'package:lantern/common/common_desktop.dart'; import 'package:lantern/plans/payment_provider.dart'; import 'package:lantern/plans/plan_details.dart'; import 'package:lantern/plans/utils.dart'; +import 'package:retry/retry.dart'; @RoutePage(name: 'Checkout') class Checkout extends StatefulWidget { @@ -385,6 +386,8 @@ class _CheckoutState extends State provider: provider, redirectUrl: redirectUrl, onClose: checkProUser); + //as soon user click we should start polling userData + Future.delayed(const Duration(seconds: 2), hasPlansUpdateOrBuy); } catch (error, stackTrace) { context.loaderOverlay.hide(); showError(context, error: error, stackTrace: stackTrace); @@ -473,4 +476,27 @@ class _CheckoutState extends State return false; } } + + ///This methods is responsible for polling for user data + ///so if user has done payment or renew plans and show + void hasPlansUpdateOrBuy() { + appLogger.i("calling hasPlansUpdateOrBuy to update plans or buy"); + try { + retry( + () async { + /// Polling for userData that user has updates plans or buy + final plansUpdated = await sessionModel.hasUpdatePlansOrBuy(); + if (plansUpdated) { + if (mounted) { + showSuccessDialog(context, widget.isPro); + } + } + }, + delayFactor: const Duration(seconds: 2), + retryIf: (e) => e is NoPlansUpdate, + ); + } catch (e) { + appLogger.e('Error while polling for plans update or buy', error: e); + } + } } diff --git a/lib/plans/utils.dart b/lib/plans/utils.dart index 7cd624d7f..a9ddf49e4 100644 --- a/lib/plans/utils.dart +++ b/lib/plans/utils.dart @@ -113,7 +113,7 @@ Future openDesktopWebview( break; case 'macos': if (provider == Providers.fropay.name) { - // Open with system browser browser on mac due to not able to by pass humans verification. + // Open with system browser browser on mac due to not able to by pass human verification. await InAppBrowser.openWithSystemBrowser(url: WebUri(redirectUrl)); } else { final browser = AppBrowser(onClose: onClose);