geek-cafe-saas-sdk 0.7.0__py3-none-any.whl → 0.7.2__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 geek-cafe-saas-sdk might be problematic. Click here for more details.
- geek_cafe_saas_sdk/__init__.py +1 -1
- geek_cafe_saas_sdk/domains/files/models/directory.py +42 -6
- geek_cafe_saas_sdk/domains/files/models/file.py +40 -4
- geek_cafe_saas_sdk/domains/files/models/file_share.py +33 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +24 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +54 -135
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +60 -136
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +43 -104
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +57 -131
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +55 -7
- geek_cafe_saas_sdk/domains/notifications/__init__.py +18 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/__init__.py +1 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/create_webhook/app.py +73 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/get/app.py +40 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/get_preferences/app.py +34 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/list/app.py +43 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/list_webhooks/app.py +40 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/mark_read/app.py +40 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/send/app.py +83 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/update_preferences/app.py +45 -0
- geek_cafe_saas_sdk/domains/notifications/models/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/notifications/models/notification.py +717 -0
- geek_cafe_saas_sdk/domains/notifications/models/notification_preference.py +365 -0
- geek_cafe_saas_sdk/domains/notifications/models/webhook_subscription.py +339 -0
- geek_cafe_saas_sdk/domains/notifications/services/__init__.py +10 -0
- geek_cafe_saas_sdk/domains/notifications/services/notification_service.py +576 -0
- geek_cafe_saas_sdk/domains/payments/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/payments/handlers/README.md +334 -0
- geek_cafe_saas_sdk/domains/payments/handlers/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/create/app.py +105 -0
- geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/update/app.py +97 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/create/app.py +97 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payments/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payments/list/app.py +68 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payments/record/app.py +118 -0
- geek_cafe_saas_sdk/domains/payments/handlers/refunds/create/app.py +89 -0
- geek_cafe_saas_sdk/domains/payments/handlers/refunds/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/models/__init__.py +17 -0
- geek_cafe_saas_sdk/domains/payments/models/billing_account.py +521 -0
- geek_cafe_saas_sdk/domains/payments/models/payment.py +639 -0
- geek_cafe_saas_sdk/domains/payments/models/payment_intent_ref.py +539 -0
- geek_cafe_saas_sdk/domains/payments/models/refund.py +404 -0
- geek_cafe_saas_sdk/domains/payments/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/payments/services/payment_service.py +405 -0
- geek_cafe_saas_sdk/domains/subscriptions/__init__.py +19 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/README.md +408 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/__init__.py +1 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/create/app.py +81 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/get/app.py +48 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/list/app.py +54 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/update/app.py +54 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/create/app.py +83 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/get/app.py +47 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/validate/app.py +62 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/create/app.py +82 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/get/app.py +48 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/list/app.py +66 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/update/app.py +54 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/aggregate/app.py +72 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/record/app.py +89 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/addon.py +604 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/discount.py +492 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/plan.py +569 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/usage_record.py +300 -0
- geek_cafe_saas_sdk/domains/subscriptions/services/__init__.py +10 -0
- geek_cafe_saas_sdk/domains/subscriptions/services/subscription_manager_service.py +694 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +123 -1
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +213 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +7 -0
- geek_cafe_saas_sdk/services/database_service.py +10 -6
- geek_cafe_saas_sdk/utilities/environment_variables.py +16 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +77 -0
- {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/METADATA +1 -1
- {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/RECORD +79 -20
- {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/WHEEL +0 -0
- {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for creating discounts.
|
|
3
|
+
|
|
4
|
+
Admin endpoint - requires authentication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_body=True,
|
|
15
|
+
convert_case=True
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Create a new discount/promo code.
|
|
22
|
+
|
|
23
|
+
Expected body:
|
|
24
|
+
{
|
|
25
|
+
"discountCode": "SUMMER25",
|
|
26
|
+
"discountName": "Summer Sale",
|
|
27
|
+
"discountType": "percentage",
|
|
28
|
+
"percentOff": 25.0,
|
|
29
|
+
"duration": "repeating",
|
|
30
|
+
"durationInMonths": 3,
|
|
31
|
+
"maxRedemptions": 100
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Returns 201 with created discount
|
|
35
|
+
"""
|
|
36
|
+
return handler_wrapper.execute(event, context, create_discount, injected_service)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_discount(
|
|
40
|
+
event: Dict[str, Any],
|
|
41
|
+
service: SubscriptionManagerService,
|
|
42
|
+
user_context: Dict[str, str]
|
|
43
|
+
) -> Any:
|
|
44
|
+
"""
|
|
45
|
+
Business logic for creating a discount.
|
|
46
|
+
"""
|
|
47
|
+
payload = event["parsed_body"]
|
|
48
|
+
|
|
49
|
+
# Extract required fields
|
|
50
|
+
discount_code = payload.get("discount_code")
|
|
51
|
+
if not discount_code:
|
|
52
|
+
raise ValueError("discount_code is required")
|
|
53
|
+
|
|
54
|
+
discount_name = payload.get("discount_name")
|
|
55
|
+
if not discount_name:
|
|
56
|
+
raise ValueError("discount_name is required")
|
|
57
|
+
|
|
58
|
+
discount_type = payload.get("discount_type", "percentage")
|
|
59
|
+
|
|
60
|
+
# Build kwargs for optional fields
|
|
61
|
+
kwargs = {}
|
|
62
|
+
optional_fields = [
|
|
63
|
+
"description", "amount_off_cents", "percent_off", "trial_extension_days",
|
|
64
|
+
"currency", "duration", "duration_in_months",
|
|
65
|
+
"valid_from_utc_ts", "valid_until_utc_ts",
|
|
66
|
+
"max_redemptions", "max_redemptions_per_customer",
|
|
67
|
+
"status", "minimum_amount_cents",
|
|
68
|
+
"applies_to_plan_codes", "applies_to_addon_codes", "applies_to_intervals",
|
|
69
|
+
"first_time_transaction", "campaign_name", "source", "notes"
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
for field in optional_fields:
|
|
73
|
+
if field in payload:
|
|
74
|
+
kwargs[field] = payload[field]
|
|
75
|
+
|
|
76
|
+
result = service.create_discount(
|
|
77
|
+
discount_code=discount_code,
|
|
78
|
+
discount_name=discount_name,
|
|
79
|
+
discount_type=discount_type,
|
|
80
|
+
**kwargs
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return result
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for getting a discount.
|
|
3
|
+
|
|
4
|
+
Admin endpoint - requires authentication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_body=False,
|
|
15
|
+
convert_case=True
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Get a discount by ID.
|
|
22
|
+
|
|
23
|
+
Path parameters:
|
|
24
|
+
- discountId: Discount ID
|
|
25
|
+
|
|
26
|
+
Returns 200 with discount details
|
|
27
|
+
"""
|
|
28
|
+
return handler_wrapper.execute(event, context, get_discount, injected_service)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_discount(
|
|
32
|
+
event: Dict[str, Any],
|
|
33
|
+
service: SubscriptionManagerService,
|
|
34
|
+
user_context: Dict[str, str]
|
|
35
|
+
) -> Any:
|
|
36
|
+
"""
|
|
37
|
+
Business logic for getting a discount.
|
|
38
|
+
"""
|
|
39
|
+
path_params = event.get("pathParameters") or {}
|
|
40
|
+
discount_id = path_params.get("discount_id")
|
|
41
|
+
|
|
42
|
+
if not discount_id:
|
|
43
|
+
raise ValueError("discount_id is required in path")
|
|
44
|
+
|
|
45
|
+
result = service.get_discount(discount_id=discount_id)
|
|
46
|
+
|
|
47
|
+
return result
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for validating discount codes.
|
|
3
|
+
|
|
4
|
+
Public endpoint - no authentication required.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_auth=False,
|
|
15
|
+
require_body=True,
|
|
16
|
+
convert_case=True
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
21
|
+
"""
|
|
22
|
+
Validate a discount code.
|
|
23
|
+
|
|
24
|
+
Expected body:
|
|
25
|
+
{
|
|
26
|
+
"discountCode": "SUMMER25",
|
|
27
|
+
"planCode": "pro", // optional
|
|
28
|
+
"amountCents": 2999, // optional
|
|
29
|
+
"isFirstPurchase": false // optional
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Returns 200 with discount details if valid
|
|
33
|
+
"""
|
|
34
|
+
return handler_wrapper.execute(event, context, validate_discount, injected_service)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def validate_discount(
|
|
38
|
+
event: Dict[str, Any],
|
|
39
|
+
service: SubscriptionManagerService,
|
|
40
|
+
user_context: Dict[str, str]
|
|
41
|
+
) -> Any:
|
|
42
|
+
"""
|
|
43
|
+
Business logic for validating a discount.
|
|
44
|
+
"""
|
|
45
|
+
payload = event["parsed_body"]
|
|
46
|
+
|
|
47
|
+
discount_code = payload.get("discount_code")
|
|
48
|
+
if not discount_code:
|
|
49
|
+
raise ValueError("discount_code is required")
|
|
50
|
+
|
|
51
|
+
plan_code = payload.get("plan_code")
|
|
52
|
+
amount_cents = payload.get("amount_cents")
|
|
53
|
+
is_first_purchase = payload.get("is_first_purchase", False)
|
|
54
|
+
|
|
55
|
+
result = service.validate_discount(
|
|
56
|
+
discount_code=discount_code,
|
|
57
|
+
plan_code=plan_code,
|
|
58
|
+
amount_cents=amount_cents,
|
|
59
|
+
is_first_purchase=is_first_purchase
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return result
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for creating subscription plans.
|
|
3
|
+
|
|
4
|
+
Admin endpoint - requires authentication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_body=True,
|
|
15
|
+
convert_case=True
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Create a new subscription plan.
|
|
22
|
+
|
|
23
|
+
Expected body:
|
|
24
|
+
{
|
|
25
|
+
"planCode": "pro",
|
|
26
|
+
"planName": "Pro Plan",
|
|
27
|
+
"priceMonthlyC ents": 2999,
|
|
28
|
+
"description": "For growing teams",
|
|
29
|
+
"features": {"api_access": true, "sso": false},
|
|
30
|
+
"limits": {"max_projects": 100, "max_storage_gb": 500}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Returns 201 with created plan
|
|
34
|
+
"""
|
|
35
|
+
return handler_wrapper.execute(event, context, create_plan, injected_service)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_plan(
|
|
39
|
+
event: Dict[str, Any],
|
|
40
|
+
service: SubscriptionManagerService,
|
|
41
|
+
user_context: Dict[str, str]
|
|
42
|
+
) -> Any:
|
|
43
|
+
"""
|
|
44
|
+
Business logic for creating a plan.
|
|
45
|
+
"""
|
|
46
|
+
payload = event["parsed_body"]
|
|
47
|
+
|
|
48
|
+
# Extract required fields
|
|
49
|
+
plan_code = payload.get("plan_code")
|
|
50
|
+
if not plan_code:
|
|
51
|
+
raise ValueError("plan_code is required")
|
|
52
|
+
|
|
53
|
+
plan_name = payload.get("plan_name")
|
|
54
|
+
if not plan_name:
|
|
55
|
+
raise ValueError("plan_name is required")
|
|
56
|
+
|
|
57
|
+
price_monthly_cents = payload.get("price_monthly_cents", 0)
|
|
58
|
+
|
|
59
|
+
# Build kwargs for optional fields
|
|
60
|
+
kwargs = {}
|
|
61
|
+
optional_fields = [
|
|
62
|
+
"description", "tagline", "status", "is_public", "is_featured", "sort_order",
|
|
63
|
+
"price_annual_cents", "price_monthly_currency", "price_annual_currency",
|
|
64
|
+
"annual_discount_percentage", "trial_days", "trial_requires_payment_method",
|
|
65
|
+
"min_seats", "max_seats", "price_per_additional_seat_cents",
|
|
66
|
+
"features", "limits", "included_addon_ids", "compatible_addon_ids",
|
|
67
|
+
"feature_list", "cta_text", "recommended",
|
|
68
|
+
"allow_downgrades", "allow_upgrades"
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
for field in optional_fields:
|
|
72
|
+
if field in payload:
|
|
73
|
+
kwargs[field] = payload[field]
|
|
74
|
+
|
|
75
|
+
result = service.create_plan(
|
|
76
|
+
plan_code=plan_code,
|
|
77
|
+
plan_name=plan_name,
|
|
78
|
+
price_monthly_cents=price_monthly_cents,
|
|
79
|
+
**kwargs
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return result
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for getting a subscription plan.
|
|
3
|
+
|
|
4
|
+
Public endpoint - no authentication required.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_auth=False,
|
|
15
|
+
require_body=False,
|
|
16
|
+
convert_case=True
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
21
|
+
"""
|
|
22
|
+
Get a subscription plan by ID.
|
|
23
|
+
|
|
24
|
+
Path parameters:
|
|
25
|
+
- planId: Plan ID
|
|
26
|
+
|
|
27
|
+
Returns 200 with plan details
|
|
28
|
+
"""
|
|
29
|
+
return handler_wrapper.execute(event, context, get_plan, injected_service)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_plan(
|
|
33
|
+
event: Dict[str, Any],
|
|
34
|
+
service: SubscriptionManagerService,
|
|
35
|
+
user_context: Dict[str, str]
|
|
36
|
+
) -> Any:
|
|
37
|
+
"""
|
|
38
|
+
Business logic for getting a plan.
|
|
39
|
+
"""
|
|
40
|
+
path_params = event.get("pathParameters") or {}
|
|
41
|
+
plan_id = path_params.get("plan_id")
|
|
42
|
+
|
|
43
|
+
if not plan_id:
|
|
44
|
+
raise ValueError("plan_id is required in path")
|
|
45
|
+
|
|
46
|
+
result = service.get_plan(plan_id=plan_id)
|
|
47
|
+
|
|
48
|
+
return result
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for listing subscription plans.
|
|
3
|
+
|
|
4
|
+
Public endpoint - no authentication required for public plans.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Public endpoint - no auth required
|
|
13
|
+
handler_wrapper = create_handler(
|
|
14
|
+
service_class=SubscriptionManagerService,
|
|
15
|
+
require_auth=False,
|
|
16
|
+
require_body=False,
|
|
17
|
+
convert_case=True
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
22
|
+
"""
|
|
23
|
+
List public subscription plans.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
event: Lambda event from API Gateway
|
|
27
|
+
context: Lambda context
|
|
28
|
+
injected_service: Optional SubscriptionManagerService for testing
|
|
29
|
+
|
|
30
|
+
Query parameters:
|
|
31
|
+
- status: Filter by status (default: "active")
|
|
32
|
+
- isPublic: Filter by public visibility (default: true)
|
|
33
|
+
- limit: Max results (default: 50)
|
|
34
|
+
|
|
35
|
+
Returns 200 with list of plans
|
|
36
|
+
"""
|
|
37
|
+
return handler_wrapper.execute(event, context, list_plans, injected_service)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def list_plans(
|
|
41
|
+
event: Dict[str, Any],
|
|
42
|
+
service: SubscriptionManagerService,
|
|
43
|
+
user_context: Dict[str, str]
|
|
44
|
+
) -> Any:
|
|
45
|
+
"""
|
|
46
|
+
Business logic for listing plans.
|
|
47
|
+
"""
|
|
48
|
+
params = event.get("queryStringParameters") or {}
|
|
49
|
+
|
|
50
|
+
status = params.get("status", "active")
|
|
51
|
+
is_public = params.get("is_public")
|
|
52
|
+
limit = int(params.get("limit", "50"))
|
|
53
|
+
|
|
54
|
+
# Convert string to bool if provided
|
|
55
|
+
if is_public is not None:
|
|
56
|
+
is_public = is_public.lower() in ["true", "1", "yes"]
|
|
57
|
+
else:
|
|
58
|
+
is_public = True # Default to public plans only
|
|
59
|
+
|
|
60
|
+
result = service.list_plans(
|
|
61
|
+
status=status,
|
|
62
|
+
is_public=is_public,
|
|
63
|
+
limit=limit
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return result
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for updating subscription plans.
|
|
3
|
+
|
|
4
|
+
Admin endpoint - requires authentication.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_body=True,
|
|
15
|
+
convert_case=True
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Update a subscription plan.
|
|
22
|
+
|
|
23
|
+
Path parameters:
|
|
24
|
+
- planId: Plan ID
|
|
25
|
+
|
|
26
|
+
Body contains fields to update
|
|
27
|
+
|
|
28
|
+
Returns 200 with updated plan
|
|
29
|
+
"""
|
|
30
|
+
return handler_wrapper.execute(event, context, update_plan, injected_service)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def update_plan(
|
|
34
|
+
event: Dict[str, Any],
|
|
35
|
+
service: SubscriptionManagerService,
|
|
36
|
+
user_context: Dict[str, str]
|
|
37
|
+
) -> Any:
|
|
38
|
+
"""
|
|
39
|
+
Business logic for updating a plan.
|
|
40
|
+
"""
|
|
41
|
+
path_params = event.get("pathParameters") or {}
|
|
42
|
+
plan_id = path_params.get("plan_id")
|
|
43
|
+
|
|
44
|
+
if not plan_id:
|
|
45
|
+
raise ValueError("plan_id is required in path")
|
|
46
|
+
|
|
47
|
+
payload = event["parsed_body"]
|
|
48
|
+
|
|
49
|
+
result = service.update_plan(
|
|
50
|
+
plan_id=plan_id,
|
|
51
|
+
updates=payload
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return result
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for aggregating usage.
|
|
3
|
+
|
|
4
|
+
Requires authentication (secure mode).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_body=False,
|
|
15
|
+
convert_case=True
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Get aggregated usage for a billing period.
|
|
22
|
+
|
|
23
|
+
Query parameters:
|
|
24
|
+
- subscriptionId: Subscription ID (required)
|
|
25
|
+
- addonCode: Addon code (required)
|
|
26
|
+
- periodStart: Period start timestamp (required)
|
|
27
|
+
- periodEnd: Period end timestamp (required)
|
|
28
|
+
|
|
29
|
+
Returns 200 with aggregated usage total
|
|
30
|
+
"""
|
|
31
|
+
return handler_wrapper.execute(event, context, aggregate_usage, injected_service)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def aggregate_usage(
|
|
35
|
+
event: Dict[str, Any],
|
|
36
|
+
service: SubscriptionManagerService,
|
|
37
|
+
user_context: Dict[str, str]
|
|
38
|
+
) -> Any:
|
|
39
|
+
"""
|
|
40
|
+
Business logic for aggregating usage.
|
|
41
|
+
"""
|
|
42
|
+
params = event.get("queryStringParameters") or {}
|
|
43
|
+
|
|
44
|
+
tenant_id = user_context.get("tenant_id")
|
|
45
|
+
|
|
46
|
+
subscription_id = params.get("subscription_id")
|
|
47
|
+
if not subscription_id:
|
|
48
|
+
raise ValueError("subscription_id is required")
|
|
49
|
+
|
|
50
|
+
addon_code = params.get("addon_code")
|
|
51
|
+
if not addon_code:
|
|
52
|
+
raise ValueError("addon_code is required")
|
|
53
|
+
|
|
54
|
+
period_start = params.get("period_start")
|
|
55
|
+
if not period_start:
|
|
56
|
+
raise ValueError("period_start is required")
|
|
57
|
+
period_start = float(period_start)
|
|
58
|
+
|
|
59
|
+
period_end = params.get("period_end")
|
|
60
|
+
if not period_end:
|
|
61
|
+
raise ValueError("period_end is required")
|
|
62
|
+
period_end = float(period_end)
|
|
63
|
+
|
|
64
|
+
result = service.aggregate_usage(
|
|
65
|
+
tenant_id=tenant_id,
|
|
66
|
+
subscription_id=subscription_id,
|
|
67
|
+
addon_code=addon_code,
|
|
68
|
+
period_start=period_start,
|
|
69
|
+
period_end=period_end
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return result
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for recording usage events.
|
|
3
|
+
|
|
4
|
+
Requires authentication (secure mode).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Dict, Any
|
|
8
|
+
from geek_cafe_saas_sdk.lambda_handlers import create_handler
|
|
9
|
+
from geek_cafe_saas_sdk.domains.subscriptions.services import SubscriptionManagerService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
handler_wrapper = create_handler(
|
|
13
|
+
service_class=SubscriptionManagerService,
|
|
14
|
+
require_body=True,
|
|
15
|
+
convert_case=True
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
|
|
20
|
+
"""
|
|
21
|
+
Record a usage event for metered billing.
|
|
22
|
+
|
|
23
|
+
Expected body:
|
|
24
|
+
{
|
|
25
|
+
"subscriptionId": "sub-123",
|
|
26
|
+
"addonCode": "extra_storage",
|
|
27
|
+
"meterEventName": "storage_gb",
|
|
28
|
+
"quantity": 50.5,
|
|
29
|
+
"action": "increment", // optional: increment, decrement, set
|
|
30
|
+
"idempotencyKey": "unique-key", // optional
|
|
31
|
+
"metadata": {} // optional
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Returns 201 with usage record
|
|
35
|
+
"""
|
|
36
|
+
return handler_wrapper.execute(event, context, record_usage, injected_service)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def record_usage(
|
|
40
|
+
event: Dict[str, Any],
|
|
41
|
+
service: SubscriptionManagerService,
|
|
42
|
+
user_context: Dict[str, str]
|
|
43
|
+
) -> Any:
|
|
44
|
+
"""
|
|
45
|
+
Business logic for recording usage.
|
|
46
|
+
"""
|
|
47
|
+
payload = event["parsed_body"]
|
|
48
|
+
|
|
49
|
+
tenant_id = user_context.get("tenant_id")
|
|
50
|
+
|
|
51
|
+
# Extract required fields
|
|
52
|
+
subscription_id = payload.get("subscription_id")
|
|
53
|
+
if not subscription_id:
|
|
54
|
+
raise ValueError("subscription_id is required")
|
|
55
|
+
|
|
56
|
+
addon_code = payload.get("addon_code")
|
|
57
|
+
if not addon_code:
|
|
58
|
+
raise ValueError("addon_code is required")
|
|
59
|
+
|
|
60
|
+
meter_event_name = payload.get("meter_event_name")
|
|
61
|
+
if not meter_event_name:
|
|
62
|
+
raise ValueError("meter_event_name is required")
|
|
63
|
+
|
|
64
|
+
quantity = payload.get("quantity")
|
|
65
|
+
if quantity is None:
|
|
66
|
+
raise ValueError("quantity is required")
|
|
67
|
+
|
|
68
|
+
# Build kwargs for optional fields
|
|
69
|
+
kwargs = {}
|
|
70
|
+
optional_fields = [
|
|
71
|
+
"action", "unit_name", "idempotency_key",
|
|
72
|
+
"billing_period_start_utc_ts", "billing_period_end_utc_ts",
|
|
73
|
+
"metadata", "source", "description"
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
for field in optional_fields:
|
|
77
|
+
if field in payload:
|
|
78
|
+
kwargs[field] = payload[field]
|
|
79
|
+
|
|
80
|
+
result = service.record_usage(
|
|
81
|
+
tenant_id=tenant_id,
|
|
82
|
+
subscription_id=subscription_id,
|
|
83
|
+
addon_code=addon_code,
|
|
84
|
+
meter_event_name=meter_event_name,
|
|
85
|
+
quantity=quantity,
|
|
86
|
+
**kwargs
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return result
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Subscriptions Domain Models.
|
|
3
|
+
|
|
4
|
+
Geek Cafe, LLC
|
|
5
|
+
MIT License. See Project Root for the license information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .plan import Plan
|
|
9
|
+
from .addon import Addon
|
|
10
|
+
from .usage_record import UsageRecord
|
|
11
|
+
from .discount import Discount
|
|
12
|
+
|
|
13
|
+
__all__ = ["Plan", "Addon", "UsageRecord", "Discount"]
|