geek-cafe-saas-sdk 0.6.0__py3-none-any.whl → 0.7.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 geek-cafe-saas-sdk might be problematic. Click here for more details.
- geek_cafe_saas_sdk/__init__.py +2 -2
- geek_cafe_saas_sdk/domains/files/handlers/README.md +446 -0
- geek_cafe_saas_sdk/domains/files/handlers/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/create/app.py +121 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/download/app.py +80 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/get/app.py +62 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/list/app.py +72 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/create_derived/app.py +99 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/create_main/app.py +104 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/download_bundle/app.py +99 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/get_lineage/app.py +68 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/prepare_bundle/app.py +76 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +17 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +42 -6
- geek_cafe_saas_sdk/domains/files/models/file.py +158 -16
- 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/__init__.py +21 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +54 -135
- geek_cafe_saas_sdk/domains/files/services/file_lineage_service.py +487 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +37 -120
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +67 -103
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +44 -124
- 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/cognito_utility.py +16 -26
- 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.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/METADATA +11 -11
- {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/RECORD +94 -23
- {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/WHEEL +0 -0
- {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Subscriptions Domain Handlers
|
|
2
|
+
|
|
3
|
+
Lambda handlers for subscription management operations.
|
|
4
|
+
|
|
5
|
+
## Directory Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
handlers/
|
|
9
|
+
├── plans/
|
|
10
|
+
│ ├── list/app.py # GET /plans
|
|
11
|
+
│ ├── get/app.py # GET /plans/{planId}
|
|
12
|
+
│ ├── create/app.py # POST /plans (admin)
|
|
13
|
+
│ └── update/app.py # PATCH /plans/{planId} (admin)
|
|
14
|
+
├── addons/
|
|
15
|
+
│ ├── list/app.py # GET /addons
|
|
16
|
+
│ ├── get/app.py # GET /addons/{addonId}
|
|
17
|
+
│ ├── create/app.py # POST /addons (admin)
|
|
18
|
+
│ └── update/app.py # PATCH /addons/{addonId} (admin)
|
|
19
|
+
├── discounts/
|
|
20
|
+
│ ├── validate/app.py # POST /discounts/validate
|
|
21
|
+
│ ├── create/app.py # POST /discounts (admin)
|
|
22
|
+
│ └── get/app.py # GET /discounts/{discountId} (admin)
|
|
23
|
+
└── usage/
|
|
24
|
+
├── record/app.py # POST /usage
|
|
25
|
+
└── aggregate/app.py # GET /usage/aggregate
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Plan Handlers
|
|
31
|
+
|
|
32
|
+
### GET /plans
|
|
33
|
+
List public subscription plans.
|
|
34
|
+
|
|
35
|
+
**Authentication:** None (public endpoint)
|
|
36
|
+
|
|
37
|
+
**Query Parameters:**
|
|
38
|
+
- `status` (string, optional) - Filter by status (default: "active")
|
|
39
|
+
- `isPublic` (boolean, optional) - Filter by visibility (default: true)
|
|
40
|
+
- `limit` (integer, optional) - Max results (default: 50)
|
|
41
|
+
|
|
42
|
+
**Response:** 200 OK
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"success": true,
|
|
46
|
+
"data": [
|
|
47
|
+
{
|
|
48
|
+
"id": "plan-123",
|
|
49
|
+
"planCode": "pro",
|
|
50
|
+
"planName": "Pro Plan",
|
|
51
|
+
"priceMonthly Cents": 2999,
|
|
52
|
+
"priceAnnualCents": 29990,
|
|
53
|
+
"features": {...},
|
|
54
|
+
"limits": {...}
|
|
55
|
+
}
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### GET /plans/{planId}
|
|
61
|
+
Get a specific plan.
|
|
62
|
+
|
|
63
|
+
**Authentication:** None (public endpoint)
|
|
64
|
+
|
|
65
|
+
**Path Parameters:**
|
|
66
|
+
- `planId` (string, required) - Plan ID
|
|
67
|
+
|
|
68
|
+
**Response:** 200 OK
|
|
69
|
+
|
|
70
|
+
### POST /plans (Admin)
|
|
71
|
+
Create a new plan.
|
|
72
|
+
|
|
73
|
+
**Authentication:** Required (admin)
|
|
74
|
+
|
|
75
|
+
**Request Body:**
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"planCode": "enterprise",
|
|
79
|
+
"planName": "Enterprise Plan",
|
|
80
|
+
"priceMonthly Cents": 9999,
|
|
81
|
+
"description": "For large teams",
|
|
82
|
+
"features": {
|
|
83
|
+
"api_access": true,
|
|
84
|
+
"sso": true,
|
|
85
|
+
"white_label": true
|
|
86
|
+
},
|
|
87
|
+
"limits": {
|
|
88
|
+
"max_projects": -1,
|
|
89
|
+
"max_storage_gb": 1000
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Response:** 201 Created
|
|
95
|
+
|
|
96
|
+
### PATCH /plans/{planId} (Admin)
|
|
97
|
+
Update a plan.
|
|
98
|
+
|
|
99
|
+
**Authentication:** Required (admin)
|
|
100
|
+
|
|
101
|
+
**Request Body:** Fields to update
|
|
102
|
+
|
|
103
|
+
**Response:** 200 OK
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Addon Handlers
|
|
108
|
+
|
|
109
|
+
### GET /addons
|
|
110
|
+
List public addons.
|
|
111
|
+
|
|
112
|
+
**Authentication:** None (public endpoint)
|
|
113
|
+
|
|
114
|
+
**Query Parameters:**
|
|
115
|
+
- `status` (string, optional) - Filter by status
|
|
116
|
+
- `category` (string, optional) - Filter by category
|
|
117
|
+
- `limit` (integer, optional) - Max results
|
|
118
|
+
|
|
119
|
+
**Response:** 200 OK
|
|
120
|
+
|
|
121
|
+
### GET /addons/{addonId}
|
|
122
|
+
Get a specific addon.
|
|
123
|
+
|
|
124
|
+
**Authentication:** None (public endpoint)
|
|
125
|
+
|
|
126
|
+
**Response:** 200 OK
|
|
127
|
+
|
|
128
|
+
### POST /addons (Admin)
|
|
129
|
+
Create a new addon.
|
|
130
|
+
|
|
131
|
+
**Authentication:** Required (admin)
|
|
132
|
+
|
|
133
|
+
**Request Body:**
|
|
134
|
+
```json
|
|
135
|
+
{
|
|
136
|
+
"addonCode": "chat",
|
|
137
|
+
"addonName": "Chat Module",
|
|
138
|
+
"pricingModel": "fixed",
|
|
139
|
+
"priceMonthly Cents": 1500,
|
|
140
|
+
"description": "Real-time chat",
|
|
141
|
+
"category": "communication",
|
|
142
|
+
"features": {
|
|
143
|
+
"realtime_messaging": true,
|
|
144
|
+
"file_sharing": true
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Response:** 201 Created
|
|
150
|
+
|
|
151
|
+
### PATCH /addons/{addonId} (Admin)
|
|
152
|
+
Update an addon.
|
|
153
|
+
|
|
154
|
+
**Authentication:** Required (admin)
|
|
155
|
+
|
|
156
|
+
**Response:** 200 OK
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Discount Handlers
|
|
161
|
+
|
|
162
|
+
### POST /discounts/validate
|
|
163
|
+
Validate a discount code.
|
|
164
|
+
|
|
165
|
+
**Authentication:** None (public endpoint)
|
|
166
|
+
|
|
167
|
+
**Request Body:**
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"discountCode": "SUMMER25",
|
|
171
|
+
"planCode": "pro",
|
|
172
|
+
"amountCents": 2999,
|
|
173
|
+
"isFirstPurchase": false
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Response:** 200 OK (if valid)
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"success": true,
|
|
181
|
+
"data": {
|
|
182
|
+
"id": "discount-123",
|
|
183
|
+
"discountCode": "SUMMER25",
|
|
184
|
+
"discountType": "percentage",
|
|
185
|
+
"percentOff": 25.0,
|
|
186
|
+
"duration": "repeating",
|
|
187
|
+
"durationInMonths": 3
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Error Response:** 400 Bad Request (if invalid)
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"success": false,
|
|
196
|
+
"message": "Discount code is not currently valid"
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### POST /discounts (Admin)
|
|
201
|
+
Create a new discount.
|
|
202
|
+
|
|
203
|
+
**Authentication:** Required (admin)
|
|
204
|
+
|
|
205
|
+
**Request Body:**
|
|
206
|
+
```json
|
|
207
|
+
{
|
|
208
|
+
"discountCode": "WELCOME50",
|
|
209
|
+
"discountName": "Welcome Discount",
|
|
210
|
+
"discountType": "percentage",
|
|
211
|
+
"percentOff": 50.0,
|
|
212
|
+
"duration": "once",
|
|
213
|
+
"maxRedemptions": 1000,
|
|
214
|
+
"firstTimeTransaction": true
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Response:** 201 Created
|
|
219
|
+
|
|
220
|
+
### GET /discounts/{discountId} (Admin)
|
|
221
|
+
Get a discount by ID.
|
|
222
|
+
|
|
223
|
+
**Authentication:** Required (admin)
|
|
224
|
+
|
|
225
|
+
**Response:** 200 OK
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Usage Handlers
|
|
230
|
+
|
|
231
|
+
### POST /usage
|
|
232
|
+
Record a usage event for metered billing.
|
|
233
|
+
|
|
234
|
+
**Authentication:** Required
|
|
235
|
+
|
|
236
|
+
**Request Body:**
|
|
237
|
+
```json
|
|
238
|
+
{
|
|
239
|
+
"subscriptionId": "sub-123",
|
|
240
|
+
"addonCode": "extra_storage",
|
|
241
|
+
"meterEventName": "storage_gb",
|
|
242
|
+
"quantity": 50.5,
|
|
243
|
+
"action": "increment",
|
|
244
|
+
"idempotencyKey": "unique-key-123",
|
|
245
|
+
"metadata": {
|
|
246
|
+
"source": "api"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Response:** 201 Created
|
|
252
|
+
|
|
253
|
+
### GET /usage/aggregate
|
|
254
|
+
Get aggregated usage for a period.
|
|
255
|
+
|
|
256
|
+
**Authentication:** Required
|
|
257
|
+
|
|
258
|
+
**Query Parameters:**
|
|
259
|
+
- `subscriptionId` (string, required)
|
|
260
|
+
- `addonCode` (string, required)
|
|
261
|
+
- `periodStart` (timestamp, required)
|
|
262
|
+
- `periodEnd` (timestamp, required)
|
|
263
|
+
|
|
264
|
+
**Response:** 200 OK
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"success": true,
|
|
268
|
+
"data": 150.75
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Authentication
|
|
275
|
+
|
|
276
|
+
**Public Endpoints (no auth):**
|
|
277
|
+
- GET /plans
|
|
278
|
+
- GET /plans/{planId}
|
|
279
|
+
- GET /addons
|
|
280
|
+
- GET /addons/{addonId}
|
|
281
|
+
- POST /discounts/validate
|
|
282
|
+
|
|
283
|
+
**User Endpoints (auth required):**
|
|
284
|
+
- POST /usage
|
|
285
|
+
- GET /usage/aggregate
|
|
286
|
+
|
|
287
|
+
**Admin Endpoints (admin auth required):**
|
|
288
|
+
- POST /plans
|
|
289
|
+
- PATCH /plans/{planId}
|
|
290
|
+
- POST /addons
|
|
291
|
+
- PATCH /addons/{addonId}
|
|
292
|
+
- POST /discounts
|
|
293
|
+
- GET /discounts/{discountId}
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Error Handling
|
|
298
|
+
|
|
299
|
+
All handlers return standardized error responses:
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"success": false,
|
|
304
|
+
"message": "Error description",
|
|
305
|
+
"errorCode": "ERROR_CODE"
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Common Error Codes:**
|
|
310
|
+
- `VALIDATION_ERROR` - Invalid input
|
|
311
|
+
- `NOT_FOUND` - Resource not found
|
|
312
|
+
- `INTERNAL_ERROR` - Server error
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Deployment
|
|
317
|
+
|
|
318
|
+
Each handler is designed to be deployed as a separate Lambda function with API Gateway integration.
|
|
319
|
+
|
|
320
|
+
### Example SAM Template
|
|
321
|
+
|
|
322
|
+
```yaml
|
|
323
|
+
PlanListFunction:
|
|
324
|
+
Type: AWS::Serverless::Function
|
|
325
|
+
Properties:
|
|
326
|
+
CodeUri: src/geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/list/
|
|
327
|
+
Handler: app.handler
|
|
328
|
+
Runtime: python3.11
|
|
329
|
+
Events:
|
|
330
|
+
ListPlans:
|
|
331
|
+
Type: Api
|
|
332
|
+
Properties:
|
|
333
|
+
Path: /plans
|
|
334
|
+
Method: get
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Testing
|
|
340
|
+
|
|
341
|
+
Handlers support dependency injection for testing:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
from handlers.plans.list.app import handler
|
|
345
|
+
|
|
346
|
+
# Mock service
|
|
347
|
+
class MockService:
|
|
348
|
+
def list_plans(self, **kwargs):
|
|
349
|
+
return ServiceResult.success_result([])
|
|
350
|
+
|
|
351
|
+
# Test
|
|
352
|
+
event = {"queryStringParameters": {"status": "active"}}
|
|
353
|
+
response = handler(event, None, injected_service=MockService())
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## Request/Response Format
|
|
359
|
+
|
|
360
|
+
### Request Format
|
|
361
|
+
|
|
362
|
+
Handlers automatically convert camelCase to snake_case:
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"planCode": "pro", // Converted to plan_code
|
|
367
|
+
"priceMonthly Cents": 2999 // Converted to price_monthly_cents
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Response Format
|
|
372
|
+
|
|
373
|
+
All responses follow the ServiceResult pattern:
|
|
374
|
+
|
|
375
|
+
**Success:**
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"success": true,
|
|
379
|
+
"data": {...}
|
|
380
|
+
}
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
**Error:**
|
|
384
|
+
```json
|
|
385
|
+
{
|
|
386
|
+
"success": false,
|
|
387
|
+
"message": "Error message",
|
|
388
|
+
"errorCode": "ERROR_CODE"
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
---
|
|
393
|
+
|
|
394
|
+
## Environment Variables
|
|
395
|
+
|
|
396
|
+
Required environment variables:
|
|
397
|
+
|
|
398
|
+
- `DYNAMODB_TABLE_NAME` - DynamoDB table name
|
|
399
|
+
- `AWS_REGION` - AWS region
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## Notes
|
|
404
|
+
|
|
405
|
+
- Plan and addon handlers are public to allow pricing page display
|
|
406
|
+
- Usage handlers require authentication to prevent abuse
|
|
407
|
+
- Discount validation is public but discount management is admin-only
|
|
408
|
+
- All admin operations should implement proper authorization checks
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Subscriptions Domain Handlers
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for creating addons.
|
|
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 addon.
|
|
22
|
+
|
|
23
|
+
Expected body:
|
|
24
|
+
{
|
|
25
|
+
"addonCode": "chat",
|
|
26
|
+
"addonName": "Chat Module",
|
|
27
|
+
"pricingModel": "fixed",
|
|
28
|
+
"priceMonthly Cents": 1500,
|
|
29
|
+
"description": "Real-time chat for your app",
|
|
30
|
+
"category": "communication"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
Returns 201 with created addon
|
|
34
|
+
"""
|
|
35
|
+
return handler_wrapper.execute(event, context, create_addon, injected_service)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def create_addon(
|
|
39
|
+
event: Dict[str, Any],
|
|
40
|
+
service: SubscriptionManagerService,
|
|
41
|
+
user_context: Dict[str, str]
|
|
42
|
+
) -> Any:
|
|
43
|
+
"""
|
|
44
|
+
Business logic for creating an addon.
|
|
45
|
+
"""
|
|
46
|
+
payload = event["parsed_body"]
|
|
47
|
+
|
|
48
|
+
# Extract required fields
|
|
49
|
+
addon_code = payload.get("addon_code")
|
|
50
|
+
if not addon_code:
|
|
51
|
+
raise ValueError("addon_code is required")
|
|
52
|
+
|
|
53
|
+
addon_name = payload.get("addon_name")
|
|
54
|
+
if not addon_name:
|
|
55
|
+
raise ValueError("addon_name is required")
|
|
56
|
+
|
|
57
|
+
pricing_model = payload.get("pricing_model", "fixed")
|
|
58
|
+
|
|
59
|
+
# Build kwargs for optional fields
|
|
60
|
+
kwargs = {}
|
|
61
|
+
optional_fields = [
|
|
62
|
+
"description", "category", "status", "is_public", "sort_order",
|
|
63
|
+
"price_monthly_cents", "price_annual_cents", "currency",
|
|
64
|
+
"price_per_unit_cents", "unit_name", "included_units", "min_units", "max_units",
|
|
65
|
+
"pricing_tiers", "trial_days", "features", "limits",
|
|
66
|
+
"compatible_plan_codes", "incompatible_addon_codes", "feature_list",
|
|
67
|
+
"icon", "color", "is_metered", "meter_event_name", "billing_scheme"
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
for field in optional_fields:
|
|
71
|
+
if field in payload:
|
|
72
|
+
kwargs[field] = payload[field]
|
|
73
|
+
|
|
74
|
+
result = service.create_addon(
|
|
75
|
+
addon_code=addon_code,
|
|
76
|
+
addon_name=addon_name,
|
|
77
|
+
pricing_model=pricing_model,
|
|
78
|
+
**kwargs
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return result
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for getting an addon.
|
|
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 an addon by ID.
|
|
23
|
+
|
|
24
|
+
Path parameters:
|
|
25
|
+
- addonId: Addon ID
|
|
26
|
+
|
|
27
|
+
Returns 200 with addon details
|
|
28
|
+
"""
|
|
29
|
+
return handler_wrapper.execute(event, context, get_addon, injected_service)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_addon(
|
|
33
|
+
event: Dict[str, Any],
|
|
34
|
+
service: SubscriptionManagerService,
|
|
35
|
+
user_context: Dict[str, str]
|
|
36
|
+
) -> Any:
|
|
37
|
+
"""
|
|
38
|
+
Business logic for getting an addon.
|
|
39
|
+
"""
|
|
40
|
+
path_params = event.get("pathParameters") or {}
|
|
41
|
+
addon_id = path_params.get("addon_id")
|
|
42
|
+
|
|
43
|
+
if not addon_id:
|
|
44
|
+
raise ValueError("addon_id is required in path")
|
|
45
|
+
|
|
46
|
+
result = service.get_addon(addon_id=addon_id)
|
|
47
|
+
|
|
48
|
+
return result
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for listing addons.
|
|
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
|
+
List public addons.
|
|
23
|
+
|
|
24
|
+
Query parameters:
|
|
25
|
+
- status: Filter by status (default: "active")
|
|
26
|
+
- category: Filter by category
|
|
27
|
+
- limit: Max results (default: 50)
|
|
28
|
+
|
|
29
|
+
Returns 200 with list of addons
|
|
30
|
+
"""
|
|
31
|
+
return handler_wrapper.execute(event, context, list_addons, injected_service)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def list_addons(
|
|
35
|
+
event: Dict[str, Any],
|
|
36
|
+
service: SubscriptionManagerService,
|
|
37
|
+
user_context: Dict[str, str]
|
|
38
|
+
) -> Any:
|
|
39
|
+
"""
|
|
40
|
+
Business logic for listing addons.
|
|
41
|
+
"""
|
|
42
|
+
params = event.get("queryStringParameters") or {}
|
|
43
|
+
|
|
44
|
+
status = params.get("status", "active")
|
|
45
|
+
category = params.get("category")
|
|
46
|
+
limit = int(params.get("limit", "50"))
|
|
47
|
+
|
|
48
|
+
result = service.list_addons(
|
|
49
|
+
status=status,
|
|
50
|
+
category=category,
|
|
51
|
+
limit=limit
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return result
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Lambda handler for updating addons.
|
|
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 an addon.
|
|
22
|
+
|
|
23
|
+
Path parameters:
|
|
24
|
+
- addonId: Addon ID
|
|
25
|
+
|
|
26
|
+
Body contains fields to update
|
|
27
|
+
|
|
28
|
+
Returns 200 with updated addon
|
|
29
|
+
"""
|
|
30
|
+
return handler_wrapper.execute(event, context, update_addon, injected_service)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def update_addon(
|
|
34
|
+
event: Dict[str, Any],
|
|
35
|
+
service: SubscriptionManagerService,
|
|
36
|
+
user_context: Dict[str, str]
|
|
37
|
+
) -> Any:
|
|
38
|
+
"""
|
|
39
|
+
Business logic for updating an addon.
|
|
40
|
+
"""
|
|
41
|
+
path_params = event.get("pathParameters") or {}
|
|
42
|
+
addon_id = path_params.get("addon_id")
|
|
43
|
+
|
|
44
|
+
if not addon_id:
|
|
45
|
+
raise ValueError("addon_id is required in path")
|
|
46
|
+
|
|
47
|
+
payload = event["parsed_body"]
|
|
48
|
+
|
|
49
|
+
result = service.update_addon(
|
|
50
|
+
addon_id=addon_id,
|
|
51
|
+
updates=payload
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
return result
|