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,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,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
|