ipulse-shared-core-ftredge 16.0.1__py3-none-any.whl → 19.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ipulse-shared-core-ftredge might be problematic. Click here for more details.
- ipulse_shared_core_ftredge/__init__.py +1 -12
- ipulse_shared_core_ftredge/dependencies/authz_for_apis.py +8 -5
- ipulse_shared_core_ftredge/exceptions/__init__.py +47 -0
- ipulse_shared_core_ftredge/exceptions/user_exceptions.py +219 -0
- ipulse_shared_core_ftredge/models/__init__.py +1 -3
- ipulse_shared_core_ftredge/models/base_api_response.py +15 -0
- ipulse_shared_core_ftredge/models/base_data_model.py +7 -6
- ipulse_shared_core_ftredge/models/user_auth.py +59 -4
- ipulse_shared_core_ftredge/models/user_profile.py +41 -7
- ipulse_shared_core_ftredge/models/user_status.py +44 -138
- ipulse_shared_core_ftredge/monitoring/__init__.py +5 -0
- ipulse_shared_core_ftredge/monitoring/microservmon.py +483 -0
- ipulse_shared_core_ftredge/services/__init__.py +21 -14
- ipulse_shared_core_ftredge/services/base/__init__.py +12 -0
- ipulse_shared_core_ftredge/services/base/base_firestore_service.py +520 -0
- ipulse_shared_core_ftredge/services/cache_aware_firestore_service.py +44 -8
- ipulse_shared_core_ftredge/services/charging_service.py +1 -1
- ipulse_shared_core_ftredge/services/user/__init__.py +37 -0
- ipulse_shared_core_ftredge/services/user/iam_management_operations.py +326 -0
- ipulse_shared_core_ftredge/services/user/subscription_management_operations.py +384 -0
- ipulse_shared_core_ftredge/services/user/user_account_operations.py +479 -0
- ipulse_shared_core_ftredge/services/user/user_auth_operations.py +305 -0
- ipulse_shared_core_ftredge/services/user/user_core_service.py +651 -0
- ipulse_shared_core_ftredge/services/user/user_holistic_operations.py +436 -0
- {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/METADATA +2 -2
- ipulse_shared_core_ftredge-19.0.1.dist-info/RECORD +41 -0
- {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/WHEEL +1 -1
- ipulse_shared_core_ftredge/models/organization_profile.py +0 -96
- ipulse_shared_core_ftredge/models/user_profile_update.py +0 -39
- ipulse_shared_core_ftredge/services/base_firestore_service.py +0 -249
- ipulse_shared_core_ftredge/services/fastapiservicemon.py +0 -140
- ipulse_shared_core_ftredge/services/servicemon.py +0 -240
- ipulse_shared_core_ftredge-16.0.1.dist-info/RECORD +0 -33
- ipulse_shared_core_ftredge/{services/base_service_exceptions.py → exceptions/base_exceptions.py} +1 -1
- {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/licenses/LICENCE +0 -0
- {ipulse_shared_core_ftredge-16.0.1.dist-info → ipulse_shared_core_ftredge-19.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Subscription Management Operations - Handle user subscriptions and related operations
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from google.cloud import firestore
|
|
8
|
+
from pydantic import BaseModel
|
|
9
|
+
|
|
10
|
+
from ...models.subscription import Subscription
|
|
11
|
+
from ...exceptions import ResourceNotFoundError
|
|
12
|
+
from ..base import BaseFirestoreService
|
|
13
|
+
from ipulse_shared_base_ftredge.enums import SubscriptionPlan, SubscriptionStatus
|
|
14
|
+
from ...exceptions import SubscriptionError, UserStatusError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Model for subscription plan data from Firestore
|
|
18
|
+
class SubscriptionPlanDocument(BaseModel):
|
|
19
|
+
"""Model for subscription plan documents stored in Firestore"""
|
|
20
|
+
id: str
|
|
21
|
+
plan_name: str
|
|
22
|
+
plan_version: int
|
|
23
|
+
display_name: str
|
|
24
|
+
description: str
|
|
25
|
+
default_iam_domain_permissions: Optional[Dict[str, Dict[str, List[str]]]] = None
|
|
26
|
+
subscription_based_insight_credits_per_update: Optional[int] = None
|
|
27
|
+
subscription_based_insight_credits_update_freq_h: Optional[int] = None
|
|
28
|
+
extra_insight_credits_per_cycle: Optional[int] = None
|
|
29
|
+
voting_credits_per_update: Optional[int] = None
|
|
30
|
+
voting_credits_update_freq_h: Optional[int] = None
|
|
31
|
+
plan_validity_cycle_length: Optional[int] = None
|
|
32
|
+
plan_validity_cycle_unit: Optional[str] = None
|
|
33
|
+
plan_per_cycle_price_usd: Optional[float] = None
|
|
34
|
+
plan_auto_renewal: Optional[bool] = None
|
|
35
|
+
fallback_plan_id_if_current_plan_expired: Optional[str] = None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SubscriptionManagementOperations:
|
|
39
|
+
"""
|
|
40
|
+
Handles subscription-related operations for users
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
firestore_client: firestore.Client,
|
|
46
|
+
user_account_ops, # UserManagementOperations instance
|
|
47
|
+
logger: Optional[logging.Logger] = None,
|
|
48
|
+
timeout: float = 10.0,
|
|
49
|
+
subscription_plans_collection: str = "papp_core_configs_subscriptionplans_defaults"
|
|
50
|
+
):
|
|
51
|
+
self.db = firestore_client
|
|
52
|
+
self.user_account_ops = user_account_ops
|
|
53
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
54
|
+
self.timeout = timeout
|
|
55
|
+
|
|
56
|
+
# Initialize subscription plans DB service
|
|
57
|
+
self._subscription_plans_db_service = BaseFirestoreService[SubscriptionPlanDocument](
|
|
58
|
+
db=self.db,
|
|
59
|
+
collection_name=subscription_plans_collection,
|
|
60
|
+
resource_type="SubscriptionPlan",
|
|
61
|
+
logger=self.logger,
|
|
62
|
+
timeout=self.timeout
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
async def fetch_subscription_plan_details(self, plan_id: str) -> Optional[SubscriptionPlanDocument]:
|
|
66
|
+
"""Fetch subscription plan details from Firestore"""
|
|
67
|
+
try:
|
|
68
|
+
plan_data_dict = await self._subscription_plans_db_service.get_document(plan_id)
|
|
69
|
+
if not plan_data_dict:
|
|
70
|
+
self.logger.warning(f"Subscription plan with ID '{plan_id}' not found")
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
# Add the plan_id to the dict if it's not already there
|
|
74
|
+
plan_data_dict.setdefault('id', plan_id)
|
|
75
|
+
plan_doc = SubscriptionPlanDocument(**plan_data_dict)
|
|
76
|
+
self.logger.info(f"Successfully fetched subscription plan details for plan_id: {plan_id}")
|
|
77
|
+
return plan_doc
|
|
78
|
+
|
|
79
|
+
except ResourceNotFoundError:
|
|
80
|
+
self.logger.warning(f"Subscription plan '{plan_id}' not found")
|
|
81
|
+
return None
|
|
82
|
+
except Exception as e:
|
|
83
|
+
self.logger.error(f"Error fetching subscription plan details for {plan_id}: {e}", exc_info=True)
|
|
84
|
+
raise SubscriptionError(
|
|
85
|
+
detail=f"Failed to fetch subscription plan: {str(e)}",
|
|
86
|
+
plan_id=plan_id,
|
|
87
|
+
operation="fetch_subscription_plan_details",
|
|
88
|
+
original_error=e
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async def apply_subscription_plan(
|
|
92
|
+
self,
|
|
93
|
+
user_uid: str,
|
|
94
|
+
plan_id: str,
|
|
95
|
+
source: str = "system_default_config"
|
|
96
|
+
) -> Subscription:
|
|
97
|
+
"""Apply a subscription plan to a user"""
|
|
98
|
+
user_status = await self.user_account_ops.get_userstatus(user_uid)
|
|
99
|
+
if not user_status:
|
|
100
|
+
raise UserStatusError(
|
|
101
|
+
detail=f"UserStatus not found for user_uid {user_uid}",
|
|
102
|
+
user_uid=user_uid,
|
|
103
|
+
operation="apply_subscription_plan"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
plan_doc = await self.fetch_subscription_plan_details(plan_id)
|
|
107
|
+
if not plan_doc:
|
|
108
|
+
raise SubscriptionError(
|
|
109
|
+
detail=f"Subscription plan {plan_id} not found",
|
|
110
|
+
user_uid=user_uid,
|
|
111
|
+
plan_id=plan_id,
|
|
112
|
+
operation="apply_subscription_plan"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# Validate plan data
|
|
116
|
+
plan_name_str = plan_doc.plan_name
|
|
117
|
+
if not plan_name_str:
|
|
118
|
+
raise SubscriptionError(
|
|
119
|
+
detail="Plan name missing in plan details",
|
|
120
|
+
user_uid=user_uid,
|
|
121
|
+
plan_id=plan_id,
|
|
122
|
+
operation="apply_subscription_plan"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
plan_name_enum = SubscriptionPlan(plan_name_str)
|
|
127
|
+
except ValueError as e:
|
|
128
|
+
raise SubscriptionError(
|
|
129
|
+
detail=f"Invalid plan name '{plan_name_str}': {str(e)}",
|
|
130
|
+
user_uid=user_uid,
|
|
131
|
+
plan_id=plan_id,
|
|
132
|
+
operation="apply_subscription_plan",
|
|
133
|
+
original_error=e
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Validate required fields
|
|
137
|
+
plan_version = plan_doc.plan_version
|
|
138
|
+
validity_length = plan_doc.plan_validity_cycle_length
|
|
139
|
+
validity_unit = plan_doc.plan_validity_cycle_unit
|
|
140
|
+
|
|
141
|
+
if not all([
|
|
142
|
+
plan_version is not None and isinstance(plan_version, int),
|
|
143
|
+
validity_length is not None and isinstance(validity_length, int),
|
|
144
|
+
validity_unit is not None and isinstance(validity_unit, str)
|
|
145
|
+
]):
|
|
146
|
+
raise SubscriptionError(
|
|
147
|
+
detail="Missing or invalid subscription duration fields",
|
|
148
|
+
user_uid=user_uid,
|
|
149
|
+
plan_id=plan_id,
|
|
150
|
+
operation="apply_subscription_plan"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Create subscription
|
|
154
|
+
start_date = datetime.now(timezone.utc)
|
|
155
|
+
# At this point, we know validity_length and validity_unit are not None
|
|
156
|
+
# Type assertions to satisfy type checker
|
|
157
|
+
assert validity_length is not None and validity_unit is not None
|
|
158
|
+
end_date = Subscription.calculate_cycle_end_date(start_date, validity_length, validity_unit)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
new_subscription = Subscription(
|
|
162
|
+
plan_name=plan_name_enum,
|
|
163
|
+
plan_version=int(plan_version),
|
|
164
|
+
plan_id=plan_id,
|
|
165
|
+
cycle_start_date=start_date,
|
|
166
|
+
cycle_end_date=end_date,
|
|
167
|
+
validity_time_length=validity_length, # We already validated it's not None
|
|
168
|
+
validity_time_unit=validity_unit, # We already validated it's not None
|
|
169
|
+
auto_renew=plan_doc.plan_auto_renewal if plan_doc.plan_auto_renewal is not None else False,
|
|
170
|
+
status=SubscriptionStatus.ACTIVE,
|
|
171
|
+
default_iam_domain_permissions=plan_doc.default_iam_domain_permissions or {},
|
|
172
|
+
fallback_plan_id=plan_doc.fallback_plan_id_if_current_plan_expired,
|
|
173
|
+
price_paid_usd=float(plan_doc.plan_per_cycle_price_usd if plan_doc.plan_per_cycle_price_usd is not None else 0.0),
|
|
174
|
+
created_by=source,
|
|
175
|
+
updated_by=source,
|
|
176
|
+
subscription_based_insight_credits_per_update=int(plan_doc.subscription_based_insight_credits_per_update if plan_doc.subscription_based_insight_credits_per_update is not None else 0),
|
|
177
|
+
subscription_based_insight_credits_update_freq_h=int(plan_doc.subscription_based_insight_credits_update_freq_h if plan_doc.subscription_based_insight_credits_update_freq_h is not None else 24),
|
|
178
|
+
extra_insight_credits_per_cycle=int(plan_doc.extra_insight_credits_per_cycle if plan_doc.extra_insight_credits_per_cycle is not None else 0),
|
|
179
|
+
voting_credits_per_update=int(plan_doc.voting_credits_per_update if plan_doc.voting_credits_per_update is not None else 0),
|
|
180
|
+
voting_credits_update_freq_h=int(plan_doc.voting_credits_update_freq_h if plan_doc.voting_credits_update_freq_h is not None else 744),
|
|
181
|
+
)
|
|
182
|
+
except Exception as e:
|
|
183
|
+
raise SubscriptionError(
|
|
184
|
+
detail=f"Failed to create subscription object: {str(e)}",
|
|
185
|
+
user_uid=user_uid,
|
|
186
|
+
plan_id=plan_id,
|
|
187
|
+
operation="apply_subscription_plan",
|
|
188
|
+
original_error=e
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Apply subscription to user status
|
|
192
|
+
try:
|
|
193
|
+
user_status.apply_subscription(new_subscription)
|
|
194
|
+
user_status.updated_at = datetime.now(timezone.utc)
|
|
195
|
+
user_status.updated_by = f"SubscriptionManagement.apply_plan:{source}"
|
|
196
|
+
|
|
197
|
+
await self.user_account_ops.update_userstatus(
|
|
198
|
+
user_uid=user_uid,
|
|
199
|
+
status_data=user_status.model_dump(exclude_none=True),
|
|
200
|
+
updater_uid=f"SubscriptionManagement:{source}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
self.logger.info(f"Successfully applied subscription plan {plan_id} for user {user_uid}")
|
|
204
|
+
return new_subscription
|
|
205
|
+
|
|
206
|
+
except Exception as e:
|
|
207
|
+
self.logger.error(f"Failed to apply subscription to user status: {e}", exc_info=True)
|
|
208
|
+
raise SubscriptionError(
|
|
209
|
+
detail=f"Failed to apply subscription to user: {str(e)}",
|
|
210
|
+
user_uid=user_uid,
|
|
211
|
+
plan_id=plan_id,
|
|
212
|
+
operation="apply_subscription_plan",
|
|
213
|
+
original_error=e
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
async def get_user_active_subscription(self, user_uid: str) -> Optional[Subscription]:
|
|
217
|
+
"""Get the user's currently active subscription"""
|
|
218
|
+
user_status = await self.user_account_ops.get_userstatus(user_uid)
|
|
219
|
+
if user_status and user_status.active_subscription and user_status.active_subscription.is_active():
|
|
220
|
+
self.logger.info(f"Active subscription found for user {user_uid}: {user_status.active_subscription.plan_id}")
|
|
221
|
+
return user_status.active_subscription
|
|
222
|
+
|
|
223
|
+
self.logger.info(f"No active subscription found for user {user_uid}")
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
async def change_user_subscription(
|
|
227
|
+
self,
|
|
228
|
+
user_uid: str,
|
|
229
|
+
new_plan_id: str,
|
|
230
|
+
source: Optional[str] = None
|
|
231
|
+
) -> Optional[Subscription]:
|
|
232
|
+
"""Change a user's subscription to a new plan"""
|
|
233
|
+
self.logger.info(f"Attempting to change subscription for user {user_uid} to plan {new_plan_id}")
|
|
234
|
+
|
|
235
|
+
user_status = await self.user_account_ops.get_userstatus(user_uid)
|
|
236
|
+
if not user_status:
|
|
237
|
+
raise UserStatusError(
|
|
238
|
+
detail=f"UserStatus not found for user_uid {user_uid}",
|
|
239
|
+
user_uid=user_uid,
|
|
240
|
+
operation="change_user_subscription"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
effective_source = source or f"user_initiated_change_uid_{user_uid}"
|
|
244
|
+
|
|
245
|
+
# Archive current subscription if exists
|
|
246
|
+
if user_status.active_subscription:
|
|
247
|
+
self.logger.info(f"Archiving current active subscription {user_status.active_subscription.plan_id} for user {user_uid}")
|
|
248
|
+
old_subscription = user_status.active_subscription
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
old_subscription_dict = old_subscription.model_dump()
|
|
252
|
+
old_subscription_dict['status'] = SubscriptionStatus.INACTIVE
|
|
253
|
+
old_subscription_dict['updated_at'] = datetime.now(timezone.utc)
|
|
254
|
+
old_subscription_dict['updated_by'] = f"superseded_by_{new_plan_id}_via_{effective_source}"
|
|
255
|
+
|
|
256
|
+
modified_old_subscription = Subscription(**old_subscription_dict)
|
|
257
|
+
|
|
258
|
+
if user_status.subscriptions_history is None:
|
|
259
|
+
user_status.subscriptions_history = {}
|
|
260
|
+
user_status.subscriptions_history[modified_old_subscription.uuid] = modified_old_subscription
|
|
261
|
+
|
|
262
|
+
except Exception as e:
|
|
263
|
+
self.logger.error(f"Failed to archive old subscription: {e}", exc_info=True)
|
|
264
|
+
raise SubscriptionError(
|
|
265
|
+
detail=f"Failed to archive old subscription: {str(e)}",
|
|
266
|
+
user_uid=user_uid,
|
|
267
|
+
plan_id=new_plan_id,
|
|
268
|
+
operation="change_user_subscription",
|
|
269
|
+
original_error=e
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Apply new subscription
|
|
273
|
+
try:
|
|
274
|
+
new_subscription_obj = await self.apply_subscription_plan(user_uid, new_plan_id, source=effective_source)
|
|
275
|
+
self.logger.info(f"Successfully changed subscription for user {user_uid} to {new_plan_id}")
|
|
276
|
+
return new_subscription_obj
|
|
277
|
+
except Exception as e:
|
|
278
|
+
self.logger.error(f"Error changing subscription for user {user_uid} to {new_plan_id}: {e}", exc_info=True)
|
|
279
|
+
raise
|
|
280
|
+
|
|
281
|
+
async def cancel_user_subscription(
|
|
282
|
+
self,
|
|
283
|
+
user_uid: str,
|
|
284
|
+
reason: Optional[str] = None,
|
|
285
|
+
cancelled_by: Optional[str] = None
|
|
286
|
+
) -> bool:
|
|
287
|
+
"""Cancel a user's active subscription"""
|
|
288
|
+
self.logger.info(f"Attempting to cancel subscription for user {user_uid}. Reason: {reason}")
|
|
289
|
+
|
|
290
|
+
user_status = await self.user_account_ops.get_userstatus(user_uid)
|
|
291
|
+
if not user_status:
|
|
292
|
+
raise UserStatusError(
|
|
293
|
+
detail=f"UserStatus not found for user_uid {user_uid}",
|
|
294
|
+
user_uid=user_uid,
|
|
295
|
+
operation="cancel_user_subscription"
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
effective_canceller = cancelled_by or f"SubscriptionManagement.cancel:{reason or 'not_specified'}"
|
|
299
|
+
|
|
300
|
+
if user_status.active_subscription and user_status.active_subscription.status == SubscriptionStatus.ACTIVE:
|
|
301
|
+
try:
|
|
302
|
+
active_sub_dict = user_status.active_subscription.model_dump()
|
|
303
|
+
|
|
304
|
+
self.logger.info(f"Cancelling active subscription {active_sub_dict['plan_id']} for user {user_uid}")
|
|
305
|
+
|
|
306
|
+
active_sub_dict['status'] = SubscriptionStatus.CANCELLED
|
|
307
|
+
active_sub_dict['auto_renew'] = False
|
|
308
|
+
active_sub_dict['updated_at'] = datetime.now(timezone.utc)
|
|
309
|
+
active_sub_dict['updated_by'] = effective_canceller
|
|
310
|
+
|
|
311
|
+
cancelled_subscription = Subscription(**active_sub_dict)
|
|
312
|
+
|
|
313
|
+
if user_status.subscriptions_history is None:
|
|
314
|
+
user_status.subscriptions_history = {}
|
|
315
|
+
user_status.subscriptions_history[cancelled_subscription.uuid] = cancelled_subscription
|
|
316
|
+
|
|
317
|
+
user_status.revoke_subscription()
|
|
318
|
+
user_status.updated_at = datetime.now(timezone.utc)
|
|
319
|
+
user_status.updated_by = effective_canceller
|
|
320
|
+
|
|
321
|
+
await self.user_account_ops.update_userstatus(
|
|
322
|
+
user_uid=user_uid,
|
|
323
|
+
status_data=user_status.model_dump(exclude_none=True),
|
|
324
|
+
updater_uid=effective_canceller
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
self.logger.info(f"Successfully cancelled subscription for user {user_uid}")
|
|
328
|
+
return True
|
|
329
|
+
|
|
330
|
+
except Exception as e:
|
|
331
|
+
self.logger.error(f"Failed to cancel subscription: {e}", exc_info=True)
|
|
332
|
+
raise SubscriptionError(
|
|
333
|
+
detail=f"Failed to cancel subscription: {str(e)}",
|
|
334
|
+
user_uid=user_uid,
|
|
335
|
+
operation="cancel_user_subscription",
|
|
336
|
+
original_error=e
|
|
337
|
+
)
|
|
338
|
+
else:
|
|
339
|
+
self.logger.info(f"No active subscription to cancel for user {user_uid}")
|
|
340
|
+
return False
|
|
341
|
+
|
|
342
|
+
async def get_subscription_history(self, user_uid: str) -> Dict[str, Subscription]:
|
|
343
|
+
"""Get the user's subscription history"""
|
|
344
|
+
user_status = await self.user_account_ops.get_userstatus(user_uid)
|
|
345
|
+
if user_status and user_status.subscriptions_history:
|
|
346
|
+
return user_status.subscriptions_history
|
|
347
|
+
return {}
|
|
348
|
+
|
|
349
|
+
async def get_all_subscription_plans(self) -> List[SubscriptionPlanDocument]:
|
|
350
|
+
"""Get all available subscription plans"""
|
|
351
|
+
try:
|
|
352
|
+
# This would require implementing a list_documents method in BaseFirestoreService
|
|
353
|
+
# For now, we'll implement a basic version
|
|
354
|
+
collection_ref = self.db.collection(self._subscription_plans_db_service.collection_name)
|
|
355
|
+
docs = collection_ref.stream()
|
|
356
|
+
|
|
357
|
+
plans = []
|
|
358
|
+
for doc in docs:
|
|
359
|
+
plan_data = doc.to_dict()
|
|
360
|
+
plan_data['id'] = doc.id
|
|
361
|
+
try:
|
|
362
|
+
plan = SubscriptionPlanDocument(**plan_data)
|
|
363
|
+
plans.append(plan)
|
|
364
|
+
except Exception as e:
|
|
365
|
+
self.logger.warning(f"Failed to parse subscription plan {doc.id}: {e}")
|
|
366
|
+
continue
|
|
367
|
+
|
|
368
|
+
return plans
|
|
369
|
+
|
|
370
|
+
except Exception as e:
|
|
371
|
+
self.logger.error(f"Error fetching subscription plans: {e}", exc_info=True)
|
|
372
|
+
raise SubscriptionError(
|
|
373
|
+
detail=f"Failed to fetch subscription plans: {str(e)}",
|
|
374
|
+
operation="get_all_subscription_plans",
|
|
375
|
+
original_error=e
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
async def validate_subscription_plan(self, plan_id: str) -> bool:
|
|
379
|
+
"""Validate if a subscription plan exists and is valid"""
|
|
380
|
+
try:
|
|
381
|
+
plan = await self.fetch_subscription_plan_details(plan_id)
|
|
382
|
+
return plan is not None
|
|
383
|
+
except Exception:
|
|
384
|
+
return False
|