18.6 C
Canberra
Tuesday, March 17, 2026

ios – restorePurchases isn’t working as anticipated Flutter


I’m caught with a use case associated to in-app purchases. I’ve carried out in-app purchases with two subscription gadgets.

The merchandise are loading accurately from Apple. I’ve additionally carried out an auto-reload logic the place, if the subscription is already bought, it ought to be highlighted; in any other case the display screen stays the identical.

Nonetheless, the difficulty I’m going through now could be that restorePurchases() isn’t returning any response. Earlier it was working completely, however now it appears to have stopped working.

I’m unsure whether or not I by accident added one thing incorrect or if current modifications brought about this problem.

I’ve connected the code under. If you happen to discover something incorrect or one thing that might trigger this drawback, please let me know.

const Set iosProductIds = {
  'monthly_iOS_Seven_Free_Days',
  'yearly_iOS_Seven_Free_Days',
};

const Set androidProductIds = {
  'premium',
  'premium_yearly',
};

class AppleCubit extends Cubit {
  ultimate InAppPurchase _iap = InAppPurchase.occasion;

  StreamSubscription>? _purchaseSubscription;

  bool globalWebRedirectDone = false;
  Timer? _debounceTimer;
  Record _pendingPurchases = [];
  bool _notificationShown = false;
  bool _isRestoring = false;
  ultimate Set _processedPurchaseIds = {};

  AppleCubit({bool autoRestore = false})
    : tremendous(AppleState()) {
    _initStore(autoRestore: autoRestore);
  }

  // ---------------- INIT ----------------
  Future _initStore({bool autoRestore = false}) async {
    emit(state.copyWith(loading: state.merchandise.isEmpty));
    if (!kIsWeb) {
      _notificationShown = false;

      ultimate isAvailable = await _iap.isAvailable();
      emit(state.copyWith(storeAvailable: isAvailable));

      if (!isAvailable) {
        emit(state.copyWith(loading: false));
        return;
      }

      _purchaseSubscription = _iap.purchaseStream.pay attention(
        _onPurchaseUpdated,
        onError: (error) {
          emit(
            state.copyWith(
              loading: false,
              error: "Buy stream error: $error",
            ),
          );
        },
      );

      await _loadProducts();

      if (autoRestore) {
        await restorePurchases();
      }
    }
  }

  Future handleWebRedirectionIfNeeded() async {
    if (globalWebRedirectDone) return;
    globalWebRedirectDone = true;

    ultimate webSessionToken = await AppleSubscriptionRepository()
        .getSubscriptionSessionToken();

    if (webSessionToken.session.isEmpty) return;

    ultimate callback = Uri.encodeComponent(Uri.base.toString());

    ultimate url ="select/${webSessionToken.session}?callback=$callback";

    print(url);

    ultimate uri = Uri.parse(url);
    if (kIsWeb) {
      await launchUrl(uri, webOnlyWindowName: '_self');
    } else {
      await launchUrl(uri, mode: LaunchMode.externalApplication);
    }
  }

  // ---------------- PRODUCTS ----------------

  Future _loadProducts() async {
    strive {
      ultimate productIds = kIsWeb
          ? {}
          : Platform.isIOS
          ? iosProductIds
          : androidProductIds;

      ultimate response = await _iap.queryProductDetails(productIds);

      if (response.error != null) {
        emit(state.copyWith(loading: false, error: response.error!.message));
        return;
      }

      if (response.productDetails.isEmpty) {
        emit(state.copyWith(loading: false, error: "No subscriptions discovered"));
        return;
      }

      emit(state.copyWith(loading: false, merchandise: response.productDetails));
    } catch (e) {
      emit(
        state.copyWith(
          loading: false,
          error: "Didn't load subscriptions: $e",
        ),
      );
    }
  }

  // ---------------- PLAN SELECTION ----------------

  void selectPlan(ProductDetails product) {
    emit(state.copyWith(selectedProduct: product));
  }

  // ---------------- BUY ----------------

