mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: interpret subscription + usage on action
This commit is contained in:
parent
687091e2f2
commit
9668ab20f9
@ -5,6 +5,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -17,6 +18,7 @@ import 'package:bloc/bloc.dart';
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
part 'settings_billing_bloc.freezed.dart';
|
part 'settings_billing_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -113,8 +115,61 @@ class SettingsBillingBloc
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
cancelSubscription: (plan) async {
|
cancelSubscription: (plan) async {
|
||||||
await _userService.cancelSubscription(workspaceId, plan);
|
final result =
|
||||||
await _onPaymentSuccessful();
|
await _userService.cancelSubscription(workspaceId, plan);
|
||||||
|
final successOrNull = result.fold(
|
||||||
|
(_) => true,
|
||||||
|
(f) {
|
||||||
|
Log.error(
|
||||||
|
'Failed to cancel subscription of ${plan.label}: ${f.msg}',
|
||||||
|
f,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (successOrNull != true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate cache for this workspace
|
||||||
|
await UserBackendService.invalidateWorkspaceSubscriptionCache(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final subscriptionInfo = state.mapOrNull(
|
||||||
|
ready: (s) => s.subscriptionInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is impossible, but for good measure
|
||||||
|
if (subscriptionInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionInfo.freeze();
|
||||||
|
final newInfo = subscriptionInfo.rebuild((value) {
|
||||||
|
if (plan.isAddOn) {
|
||||||
|
value.addOns.removeWhere(
|
||||||
|
(addon) => addon.addOnSubscription.subscriptionPlan == plan,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.plan == WorkspacePlanPB.ProPlan) {
|
||||||
|
value.plan = WorkspacePlanPB.FreePlan;
|
||||||
|
value.planSubscription.freeze();
|
||||||
|
value.planSubscription = value.planSubscription.rebuild((sub) {
|
||||||
|
sub.status = WorkspaceSubscriptionStatusPB.Active;
|
||||||
|
sub.subscriptionPlan = SubscriptionPlanPB.None;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emit(
|
||||||
|
SettingsBillingState.ready(
|
||||||
|
subscriptionInfo: newInfo,
|
||||||
|
billingPortal: _billingPortal,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
paymentSuccessful: (plan) async {
|
paymentSuccessful: (plan) async {
|
||||||
final result = await UserBackendService.getWorkspaceSubscriptionInfo(
|
final result = await UserBackendService.getWorkspaceSubscriptionInfo(
|
||||||
@ -205,6 +260,7 @@ class SettingsBillingState extends Equatable with _$SettingsBillingState {
|
|||||||
subscription,
|
subscription,
|
||||||
billingPortal,
|
billingPortal,
|
||||||
plan,
|
plan,
|
||||||
|
...subscription.addOns,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart';
|
||||||
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -13,6 +14,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbserver.dart';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart';
|
||||||
|
|
||||||
part 'settings_plan_bloc.freezed.dart';
|
part 'settings_plan_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -87,7 +89,10 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(pl) => afLaunchUrlString(pl.paymentLink),
|
(pl) => afLaunchUrlString(pl.paymentLink),
|
||||||
(f) => Log.error(f.msg, f),
|
(f) => Log.error(
|
||||||
|
'Failed to fetch paymentlink for $plan: ${f.msg}',
|
||||||
|
f,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
cancelSubscription: () async {
|
cancelSubscription: () async {
|
||||||
@ -98,12 +103,68 @@ class SettingsPlanBloc extends Bloc<SettingsPlanEvent, SettingsPlanState> {
|
|||||||
|
|
||||||
// We can hardcode the subscription plan here because we cannot cancel addons
|
// We can hardcode the subscription plan here because we cannot cancel addons
|
||||||
// on the Plan page
|
// on the Plan page
|
||||||
await _userService.cancelSubscription(
|
final result = await _userService.cancelSubscription(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
SubscriptionPlanPB.Pro,
|
SubscriptionPlanPB.Pro,
|
||||||
);
|
);
|
||||||
|
|
||||||
add(const SettingsPlanEvent.started());
|
final successOrNull = result.fold(
|
||||||
|
(_) => true,
|
||||||
|
(f) {
|
||||||
|
Log.error('Failed to cancel subscription of Pro: ${f.msg}', f);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (successOrNull != true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate the cache
|
||||||
|
await UserBackendService.invalidateWorkspaceSubscriptionCache(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
final subscriptionInfo = state.mapOrNull(
|
||||||
|
ready: (s) => s.subscriptionInfo,
|
||||||
|
);
|
||||||
|
|
||||||
|
// This is impossible, but for good measure
|
||||||
|
if (subscriptionInfo == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We assume their new plan is Free, since we only have Pro plan
|
||||||
|
// at the moment.
|
||||||
|
subscriptionInfo.freeze();
|
||||||
|
final newInfo = subscriptionInfo.rebuild((value) {
|
||||||
|
value.plan = WorkspacePlanPB.FreePlan;
|
||||||
|
value.planSubscription.freeze();
|
||||||
|
value.planSubscription = value.planSubscription.rebuild((sub) {
|
||||||
|
sub.status = WorkspaceSubscriptionStatusPB.Active;
|
||||||
|
sub.subscriptionPlan = SubscriptionPlanPB.None;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// We need to remove unlimited indicator for storage and
|
||||||
|
// AI usage, if they don't have an addon that changes this behavior.
|
||||||
|
final usage = state.mapOrNull(ready: (s) => s.workspaceUsage)!;
|
||||||
|
|
||||||
|
usage.freeze();
|
||||||
|
final newUsage = usage.rebuild((value) {
|
||||||
|
if (!newInfo.hasAIMax && !newInfo.hasAIOnDevice) {
|
||||||
|
value.aiResponsesUnlimited = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value.storageBytesUnlimited = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
emit(
|
||||||
|
SettingsPlanState.ready(
|
||||||
|
subscriptionInfo: newInfo,
|
||||||
|
workspaceUsage: newUsage,
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
paymentSuccessful: (plan) {
|
paymentSuccessful: (plan) {
|
||||||
final readyState = state.mapOrNull(ready: (state) => state);
|
final readyState = state.mapOrNull(ready: (state) => state);
|
||||||
|
@ -66,3 +66,14 @@ extension ToRecognizable on SubscriptionPlanPB {
|
|||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension IsAddOn on SubscriptionPlanPB {
|
||||||
|
/// Returns true if the plan is an add-on and not
|
||||||
|
/// a workspace plan.
|
||||||
|
///
|
||||||
|
bool get isAddOn => switch (this) {
|
||||||
|
SubscriptionPlanPB.AiMax => true,
|
||||||
|
SubscriptionPlanPB.AiLocal => true,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user