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.

Files changed (79) hide show
  1. geek_cafe_saas_sdk/__init__.py +1 -1
  2. geek_cafe_saas_sdk/domains/files/models/directory.py +42 -6
  3. geek_cafe_saas_sdk/domains/files/models/file.py +40 -4
  4. geek_cafe_saas_sdk/domains/files/models/file_share.py +33 -0
  5. geek_cafe_saas_sdk/domains/files/models/file_version.py +24 -0
  6. geek_cafe_saas_sdk/domains/files/services/directory_service.py +54 -135
  7. geek_cafe_saas_sdk/domains/files/services/file_share_service.py +60 -136
  8. geek_cafe_saas_sdk/domains/files/services/file_system_service.py +43 -104
  9. geek_cafe_saas_sdk/domains/files/services/file_version_service.py +57 -131
  10. geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +55 -7
  11. geek_cafe_saas_sdk/domains/notifications/__init__.py +18 -0
  12. geek_cafe_saas_sdk/domains/notifications/handlers/__init__.py +1 -0
  13. geek_cafe_saas_sdk/domains/notifications/handlers/create_webhook/app.py +73 -0
  14. geek_cafe_saas_sdk/domains/notifications/handlers/get/app.py +40 -0
  15. geek_cafe_saas_sdk/domains/notifications/handlers/get_preferences/app.py +34 -0
  16. geek_cafe_saas_sdk/domains/notifications/handlers/list/app.py +43 -0
  17. geek_cafe_saas_sdk/domains/notifications/handlers/list_webhooks/app.py +40 -0
  18. geek_cafe_saas_sdk/domains/notifications/handlers/mark_read/app.py +40 -0
  19. geek_cafe_saas_sdk/domains/notifications/handlers/send/app.py +83 -0
  20. geek_cafe_saas_sdk/domains/notifications/handlers/update_preferences/app.py +45 -0
  21. geek_cafe_saas_sdk/domains/notifications/models/__init__.py +16 -0
  22. geek_cafe_saas_sdk/domains/notifications/models/notification.py +717 -0
  23. geek_cafe_saas_sdk/domains/notifications/models/notification_preference.py +365 -0
  24. geek_cafe_saas_sdk/domains/notifications/models/webhook_subscription.py +339 -0
  25. geek_cafe_saas_sdk/domains/notifications/services/__init__.py +10 -0
  26. geek_cafe_saas_sdk/domains/notifications/services/notification_service.py +576 -0
  27. geek_cafe_saas_sdk/domains/payments/__init__.py +16 -0
  28. geek_cafe_saas_sdk/domains/payments/handlers/README.md +334 -0
  29. geek_cafe_saas_sdk/domains/payments/handlers/__init__.py +6 -0
  30. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/create/app.py +105 -0
  31. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/get/app.py +60 -0
  32. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/update/app.py +97 -0
  33. geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/create/app.py +97 -0
  34. geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/get/app.py +60 -0
  35. geek_cafe_saas_sdk/domains/payments/handlers/payments/get/app.py +60 -0
  36. geek_cafe_saas_sdk/domains/payments/handlers/payments/list/app.py +68 -0
  37. geek_cafe_saas_sdk/domains/payments/handlers/payments/record/app.py +118 -0
  38. geek_cafe_saas_sdk/domains/payments/handlers/refunds/create/app.py +89 -0
  39. geek_cafe_saas_sdk/domains/payments/handlers/refunds/get/app.py +60 -0
  40. geek_cafe_saas_sdk/domains/payments/models/__init__.py +17 -0
  41. geek_cafe_saas_sdk/domains/payments/models/billing_account.py +521 -0
  42. geek_cafe_saas_sdk/domains/payments/models/payment.py +639 -0
  43. geek_cafe_saas_sdk/domains/payments/models/payment_intent_ref.py +539 -0
  44. geek_cafe_saas_sdk/domains/payments/models/refund.py +404 -0
  45. geek_cafe_saas_sdk/domains/payments/services/__init__.py +11 -0
  46. geek_cafe_saas_sdk/domains/payments/services/payment_service.py +405 -0
  47. geek_cafe_saas_sdk/domains/subscriptions/__init__.py +19 -0
  48. geek_cafe_saas_sdk/domains/subscriptions/handlers/README.md +408 -0
  49. geek_cafe_saas_sdk/domains/subscriptions/handlers/__init__.py +1 -0
  50. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/create/app.py +81 -0
  51. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/get/app.py +48 -0
  52. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/list/app.py +54 -0
  53. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/update/app.py +54 -0
  54. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/create/app.py +83 -0
  55. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/get/app.py +47 -0
  56. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/validate/app.py +62 -0
  57. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/create/app.py +82 -0
  58. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/get/app.py +48 -0
  59. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/list/app.py +66 -0
  60. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/update/app.py +54 -0
  61. geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/aggregate/app.py +72 -0
  62. geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/record/app.py +89 -0
  63. geek_cafe_saas_sdk/domains/subscriptions/models/__init__.py +13 -0
  64. geek_cafe_saas_sdk/domains/subscriptions/models/addon.py +604 -0
  65. geek_cafe_saas_sdk/domains/subscriptions/models/discount.py +492 -0
  66. geek_cafe_saas_sdk/domains/subscriptions/models/plan.py +569 -0
  67. geek_cafe_saas_sdk/domains/subscriptions/models/usage_record.py +300 -0
  68. geek_cafe_saas_sdk/domains/subscriptions/services/__init__.py +10 -0
  69. geek_cafe_saas_sdk/domains/subscriptions/services/subscription_manager_service.py +694 -0
  70. geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +123 -1
  71. geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +213 -0
  72. geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +7 -0
  73. geek_cafe_saas_sdk/services/database_service.py +10 -6
  74. geek_cafe_saas_sdk/utilities/environment_variables.py +16 -0
  75. geek_cafe_saas_sdk/utilities/logging_utility.py +77 -0
  76. {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/METADATA +1 -1
  77. {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/RECORD +79 -20
  78. {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/WHEEL +0 -0
  79. {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,334 @@
1
+ # Payment Handlers
2
+
3
+ Lambda handlers for payment operations.
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ handlers/
9
+ ├── billing_accounts/
10
+ │ ├── create/app.py - POST /billing-accounts
11
+ │ ├── get/app.py - GET /billing-accounts/{accountId}
12
+ │ └── update/app.py - PATCH /billing-accounts/{accountId}
13
+ ├── payment_intents/
14
+ │ ├── create/app.py - POST /payment-intents
15
+ │ └── get/app.py - GET /payment-intents/{intentId}
16
+ ├── payments/
17
+ │ ├── record/app.py - POST /payments (webhook handler)
18
+ │ ├── get/app.py - GET /payments/{paymentId}
19
+ │ └── list/app.py - GET /payments
20
+ └── refunds/
21
+ ├── create/app.py - POST /refunds
22
+ └── get/app.py - GET /refunds/{refundId}
23
+ ```
24
+
25
+ ## Billing Account Handlers
26
+
27
+ ### POST /billing-accounts
28
+ **Handler:** `billing_accounts/create/app.py`
29
+ **Purpose:** Create a new billing account
30
+
31
+ **Request Body:**
32
+ ```json
33
+ {
34
+ "accountHolderId": "user-123",
35
+ "accountHolderType": "user",
36
+ "currencyCode": "USD",
37
+ "billingEmail": "user@example.com",
38
+ "billingName": "John Doe",
39
+ "addressLine1": "123 Main St",
40
+ "addressCity": "San Francisco",
41
+ "addressState": "CA",
42
+ "addressPostalCode": "94105",
43
+ "addressCountry": "US",
44
+ "stripeCustomerId": "cus_xxx"
45
+ }
46
+ ```
47
+
48
+ **Response:** 201 Created
49
+ ```json
50
+ {
51
+ "success": true,
52
+ "data": {
53
+ "accountId": "account-123",
54
+ "accountHolderId": "user-123",
55
+ "currencyCode": "USD",
56
+ "status": "active",
57
+ ...
58
+ }
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ### GET /billing-accounts/{accountId}
65
+ **Handler:** `billing_accounts/get/app.py`
66
+ **Purpose:** Retrieve billing account details
67
+
68
+ **Path Parameters:**
69
+ - `accountId` - Billing account ID
70
+
71
+ **Response:** 200 OK
72
+ ```json
73
+ {
74
+ "success": true,
75
+ "data": {
76
+ "accountId": "account-123",
77
+ "accountHolderId": "user-123",
78
+ "status": "active",
79
+ ...
80
+ }
81
+ }
82
+ ```
83
+
84
+ ---
85
+
86
+ ### PATCH /billing-accounts/{accountId}
87
+ **Handler:** `billing_accounts/update/app.py`
88
+ **Purpose:** Update billing account
89
+
90
+ **Path Parameters:**
91
+ - `accountId` - Billing account ID
92
+
93
+ **Request Body:**
94
+ ```json
95
+ {
96
+ "billingEmail": "newemail@example.com",
97
+ "defaultPaymentMethodId": "pm_xxx",
98
+ "autoChargeEnabled": true
99
+ }
100
+ ```
101
+
102
+ **Response:** 200 OK
103
+
104
+ ---
105
+
106
+ ## Payment Intent Handlers
107
+
108
+ ### POST /payment-intents
109
+ **Handler:** `payment_intents/create/app.py`
110
+ **Purpose:** Create a payment intent for frontend payment flow
111
+
112
+ **Request Body:**
113
+ ```json
114
+ {
115
+ "billingAccountId": "account-123",
116
+ "amountCents": 5000,
117
+ "currencyCode": "USD",
118
+ "description": "Subscription payment",
119
+ "receiptEmail": "user@example.com"
120
+ }
121
+ ```
122
+
123
+ **Response:** 201 Created
124
+ ```json
125
+ {
126
+ "success": true,
127
+ "data": {
128
+ "intentRefId": "intent-123",
129
+ "pspClientSecret": "pi_xxx_secret_yyy",
130
+ "status": "created",
131
+ "amountCents": 5000,
132
+ ...
133
+ }
134
+ }
135
+ ```
136
+
137
+ ---
138
+
139
+ ### GET /payment-intents/{intentId}
140
+ **Handler:** `payment_intents/get/app.py`
141
+ **Purpose:** Retrieve payment intent status
142
+
143
+ **Path Parameters:**
144
+ - `intentId` - Payment intent reference ID
145
+
146
+ **Response:** 200 OK
147
+
148
+ ---
149
+
150
+ ## Payment Handlers
151
+
152
+ ### POST /payments
153
+ **Handler:** `payments/record/app.py`
154
+ **Purpose:** Record a settled payment (typically called from webhooks)
155
+
156
+ **Request Body:**
157
+ ```json
158
+ {
159
+ "billingAccountId": "account-123",
160
+ "paymentIntentRefId": "intent-123",
161
+ "grossAmountCents": 5000,
162
+ "feeAmountCents": 145,
163
+ "currencyCode": "USD",
164
+ "pspType": "stripe",
165
+ "pspTransactionId": "txn_xxx",
166
+ "pspChargeId": "ch_xxx",
167
+ "paymentMethodLast4": "4242",
168
+ "paymentMethodBrand": "visa"
169
+ }
170
+ ```
171
+
172
+ **Response:** 201 Created
173
+ ```json
174
+ {
175
+ "success": true,
176
+ "data": {
177
+ "paymentId": "payment-123",
178
+ "grossAmountCents": 5000,
179
+ "feeAmountCents": 145,
180
+ "netAmountCents": 4855,
181
+ "status": "succeeded",
182
+ ...
183
+ }
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ### GET /payments/{paymentId}
190
+ **Handler:** `payments/get/app.py`
191
+ **Purpose:** Retrieve payment details
192
+
193
+ **Path Parameters:**
194
+ - `paymentId` - Payment ID
195
+
196
+ **Response:** 200 OK
197
+
198
+ ---
199
+
200
+ ### GET /payments
201
+ **Handler:** `payments/list/app.py`
202
+ **Purpose:** List payments for tenant or billing account
203
+
204
+ **Query Parameters:**
205
+ - `billingAccountId` (optional) - Filter by billing account
206
+ - `limit` (optional) - Page size (default: 50, max: 100)
207
+
208
+ **Response:** 200 OK
209
+ ```json
210
+ {
211
+ "success": true,
212
+ "data": [
213
+ {
214
+ "paymentId": "payment-123",
215
+ "grossAmountCents": 5000,
216
+ "settledUtcTs": 1697500000.0,
217
+ ...
218
+ }
219
+ ]
220
+ }
221
+ ```
222
+
223
+ ---
224
+
225
+ ## Refund Handlers
226
+
227
+ ### POST /refunds
228
+ **Handler:** `refunds/create/app.py`
229
+ **Purpose:** Create a refund for a payment
230
+
231
+ **Request Body:**
232
+ ```json
233
+ {
234
+ "paymentId": "payment-123",
235
+ "amountCents": 5000,
236
+ "reason": "requested_by_customer",
237
+ "description": "Customer requested refund"
238
+ }
239
+ ```
240
+
241
+ **Response:** 201 Created
242
+ ```json
243
+ {
244
+ "success": true,
245
+ "data": {
246
+ "refundId": "refund-123",
247
+ "paymentId": "payment-123",
248
+ "amountCents": 5000,
249
+ "status": "pending",
250
+ ...
251
+ }
252
+ }
253
+ ```
254
+
255
+ ---
256
+
257
+ ### GET /refunds/{refundId}
258
+ **Handler:** `refunds/get/app.py`
259
+ **Purpose:** Retrieve refund status
260
+
261
+ **Path Parameters:**
262
+ - `refundId` - Refund ID
263
+
264
+ **Response:** 200 OK
265
+
266
+ ---
267
+
268
+ ## Authentication
269
+
270
+ All handlers require authentication. The `create_handler` factory automatically:
271
+ - Validates JWT tokens
272
+ - Extracts tenant_id and user_id from the token
273
+ - Returns 401 Unauthorized for invalid/missing tokens
274
+
275
+ ## Error Handling
276
+
277
+ Handlers use the ServiceResult pattern for consistent error responses:
278
+
279
+ **Success Response:**
280
+ ```json
281
+ {
282
+ "success": true,
283
+ "data": { ... }
284
+ }
285
+ ```
286
+
287
+ **Error Response:**
288
+ ```json
289
+ {
290
+ "success": false,
291
+ "message": "Error description",
292
+ "errorCode": "ERROR_CODE",
293
+ "statusCode": 400
294
+ }
295
+ ```
296
+
297
+ ## Common HTTP Status Codes
298
+
299
+ - **200 OK** - Successful GET/LIST operation
300
+ - **201 Created** - Successful POST/CREATE operation
301
+ - **400 Bad Request** - Validation error
302
+ - **401 Unauthorized** - Authentication failed
303
+ - **404 Not Found** - Resource not found
304
+ - **500 Internal Server Error** - Unexpected error
305
+
306
+ ## Deployment
307
+
308
+ Each handler is packaged as a separate Lambda function:
309
+
310
+ ```yaml
311
+ # SAM/CloudFormation template
312
+ CreateBillingAccountFunction:
313
+ Type: AWS::Serverless::Function
314
+ Properties:
315
+ Handler: app.handler
316
+ Runtime: python3.11
317
+ CodeUri: domains/payments/handlers/billing_accounts/create/
318
+ Events:
319
+ Api:
320
+ Type: Api
321
+ Properties:
322
+ Path: /billing-accounts
323
+ Method: post
324
+ ```
325
+
326
+ ## Testing
327
+
328
+ See `tests/test_payment_handlers.py` for handler integration tests.
329
+
330
+ ## Related Documentation
331
+
332
+ - **Service Layer:** `../services/payment_service.py`
333
+ - **Models:** `../models/`
334
+ - **User Guide:** `/docs/help/payments/`
@@ -0,0 +1,6 @@
1
+ """
2
+ Payment handlers.
3
+
4
+ Geek Cafe, LLC
5
+ MIT License. See Project Root for the license information.
6
+ """
@@ -0,0 +1,105 @@
1
+ """
2
+ Lambda handler for creating billing accounts.
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.payments.services import PaymentService
10
+
11
+
12
+ # Factory creates handler (defaults to secure auth)
13
+ handler_wrapper = create_handler(
14
+ service_class=PaymentService,
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
+ Create a new billing account.
23
+
24
+ Args:
25
+ event: Lambda event from API Gateway
26
+ context: Lambda context
27
+ injected_service: Optional PaymentService for testing
28
+
29
+ Expected body (camelCase from frontend):
30
+ {
31
+ "accountHolderId": "user-123",
32
+ "accountHolderType": "user", // "user", "organization", "property"
33
+ "currencyCode": "USD",
34
+ "billingEmail": "user@example.com",
35
+ "billingName": "John Doe",
36
+ "billingPhone": "+1234567890",
37
+ "addressLine1": "123 Main St",
38
+ "addressLine2": "Apt 4B",
39
+ "addressCity": "San Francisco",
40
+ "addressState": "CA",
41
+ "addressPostalCode": "94105",
42
+ "addressCountry": "US",
43
+ "stripeCustomerId": "cus_xxx", // Optional
44
+ "taxId": "12-3456789", // Optional
45
+ "taxIdType": "us_ein", // Optional
46
+ "taxExempt": false // Optional
47
+ }
48
+
49
+ Returns 201 with created billing account
50
+ """
51
+ return handler_wrapper.execute(event, context, create_billing_account, injected_service)
52
+
53
+
54
+ def create_billing_account(
55
+ event: Dict[str, Any],
56
+ service: PaymentService,
57
+ user_context: Dict[str, str]
58
+ ) -> Any:
59
+ """
60
+ Business logic for creating billing accounts.
61
+ """
62
+ payload = event["parsed_body"]
63
+
64
+ tenant_id = user_context.get("tenant_id")
65
+
66
+ # Extract required fields
67
+ account_holder_id = payload.get("account_holder_id")
68
+ if not account_holder_id:
69
+ raise ValueError("account_holder_id is required")
70
+
71
+ # Extract optional fields with defaults
72
+ account_holder_type = payload.get("account_holder_type", "user")
73
+ currency_code = payload.get("currency_code", "USD")
74
+ billing_email = payload.get("billing_email")
75
+
76
+ # Build kwargs for optional fields
77
+ kwargs = {}
78
+ optional_fields = [
79
+ "billing_name", "billing_phone",
80
+ "address_line1", "address_line2", "address_city",
81
+ "address_state", "address_postal_code", "address_country",
82
+ "stripe_customer_id", "stripe_account_id",
83
+ "country_code", "locale",
84
+ "tax_id", "tax_id_type", "tax_exempt", "tax_metadata",
85
+ "default_payment_method_id", "allowed_payment_methods",
86
+ "auto_charge_enabled", "require_cvv", "send_receipts",
87
+ "balance_cents", "credit_limit_cents",
88
+ "notes", "external_reference"
89
+ ]
90
+
91
+ for field in optional_fields:
92
+ if field in payload:
93
+ kwargs[field] = payload[field]
94
+
95
+ # Create billing account
96
+ result = service.create_billing_account(
97
+ tenant_id=tenant_id,
98
+ account_holder_id=account_holder_id,
99
+ account_holder_type=account_holder_type,
100
+ currency_code=currency_code,
101
+ billing_email=billing_email,
102
+ **kwargs
103
+ )
104
+
105
+ return result
@@ -0,0 +1,60 @@
1
+ """
2
+ Lambda handler for getting billing account by ID.
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.payments.services import PaymentService
10
+
11
+
12
+ # Factory creates handler (defaults to secure auth)
13
+ handler_wrapper = create_handler(
14
+ service_class=PaymentService,
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 billing account by ID.
23
+
24
+ Args:
25
+ event: Lambda event from API Gateway
26
+ context: Lambda context
27
+ injected_service: Optional PaymentService for testing
28
+
29
+ Path parameters:
30
+ - accountId: Billing account ID
31
+
32
+ Returns 200 with billing account data
33
+ """
34
+ return handler_wrapper.execute(event, context, get_billing_account, injected_service)
35
+
36
+
37
+ def get_billing_account(
38
+ event: Dict[str, Any],
39
+ service: PaymentService,
40
+ user_context: Dict[str, str]
41
+ ) -> Any:
42
+ """
43
+ Business logic for getting billing account.
44
+ """
45
+ tenant_id = user_context.get("tenant_id")
46
+
47
+ # Extract account ID from path parameters
48
+ path_params = event.get("pathParameters", {})
49
+ account_id = path_params.get("accountId")
50
+
51
+ if not account_id:
52
+ raise ValueError("accountId path parameter is required")
53
+
54
+ # Get billing account
55
+ result = service.get_billing_account(
56
+ account_id=account_id,
57
+ tenant_id=tenant_id
58
+ )
59
+
60
+ return result
@@ -0,0 +1,97 @@
1
+ """
2
+ Lambda handler for updating billing accounts.
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.payments.services import PaymentService
10
+
11
+
12
+ # Factory creates handler (defaults to secure auth)
13
+ handler_wrapper = create_handler(
14
+ service_class=PaymentService,
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
+ Update a billing account.
23
+
24
+ Args:
25
+ event: Lambda event from API Gateway
26
+ context: Lambda context
27
+ injected_service: Optional PaymentService for testing
28
+
29
+ Path parameters:
30
+ - accountId: Billing account ID
31
+
32
+ Expected body (camelCase from frontend):
33
+ {
34
+ "billingEmail": "newemail@example.com",
35
+ "billingName": "Jane Doe",
36
+ "addressLine1": "456 New St",
37
+ "stripeCustomerId": "cus_yyy",
38
+ "defaultPaymentMethodId": "pm_xxx",
39
+ "autoChargeEnabled": true,
40
+ "status": "active"
41
+ // Any updateable field from BillingAccount
42
+ }
43
+
44
+ Returns 200 with updated billing account
45
+ """
46
+ return handler_wrapper.execute(event, context, update_billing_account, injected_service)
47
+
48
+
49
+ def update_billing_account(
50
+ event: Dict[str, Any],
51
+ service: PaymentService,
52
+ user_context: Dict[str, str]
53
+ ) -> Any:
54
+ """
55
+ Business logic for updating billing account.
56
+ """
57
+ payload = event["parsed_body"]
58
+ tenant_id = user_context.get("tenant_id")
59
+
60
+ # Extract account ID from path parameters
61
+ path_params = event.get("pathParameters", {})
62
+ account_id = path_params.get("accountId")
63
+
64
+ if not account_id:
65
+ raise ValueError("accountId path parameter is required")
66
+
67
+ # All payload fields are treated as updates
68
+ updates = {}
69
+ updatable_fields = [
70
+ "billing_email", "billing_name", "billing_phone",
71
+ "address_line1", "address_line2", "address_city",
72
+ "address_state", "address_postal_code", "address_country",
73
+ "stripe_customer_id", "stripe_account_id",
74
+ "country_code", "locale",
75
+ "tax_id", "tax_id_type", "tax_exempt", "tax_metadata",
76
+ "default_payment_method_id", "allowed_payment_methods",
77
+ "auto_charge_enabled", "require_cvv", "send_receipts",
78
+ "balance_cents", "credit_limit_cents",
79
+ "status", "status_reason",
80
+ "notes", "external_reference"
81
+ ]
82
+
83
+ for field in updatable_fields:
84
+ if field in payload:
85
+ updates[field] = payload[field]
86
+
87
+ if not updates:
88
+ raise ValueError("No valid fields to update")
89
+
90
+ # Update billing account
91
+ result = service.update_billing_account(
92
+ account_id=account_id,
93
+ tenant_id=tenant_id,
94
+ updates=updates
95
+ )
96
+
97
+ return result
@@ -0,0 +1,97 @@
1
+ """
2
+ Lambda handler for creating payment intents.
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.payments.services import PaymentService
10
+
11
+
12
+ # Factory creates handler (defaults to secure auth)
13
+ handler_wrapper = create_handler(
14
+ service_class=PaymentService,
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
+ Create a payment intent.
23
+
24
+ Args:
25
+ event: Lambda event from API Gateway
26
+ context: Lambda context
27
+ injected_service: Optional PaymentService for testing
28
+
29
+ Expected body (camelCase from frontend):
30
+ {
31
+ "billingAccountId": "account-123",
32
+ "amountCents": 5000, // $50.00
33
+ "currencyCode": "USD",
34
+ "pspType": "stripe", // "stripe", "paypal", "square"
35
+ "description": "Subscription payment",
36
+ "statementDescriptor": "ACME SUBSCRIPTION",
37
+ "receiptEmail": "user@example.com",
38
+ "setupFutureUsage": "off_session", // Optional
39
+ "captureMethod": "automatic", // Optional
40
+ "invoiceId": "inv-123", // Optional
41
+ "subscriptionId": "sub-123" // Optional
42
+ }
43
+
44
+ Returns 201 with created payment intent
45
+ """
46
+ return handler_wrapper.execute(event, context, create_payment_intent, injected_service)
47
+
48
+
49
+ def create_payment_intent(
50
+ event: Dict[str, Any],
51
+ service: PaymentService,
52
+ user_context: Dict[str, str]
53
+ ) -> Any:
54
+ """
55
+ Business logic for creating payment intents.
56
+ """
57
+ payload = event["parsed_body"]
58
+ tenant_id = user_context.get("tenant_id")
59
+
60
+ # Extract required fields
61
+ billing_account_id = payload.get("billing_account_id")
62
+ amount_cents = payload.get("amount_cents")
63
+
64
+ if not billing_account_id:
65
+ raise ValueError("billing_account_id is required")
66
+ if amount_cents is None or amount_cents <= 0:
67
+ raise ValueError("amount_cents must be greater than 0")
68
+
69
+ # Extract optional fields with defaults
70
+ currency_code = payload.get("currency_code", "USD")
71
+ psp_type = payload.get("psp_type", "stripe")
72
+
73
+ # Build kwargs for optional fields
74
+ kwargs = {}
75
+ optional_fields = [
76
+ "psp_intent_id", "psp_client_secret",
77
+ "payment_method_id", "payment_method_type",
78
+ "description", "statement_descriptor", "receipt_email",
79
+ "setup_future_usage", "capture_method", "confirmation_method",
80
+ "invoice_id", "subscription_id"
81
+ ]
82
+
83
+ for field in optional_fields:
84
+ if field in payload:
85
+ kwargs[field] = payload[field]
86
+
87
+ # Create payment intent
88
+ result = service.create_payment_intent(
89
+ tenant_id=tenant_id,
90
+ billing_account_id=billing_account_id,
91
+ amount_cents=amount_cents,
92
+ currency_code=currency_code,
93
+ psp_type=psp_type,
94
+ **kwargs
95
+ )
96
+
97
+ return result