ai-lls-lib 1.1.0__py3-none-any.whl → 1.3.0__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.
- ai_lls_lib/__init__.py +1 -1
- ai_lls_lib/auth/__init__.py +4 -0
- ai_lls_lib/auth/context_parser.py +68 -0
- ai_lls_lib/cli/__main__.py +2 -1
- ai_lls_lib/cli/commands/stripe.py +329 -0
- ai_lls_lib/cli/env_loader.py +122 -0
- ai_lls_lib/payment/__init__.py +13 -0
- ai_lls_lib/payment/credit_manager.py +174 -0
- ai_lls_lib/payment/models.py +96 -0
- ai_lls_lib/payment/stripe_manager.py +486 -0
- ai_lls_lib/payment/webhook_processor.py +215 -0
- {ai_lls_lib-1.1.0.dist-info → ai_lls_lib-1.3.0.dist-info}/METADATA +2 -1
- {ai_lls_lib-1.1.0.dist-info → ai_lls_lib-1.3.0.dist-info}/RECORD +15 -6
- {ai_lls_lib-1.1.0.dist-info → ai_lls_lib-1.3.0.dist-info}/WHEEL +0 -0
- {ai_lls_lib-1.1.0.dist-info → ai_lls_lib-1.3.0.dist-info}/entry_points.txt +0 -0
ai_lls_lib/__init__.py
CHANGED
@@ -13,7 +13,7 @@ from ai_lls_lib.core.verifier import PhoneVerifier
|
|
13
13
|
from ai_lls_lib.core.processor import BulkProcessor
|
14
14
|
from ai_lls_lib.core.cache import DynamoDBCache
|
15
15
|
|
16
|
-
__version__ = "1.
|
16
|
+
__version__ = "1.3.0"
|
17
17
|
__all__ = [
|
18
18
|
"PhoneVerification",
|
19
19
|
"BulkJob",
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"""Auth context parser for HTTP API v2.0 events."""
|
2
|
+
from typing import Any, Dict, Optional
|
3
|
+
|
4
|
+
|
5
|
+
def get_user_from_event(event: Dict[str, Any]) -> Optional[str]:
|
6
|
+
"""
|
7
|
+
Extract user ID from HTTP API v2.0 event with all possible paths.
|
8
|
+
Handles both JWT and API key authentication contexts.
|
9
|
+
|
10
|
+
This function handles the complexities of AWS API Gateway authorizer contexts,
|
11
|
+
especially when EnableSimpleResponses is set to false, which wraps the
|
12
|
+
context in a 'lambda' key.
|
13
|
+
|
14
|
+
Args:
|
15
|
+
event: The Lambda event from API Gateway HTTP API v2.0
|
16
|
+
|
17
|
+
Returns:
|
18
|
+
User ID string if found, None otherwise
|
19
|
+
"""
|
20
|
+
request_context = event.get("requestContext", {})
|
21
|
+
auth = request_context.get("authorizer", {})
|
22
|
+
|
23
|
+
# Handle lambda-wrapped context (EnableSimpleResponses: false)
|
24
|
+
# When EnableSimpleResponses is false, the authorizer context is wrapped
|
25
|
+
lam_ctx = auth.get("lambda", auth) if isinstance(auth.get("lambda"), dict) else auth
|
26
|
+
|
27
|
+
# Try all possible paths for user_id in priority order
|
28
|
+
user_id = (
|
29
|
+
# Lambda authorizer paths (most common with current setup)
|
30
|
+
lam_ctx.get("principal_id") or
|
31
|
+
lam_ctx.get("principalId") or
|
32
|
+
lam_ctx.get("sub") or
|
33
|
+
lam_ctx.get("user_id") or
|
34
|
+
# JWT paths (when using JWT authorizer directly)
|
35
|
+
auth.get("jwt", {}).get("claims", {}).get("sub") or
|
36
|
+
# Direct auth paths (fallback)
|
37
|
+
auth.get("principal_id") or
|
38
|
+
auth.get("principalId") or
|
39
|
+
auth.get("sub")
|
40
|
+
)
|
41
|
+
|
42
|
+
return user_id
|
43
|
+
|
44
|
+
|
45
|
+
def get_email_from_event(event: Dict[str, Any]) -> Optional[str]:
|
46
|
+
"""
|
47
|
+
Extract email from HTTP API v2.0 event.
|
48
|
+
|
49
|
+
Args:
|
50
|
+
event: The Lambda event from API Gateway HTTP API v2.0
|
51
|
+
|
52
|
+
Returns:
|
53
|
+
Email string if found, None otherwise
|
54
|
+
"""
|
55
|
+
request_context = event.get("requestContext", {})
|
56
|
+
auth = request_context.get("authorizer", {})
|
57
|
+
|
58
|
+
# Handle lambda-wrapped context
|
59
|
+
lam_ctx = auth.get("lambda", auth) if isinstance(auth.get("lambda"), dict) else auth
|
60
|
+
|
61
|
+
# Try to get email from various locations
|
62
|
+
email = (
|
63
|
+
lam_ctx.get("email") or
|
64
|
+
auth.get("jwt", {}).get("claims", {}).get("email") or
|
65
|
+
auth.get("email")
|
66
|
+
)
|
67
|
+
|
68
|
+
return email
|
ai_lls_lib/cli/__main__.py
CHANGED
@@ -3,7 +3,7 @@ Landline Scrubber CLI entry point
|
|
3
3
|
"""
|
4
4
|
import click
|
5
5
|
import sys
|
6
|
-
from ai_lls_lib.cli.commands import verify, cache, admin, test_stack
|
6
|
+
from ai_lls_lib.cli.commands import verify, cache, admin, test_stack, stripe
|
7
7
|
|
8
8
|
@click.group()
|
9
9
|
@click.version_option(version="0.1.0", prog_name="ai-lls")
|
@@ -16,6 +16,7 @@ cli.add_command(verify.verify_group)
|
|
16
16
|
cli.add_command(cache.cache_group)
|
17
17
|
cli.add_command(admin.admin_group)
|
18
18
|
cli.add_command(test_stack.test_stack_group)
|
19
|
+
cli.add_command(stripe.stripe_group)
|
19
20
|
|
20
21
|
def main():
|
21
22
|
"""Main entry point"""
|
@@ -0,0 +1,329 @@
|
|
1
|
+
"""Stripe management CLI commands."""
|
2
|
+
|
3
|
+
import click
|
4
|
+
import os
|
5
|
+
import json
|
6
|
+
from typing import Optional
|
7
|
+
from ..env_loader import load_environment_config, get_stripe_key
|
8
|
+
|
9
|
+
@click.group(name="stripe")
|
10
|
+
def stripe_group():
|
11
|
+
"""Manage Stripe products and prices."""
|
12
|
+
pass
|
13
|
+
|
14
|
+
|
15
|
+
@stripe_group.command("seed")
|
16
|
+
@click.option("--environment", type=click.Choice(["staging", "production"]), required=True)
|
17
|
+
@click.option("--api-key", help="Stripe API key (overrides environment)")
|
18
|
+
@click.option("--dry-run", is_flag=True, help="Show what would be created without making changes")
|
19
|
+
def seed_products(environment: str, api_key: Optional[str], dry_run: bool):
|
20
|
+
"""Create or update Stripe products and prices with metadata."""
|
21
|
+
try:
|
22
|
+
import stripe
|
23
|
+
except ImportError:
|
24
|
+
click.echo("Error: stripe package not installed. Run: pip install stripe", err=True)
|
25
|
+
return
|
26
|
+
|
27
|
+
# Load API key from environment if not provided
|
28
|
+
if not api_key:
|
29
|
+
api_key = get_stripe_key(environment)
|
30
|
+
if not api_key:
|
31
|
+
click.echo(f"Error: No Stripe API key found for {environment} environment", err=True)
|
32
|
+
click.echo(f"Set {environment.upper()}_STRIPE_SECRET_KEY or STRIPE_SECRET_KEY", err=True)
|
33
|
+
return
|
34
|
+
click.echo(f"Using Stripe key for {environment} environment", err=True)
|
35
|
+
|
36
|
+
stripe.api_key = api_key
|
37
|
+
|
38
|
+
# Define the products and prices to create
|
39
|
+
products_config = [
|
40
|
+
{
|
41
|
+
"name": "Landline Scrubber - Variable",
|
42
|
+
"description": "Pay as you go - choose your amount",
|
43
|
+
"metadata": {
|
44
|
+
"product_type": "landline_scrubber",
|
45
|
+
"environment": environment,
|
46
|
+
"tier": "VARIABLE"
|
47
|
+
},
|
48
|
+
"price": {
|
49
|
+
"unit_amount": 500, # $5.00 minimum
|
50
|
+
"currency": "usd",
|
51
|
+
"metadata": {
|
52
|
+
"product_type": "landline_scrubber",
|
53
|
+
"environment": environment,
|
54
|
+
"plan_type": "prepaid",
|
55
|
+
"tier": "VARIABLE",
|
56
|
+
"variable_amount": "true",
|
57
|
+
"min_amount": "5",
|
58
|
+
"default_amounts": "5,10,50",
|
59
|
+
"credits_per_dollar": "285", # ~$0.00175 per credit
|
60
|
+
"plan_credits_text": "Variable credits",
|
61
|
+
"percent_off": "",
|
62
|
+
"active": "true"
|
63
|
+
}
|
64
|
+
}
|
65
|
+
},
|
66
|
+
{
|
67
|
+
"name": "Landline Scrubber - STANDARD",
|
68
|
+
"description": "One-time purchase",
|
69
|
+
"metadata": {
|
70
|
+
"product_type": "landline_scrubber",
|
71
|
+
"environment": environment,
|
72
|
+
"tier": "STANDARD"
|
73
|
+
},
|
74
|
+
"price": {
|
75
|
+
"unit_amount": 1000, # $10.00
|
76
|
+
"currency": "usd",
|
77
|
+
"metadata": {
|
78
|
+
"product_type": "landline_scrubber",
|
79
|
+
"environment": environment,
|
80
|
+
"plan_type": "prepaid",
|
81
|
+
"tier": "STANDARD",
|
82
|
+
"credits": "5000",
|
83
|
+
"plan_credits_text": "5,000 credits",
|
84
|
+
"percent_off": "",
|
85
|
+
"active": "true"
|
86
|
+
}
|
87
|
+
}
|
88
|
+
},
|
89
|
+
{
|
90
|
+
"name": "Landline Scrubber - POWER",
|
91
|
+
"description": "Best value",
|
92
|
+
"metadata": {
|
93
|
+
"product_type": "landline_scrubber",
|
94
|
+
"environment": environment,
|
95
|
+
"tier": "POWER"
|
96
|
+
},
|
97
|
+
"price": {
|
98
|
+
"unit_amount": 5000, # $50.00
|
99
|
+
"currency": "usd",
|
100
|
+
"metadata": {
|
101
|
+
"product_type": "landline_scrubber",
|
102
|
+
"environment": environment,
|
103
|
+
"plan_type": "prepaid",
|
104
|
+
"tier": "POWER",
|
105
|
+
"credits": "28500",
|
106
|
+
"plan_credits_text": "28,500 credits",
|
107
|
+
"percent_off": "12.5% OFF",
|
108
|
+
"active": "true"
|
109
|
+
}
|
110
|
+
}
|
111
|
+
},
|
112
|
+
{
|
113
|
+
"name": "Landline Scrubber - ELITE",
|
114
|
+
"description": "Maximum savings",
|
115
|
+
"metadata": {
|
116
|
+
"product_type": "landline_scrubber",
|
117
|
+
"environment": environment,
|
118
|
+
"tier": "ELITE"
|
119
|
+
},
|
120
|
+
"price": {
|
121
|
+
"unit_amount": 10000, # $100.00
|
122
|
+
"currency": "usd",
|
123
|
+
"metadata": {
|
124
|
+
"product_type": "landline_scrubber",
|
125
|
+
"environment": environment,
|
126
|
+
"plan_type": "prepaid",
|
127
|
+
"tier": "ELITE",
|
128
|
+
"credits": "66666",
|
129
|
+
"plan_credits_text": "66,666 credits",
|
130
|
+
"percent_off": "25% OFF",
|
131
|
+
"active": "true"
|
132
|
+
}
|
133
|
+
}
|
134
|
+
},
|
135
|
+
{
|
136
|
+
"name": "Landline Scrubber - UNLIMITED",
|
137
|
+
"description": "Monthly subscription",
|
138
|
+
"metadata": {
|
139
|
+
"product_type": "landline_scrubber",
|
140
|
+
"environment": environment,
|
141
|
+
"tier": "UNLIMITED"
|
142
|
+
},
|
143
|
+
"price": {
|
144
|
+
"unit_amount": 29900, # $299.00
|
145
|
+
"currency": "usd",
|
146
|
+
"recurring": {"interval": "month"},
|
147
|
+
"metadata": {
|
148
|
+
"product_type": "landline_scrubber",
|
149
|
+
"environment": environment,
|
150
|
+
"plan_type": "postpaid",
|
151
|
+
"tier": "UNLIMITED",
|
152
|
+
"credits": "unlimited",
|
153
|
+
"plan_credits_text": "Unlimited",
|
154
|
+
"percent_off": "",
|
155
|
+
"active": "true"
|
156
|
+
}
|
157
|
+
}
|
158
|
+
}
|
159
|
+
]
|
160
|
+
|
161
|
+
if dry_run:
|
162
|
+
click.echo("DRY RUN - Would create the following:")
|
163
|
+
for config in products_config:
|
164
|
+
click.echo(f"\nProduct: {config['name']}")
|
165
|
+
click.echo(f" Description: {config['description']}")
|
166
|
+
click.echo(f" Price: ${config['price']['unit_amount'] / 100:.2f}")
|
167
|
+
if "recurring" in config["price"]:
|
168
|
+
click.echo(f" Billing: Monthly subscription")
|
169
|
+
else:
|
170
|
+
click.echo(f" Billing: One-time payment")
|
171
|
+
return
|
172
|
+
|
173
|
+
created_prices = []
|
174
|
+
|
175
|
+
for config in products_config:
|
176
|
+
try:
|
177
|
+
# Check if product already exists
|
178
|
+
existing_products = stripe.Product.list(limit=100)
|
179
|
+
product = None
|
180
|
+
for p in existing_products.data:
|
181
|
+
if (p.metadata.get("product_type") == "landline_scrubber" and
|
182
|
+
p.metadata.get("environment") == environment and
|
183
|
+
p.metadata.get("tier") == config["metadata"]["tier"]):
|
184
|
+
product = p
|
185
|
+
click.echo(f"Found existing product: {product.name}")
|
186
|
+
break
|
187
|
+
|
188
|
+
if not product:
|
189
|
+
# Create new product
|
190
|
+
product = stripe.Product.create(
|
191
|
+
name=config["name"],
|
192
|
+
description=config["description"],
|
193
|
+
metadata=config["metadata"]
|
194
|
+
)
|
195
|
+
click.echo(f"Created product: {product.name}")
|
196
|
+
|
197
|
+
# Create price (always create new prices, don't modify existing)
|
198
|
+
price_data = {
|
199
|
+
"product": product.id,
|
200
|
+
"unit_amount": config["price"]["unit_amount"],
|
201
|
+
"currency": config["price"]["currency"],
|
202
|
+
"metadata": config["price"]["metadata"]
|
203
|
+
}
|
204
|
+
|
205
|
+
if "recurring" in config["price"]:
|
206
|
+
price_data["recurring"] = config["price"]["recurring"]
|
207
|
+
|
208
|
+
price = stripe.Price.create(**price_data)
|
209
|
+
created_prices.append(price.id)
|
210
|
+
click.echo(f" Created price: {price.id} (${price.unit_amount / 100:.2f})")
|
211
|
+
|
212
|
+
except stripe.error.StripeError as e:
|
213
|
+
click.echo(f"Error creating {config['name']}: {e}", err=True)
|
214
|
+
|
215
|
+
if created_prices:
|
216
|
+
click.echo(f"\nCreated {len(created_prices)} prices for {environment} environment")
|
217
|
+
click.echo("\nPrice IDs:")
|
218
|
+
for price_id in created_prices:
|
219
|
+
click.echo(f" {price_id}")
|
220
|
+
|
221
|
+
|
222
|
+
@stripe_group.command("list")
|
223
|
+
@click.option("--environment", type=click.Choice(["staging", "production"]), default="staging")
|
224
|
+
@click.option("--api-key", help="Stripe API key (overrides environment)")
|
225
|
+
@click.option("--json", "output_json", is_flag=True, help="Output as JSON")
|
226
|
+
def list_products(environment: str, api_key: Optional[str], output_json: bool):
|
227
|
+
"""List all products and prices with metadata."""
|
228
|
+
try:
|
229
|
+
from ai_lls_lib.payment import StripeManager
|
230
|
+
except ImportError:
|
231
|
+
click.echo("Error: Payment module not found", err=True)
|
232
|
+
return
|
233
|
+
|
234
|
+
# Load API key from environment if not provided
|
235
|
+
if not api_key:
|
236
|
+
api_key = get_stripe_key(environment)
|
237
|
+
if not api_key:
|
238
|
+
click.echo(f"Error: No Stripe API key found for {environment} environment", err=True)
|
239
|
+
click.echo(f"Set {environment.upper()}_STRIPE_SECRET_KEY or STRIPE_SECRET_KEY", err=True)
|
240
|
+
return
|
241
|
+
|
242
|
+
try:
|
243
|
+
manager = StripeManager(api_key=api_key, environment=environment)
|
244
|
+
plans = manager.list_plans()
|
245
|
+
|
246
|
+
if output_json:
|
247
|
+
output = [plan.to_dict() for plan in plans]
|
248
|
+
click.echo(json.dumps(output, indent=2))
|
249
|
+
else:
|
250
|
+
click.echo(f"Active plans for {environment} environment:\n")
|
251
|
+
for plan in plans:
|
252
|
+
click.echo(f"{plan.plan_name}:")
|
253
|
+
click.echo(f" Price: ${plan.plan_amount:.2f}")
|
254
|
+
click.echo(f" Credits: {plan.plan_credits_text}")
|
255
|
+
click.echo(f" Type: {plan.plan_type}")
|
256
|
+
click.echo(f" Reference: {plan.plan_reference}")
|
257
|
+
if plan.percent_off:
|
258
|
+
click.echo(f" Discount: {plan.percent_off}")
|
259
|
+
click.echo()
|
260
|
+
|
261
|
+
except Exception as e:
|
262
|
+
click.echo(f"Error: {e}", err=True)
|
263
|
+
|
264
|
+
|
265
|
+
@stripe_group.command("webhook")
|
266
|
+
@click.option("--endpoint-url", help="Webhook endpoint URL")
|
267
|
+
@click.option("--environment", type=click.Choice(["staging", "production"]), default="staging")
|
268
|
+
@click.option("--api-key", help="Stripe API key (overrides environment)")
|
269
|
+
@click.option("--print-secret", is_flag=True, help="Print the webhook signing secret")
|
270
|
+
def setup_webhook(endpoint_url: Optional[str], environment: str, api_key: Optional[str], print_secret: bool):
|
271
|
+
"""Configure or display webhook endpoint."""
|
272
|
+
try:
|
273
|
+
import stripe
|
274
|
+
except ImportError:
|
275
|
+
click.echo("Error: stripe package not installed. Run: pip install stripe", err=True)
|
276
|
+
return
|
277
|
+
|
278
|
+
# Load API key from environment if not provided
|
279
|
+
if not api_key:
|
280
|
+
api_key = get_stripe_key(environment)
|
281
|
+
if not api_key:
|
282
|
+
click.echo(f"Error: No Stripe API key found for {environment} environment", err=True)
|
283
|
+
click.echo(f"Set {environment.upper()}_STRIPE_SECRET_KEY or STRIPE_SECRET_KEY", err=True)
|
284
|
+
return
|
285
|
+
|
286
|
+
stripe.api_key = api_key
|
287
|
+
|
288
|
+
if print_secret:
|
289
|
+
# List existing webhooks
|
290
|
+
webhooks = stripe.WebhookEndpoint.list(limit=10)
|
291
|
+
if webhooks.data:
|
292
|
+
click.echo("Existing webhook endpoints:\n")
|
293
|
+
for webhook in webhooks.data:
|
294
|
+
click.echo(f"URL: {webhook.url}")
|
295
|
+
click.echo(f"ID: {webhook.id}")
|
296
|
+
click.echo(f"Secret: {webhook.secret}")
|
297
|
+
click.echo(f"Status: {webhook.status}")
|
298
|
+
click.echo()
|
299
|
+
else:
|
300
|
+
click.echo("No webhook endpoints configured")
|
301
|
+
return
|
302
|
+
|
303
|
+
if not endpoint_url:
|
304
|
+
click.echo("Error: --endpoint-url required to create webhook", err=True)
|
305
|
+
return
|
306
|
+
|
307
|
+
try:
|
308
|
+
# Create webhook endpoint
|
309
|
+
webhook = stripe.WebhookEndpoint.create(
|
310
|
+
url=endpoint_url,
|
311
|
+
enabled_events=[
|
312
|
+
"checkout.session.completed",
|
313
|
+
"customer.subscription.created",
|
314
|
+
"customer.subscription.updated",
|
315
|
+
"customer.subscription.deleted",
|
316
|
+
"invoice.payment_succeeded",
|
317
|
+
"invoice.payment_failed"
|
318
|
+
]
|
319
|
+
)
|
320
|
+
|
321
|
+
click.echo(f"Webhook endpoint created:")
|
322
|
+
click.echo(f" URL: {webhook.url}")
|
323
|
+
click.echo(f" ID: {webhook.id}")
|
324
|
+
click.echo(f" Secret: {webhook.secret}")
|
325
|
+
click.echo(f"\nAdd this to your environment:")
|
326
|
+
click.echo(f" {environment.upper()}_STRIPE_WEBHOOK_SECRET={webhook.secret}")
|
327
|
+
|
328
|
+
except stripe.error.StripeError as e:
|
329
|
+
click.echo(f"Error creating webhook: {e}", err=True)
|
@@ -0,0 +1,122 @@
|
|
1
|
+
"""Environment variable loader for CLI commands."""
|
2
|
+
|
3
|
+
import os
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import Optional, Dict
|
6
|
+
import click
|
7
|
+
|
8
|
+
|
9
|
+
def load_env_file(env_path: Path) -> Dict[str, str]:
|
10
|
+
"""Load environment variables from a .env file."""
|
11
|
+
env_vars = {}
|
12
|
+
if env_path.exists():
|
13
|
+
try:
|
14
|
+
with open(env_path, 'r') as f:
|
15
|
+
for line in f:
|
16
|
+
line = line.strip()
|
17
|
+
if line and not line.startswith('#') and '=' in line:
|
18
|
+
key, value = line.split('=', 1)
|
19
|
+
# Remove quotes if present
|
20
|
+
value = value.strip().strip('"').strip("'")
|
21
|
+
env_vars[key.strip()] = value
|
22
|
+
except Exception as e:
|
23
|
+
click.echo(f"Warning: Could not read {env_path}: {e}", err=True)
|
24
|
+
return env_vars
|
25
|
+
|
26
|
+
|
27
|
+
def load_environment_config() -> Dict[str, str]:
|
28
|
+
"""
|
29
|
+
Load environment variables from multiple sources in order:
|
30
|
+
1. ~/.lls/.env (user global config)
|
31
|
+
2. ./.env (project local config)
|
32
|
+
3. System environment variables (highest priority)
|
33
|
+
|
34
|
+
Returns merged dictionary with system env taking precedence.
|
35
|
+
"""
|
36
|
+
env_vars = {}
|
37
|
+
|
38
|
+
# Load from ~/.lls/.env
|
39
|
+
home_env = Path.home() / '.lls' / '.env'
|
40
|
+
if home_env.exists():
|
41
|
+
home_vars = load_env_file(home_env)
|
42
|
+
env_vars.update(home_vars)
|
43
|
+
click.echo(f"Loaded {len(home_vars)} variables from {home_env}", err=True)
|
44
|
+
|
45
|
+
# Load from ./.env
|
46
|
+
local_env = Path('.env')
|
47
|
+
if local_env.exists():
|
48
|
+
local_vars = load_env_file(local_env)
|
49
|
+
env_vars.update(local_vars)
|
50
|
+
click.echo(f"Loaded {len(local_vars)} variables from {local_env}", err=True)
|
51
|
+
|
52
|
+
# System environment variables override file-based ones
|
53
|
+
env_vars.update(os.environ)
|
54
|
+
|
55
|
+
return env_vars
|
56
|
+
|
57
|
+
|
58
|
+
def get_stripe_key(environment: str, env_vars: Optional[Dict[str, str]] = None) -> Optional[str]:
|
59
|
+
"""
|
60
|
+
Get Stripe API key for the specified environment.
|
61
|
+
|
62
|
+
Looks for keys in this order:
|
63
|
+
1. STAGING_STRIPE_SECRET_KEY or PROD_STRIPE_SECRET_KEY (based on environment)
|
64
|
+
2. STRIPE_SECRET_KEY (fallback)
|
65
|
+
|
66
|
+
Args:
|
67
|
+
environment: 'staging' or 'production'
|
68
|
+
env_vars: Optional pre-loaded environment variables
|
69
|
+
|
70
|
+
Returns:
|
71
|
+
Stripe API key or None if not found
|
72
|
+
"""
|
73
|
+
if env_vars is None:
|
74
|
+
env_vars = load_environment_config()
|
75
|
+
|
76
|
+
# Map environment names to prefixes
|
77
|
+
env_prefix = {
|
78
|
+
'staging': 'STAGING',
|
79
|
+
'production': 'PROD'
|
80
|
+
}.get(environment, environment.upper())
|
81
|
+
|
82
|
+
# Try environment-specific key first
|
83
|
+
env_key = f"{env_prefix}_STRIPE_SECRET_KEY"
|
84
|
+
if env_key in env_vars:
|
85
|
+
return env_vars[env_key]
|
86
|
+
|
87
|
+
# Fall back to generic key
|
88
|
+
if 'STRIPE_SECRET_KEY' in env_vars:
|
89
|
+
return env_vars['STRIPE_SECRET_KEY']
|
90
|
+
|
91
|
+
return None
|
92
|
+
|
93
|
+
|
94
|
+
def get_env_variable(key: str, environment: Optional[str] = None,
|
95
|
+
env_vars: Optional[Dict[str, str]] = None) -> Optional[str]:
|
96
|
+
"""
|
97
|
+
Get an environment variable, optionally with environment prefix.
|
98
|
+
|
99
|
+
Args:
|
100
|
+
key: Variable name (e.g., 'API_URL')
|
101
|
+
environment: Optional environment ('staging' or 'production')
|
102
|
+
env_vars: Optional pre-loaded environment variables
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
Variable value or None if not found
|
106
|
+
"""
|
107
|
+
if env_vars is None:
|
108
|
+
env_vars = load_environment_config()
|
109
|
+
|
110
|
+
if environment:
|
111
|
+
env_prefix = {
|
112
|
+
'staging': 'STAGING',
|
113
|
+
'production': 'PROD'
|
114
|
+
}.get(environment, environment.upper())
|
115
|
+
|
116
|
+
# Try environment-specific key first
|
117
|
+
env_key = f"{env_prefix}_{key}"
|
118
|
+
if env_key in env_vars:
|
119
|
+
return env_vars[env_key]
|
120
|
+
|
121
|
+
# Fall back to non-prefixed key
|
122
|
+
return env_vars.get(key)
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Payment module for Landline Scrubber."""
|
2
|
+
|
3
|
+
from .models import Plan, PlanType, SubscriptionStatus
|
4
|
+
from .stripe_manager import StripeManager
|
5
|
+
from .credit_manager import CreditManager
|
6
|
+
|
7
|
+
__all__ = [
|
8
|
+
"Plan",
|
9
|
+
"PlanType",
|
10
|
+
"SubscriptionStatus",
|
11
|
+
"StripeManager",
|
12
|
+
"CreditManager",
|
13
|
+
]
|