  Future buySelected() async {
    ultimate product = state.selectedProduct;

    if (product == null) {
      emit(state.copyWith(error: "No subscription chosen"));
      return;
    }

    emit(state.copyWith(loading: true, standing: CommonApiStatus.preliminary));

    ultimate purchaseParam = PurchaseParam(productDetails: product);

    strive {
      await _iap.buyNonConsumable(purchaseParam: purchaseParam);
    } on PlatformException catch (e) {
      ultimate cancelled = e.code == 'storekit2_purchase_cancelled';

      emit(
        state.copyWith(
          loading: false,
          error: cancelled
              ? "Buy cancelled by person"
              : "Buy failed: ${e.message}",
        ),
      );
    } catch (e) {
      emit(
        state.copyWith(loading: false, error: "Surprising buy error: $e"),
      );
    }
  }

  // ---------------- PURCHASE STREAM (DEBOUNCED) ----------------

  void _onPurchaseUpdated(Record purchases) {
    if (_isRestoring && purchases.isEmpty) {
      _isRestoring = false;
      emit(state.copyWith(loading: false));
      return;
    }

    _pendingPurchases.addAll(purchases);

    _debounceTimer?.cancel();
    _debounceTimer = Timer(const Length(milliseconds: 200), () async {
      ultimate pending = Record.from(_pendingPurchases);
      _pendingPurchases.clear();

      for (ultimate buy in pending) {
        ultimate purchaseKey =
            "${buy.productID}_${buy.purchaseID ?? buy.transactionDate}";

        if (_processedPurchaseIds.incorporates(purchaseKey)) {
          proceed;
        }

        _processedPurchaseIds.add(purchaseKey);

        if (state.activeProductId == buy.productID &&
            buy.standing == PurchaseStatus.bought) {
          proceed;
        }

        change (buy.standing) {
          case PurchaseStatus.bought:
          case PurchaseStatus.restored:
            await _handleSuccess(buy);
            break;

          case PurchaseStatus.pending:
            emit(
              state.copyWith(loading: true, standing: CommonApiStatus.preliminary),
            );
            break;

          case PurchaseStatus.error:
            emit(
              state.copyWith(
                loading: false,
                error: buy.error?.message ?? "Buy failed",
              ),
            );
            break;

          default:
            break;
        }
      }
    });
  }

  // ---------------- SUCCESS HANDLING ----------------

  Future _handleSuccess(PurchaseDetails buy) async {
    _isRestoring = false;

    strive {
      if (buy.pendingCompletePurchase) {
        await _iap.completePurchase(buy);
      }

      // Stop duplicate backend calls
      if (state.subscription?.standing == "energetic" &&
          state.subscription?.productId == buy.productID) {
        return;
      }

      await AppleSubscriptionRepository().postSubscriptionApi(
        token: buy.verificationData.serverVerificationData,
        selectedSubscritionId: buy.productID,
        platform: Platform.isIOS ? "ios" : "android",
        packageName: Platform.isAndroid ? "com.avioflai.aviation" : "",
      );

      ultimate backendResponse = await AppleSubscriptionRepository()
          .getSubscriptionDetails();

      ultimate resolvedProductId = _resolveActiveProductId(
        appleProductId: buy.productID,
        backendSubscription: backendResponse.knowledge,
      );

      //  if (!kIsWeb) {
      if (!_notificationShown &&
          buy.standing == PurchaseStatus.bought &&
          !kIsWeb) {
        _notificationShown = true;
        LocalNotificationHelper.present(
          title: "Subscription Energetic",
          physique: "All premium options are unlocked",
          screenName: "profileSS",
        );
      }

      ultimate isActive = backendResponse.knowledge?.standing == "energetic";

      emit(
        state.copyWith(
          bought: isActive,
          loading: false,
          standing: CommonApiStatus.success,
          activeProductId: resolvedProductId,
          subscription: backendResponse.knowledge,
        ),
      );
    } catch (e) {
      emit(
        state.copyWith(
          loading: false,
          error: "Subscription verification failed: $e",
        ),
      );
    }
  }

  String _resolveActiveProductId({
    String? appleProductId,
    SubscriptionData? backendSubscription,
  }) {
    if (backendSubscription?.productId != null &&
        backendSubscription!.productId.isNotEmpty) {
      return backendSubscription.productId;
    }
    return appleProductId ?? "";
  }

  // ---------------- RESTORE ----------------

  Future restorePurchases() async {
    _isRestoring = true;

    emit(state.copyWith(loading: true, standing: CommonApiStatus.preliminary));

    strive {
      await _iap.restorePurchases();
    } catch (e) {
      _isRestoring = false;

      emit(
        state.copyWith(
          loading: false,
          error: "Restore failed: $e",
          standing: CommonApiStatus.failure,
        ),
      );
    }
  }

  // ---------------- CANCEL SUBSCRIPTION ----------------
  Future guideUserToCancelSubscription() async {
    if (state.activeProductId == null) {
      emit(state.copyWith(error: "No energetic subscription to cancel"));
      return;
    }

    strive {
      if (Platform.isIOS) {
        // Open Apple subscriptions web page
        const url="https://apps.apple.com/account/subscriptions";
        if (await canLaunchUrl(Uri.parse(url))) {
          await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
        } else {
          emit(
            state.copyWith(error: "Can not open App Retailer subscriptions web page"),
          );
        }
      } else if (Platform.isAndroid) {
        // Open Google Play subscriptions web page
        const url="https://play.google.com/retailer/account/subscriptions";
        if (await canLaunchUrl(Uri.parse(url))) {
          await launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication);
        } else {
          emit(
            state.copyWith(error: "Can not open Play Retailer subscriptions web page"),
          );
        }
      }
    } catch (e) {
      emit(state.copyWith(error: "Didn't open subscription web page: $e"));
    }
  }

  Future cancelSubscription() async {
    if (state.activeProductId == null || state.activeProductId!.isEmpty) {
      emit(state.copyWith(error: "No energetic subscription to cancel"));
      return;
    }

    emit(state.copyWith(loading: true, standing: CommonApiStatus.preliminary));

    strive {
      // await AppleSubscriptionRepository().cancelSubscriptionApi(
      //   productId: state.activeProductId,
      //   platform: Platform.isIOS ? "ios" : "android",
      // );
      // Replace Cubit state after cancellation
      emit(
        state.copyWith(
          bought: false,
          activeProductId: "",
          subscription: null,
          loading: false,
          standing: CommonApiStatus.success,
        ),
      );

      if (!kIsWeb) {
        LocalNotificationHelper.present(
          title: "Subscription Cancelled",
          physique: "Your subscription has been cancelled efficiently.",
          screenName: "profileSS",
        );
      }
    } catch (e) {
      emit(
        state.copyWith(
          loading: false,
          standing: CommonApiStatus.failure,
          error: "Didn't cancel subscription: $e",
        ),
      );
    }
  }

  // ---------------- BACKEND SYNC ----------------

  Future getSubscriptionsFromBackendServer(String activeProductId) async {
    emit(state.copyWith(loading: true, standing: CommonApiStatus.preliminary));

    strive {
      ultimate response = await AppleSubscriptionRepository()
          .getSubscriptionDetails();

      emit(
        state.copyWith(
          subscription: response.knowledge,
          bought: response.knowledge?.standing == "energetic",
          activeProductId: activeProductId,
          loading: false,
          standing: CommonApiStatus.success,
        ),
      );
    } catch (e) {
      emit(
        state.copyWith(
          loading: false,
          standing: CommonApiStatus.failure,
          error: "Didn't fetch subscription: $e",
        ),
      );
    }
  }

  // ---------------- CLEANUP ----------------

  @override
  Future shut() {
    _purchaseSubscription?.cancel();
    _debounceTimer?.cancel();
    return tremendous.shut();
  }
}




Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles