ai-lls-lib 1.2.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/cli/commands/stripe.py +26 -4
- ai_lls_lib/payment/stripe_manager.py +42 -29
- ai_lls_lib/payment/webhook_processor.py +53 -1
- {ai_lls_lib-1.2.0.dist-info → ai_lls_lib-1.3.0.dist-info}/METADATA +1 -1
- {ai_lls_lib-1.2.0.dist-info → ai_lls_lib-1.3.0.dist-info}/RECORD +8 -8
- {ai_lls_lib-1.2.0.dist-info → ai_lls_lib-1.3.0.dist-info}/WHEEL +0 -0
- {ai_lls_lib-1.2.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",
|
@@ -37,6 +37,32 @@ def seed_products(environment: str, api_key: Optional[str], dry_run: bool):
|
|
37
37
|
|
38
38
|
# Define the products and prices to create
|
39
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
|
+
},
|
40
66
|
{
|
41
67
|
"name": "Landline Scrubber - STANDARD",
|
42
68
|
"description": "One-time purchase",
|
@@ -53,7 +79,6 @@ def seed_products(environment: str, api_key: Optional[str], dry_run: bool):
|
|
53
79
|
"environment": environment,
|
54
80
|
"plan_type": "prepaid",
|
55
81
|
"tier": "STANDARD",
|
56
|
-
"plan_reference": "79541679412215", # Legacy ID for compatibility
|
57
82
|
"credits": "5000",
|
58
83
|
"plan_credits_text": "5,000 credits",
|
59
84
|
"percent_off": "",
|
@@ -77,7 +102,6 @@ def seed_products(environment: str, api_key: Optional[str], dry_run: bool):
|
|
77
102
|
"environment": environment,
|
78
103
|
"plan_type": "prepaid",
|
79
104
|
"tier": "POWER",
|
80
|
-
"plan_reference": "79541679412216", # Legacy ID for compatibility
|
81
105
|
"credits": "28500",
|
82
106
|
"plan_credits_text": "28,500 credits",
|
83
107
|
"percent_off": "12.5% OFF",
|
@@ -101,7 +125,6 @@ def seed_products(environment: str, api_key: Optional[str], dry_run: bool):
|
|
101
125
|
"environment": environment,
|
102
126
|
"plan_type": "prepaid",
|
103
127
|
"tier": "ELITE",
|
104
|
-
"plan_reference": "79541679412217", # Legacy ID for compatibility
|
105
128
|
"credits": "66666",
|
106
129
|
"plan_credits_text": "66,666 credits",
|
107
130
|
"percent_off": "25% OFF",
|
@@ -126,7 +149,6 @@ def seed_products(environment: str, api_key: Optional[str], dry_run: bool):
|
|
126
149
|
"environment": environment,
|
127
150
|
"plan_type": "postpaid",
|
128
151
|
"tier": "UNLIMITED",
|
129
|
-
"plan_reference": "price_unlimited",
|
130
152
|
"credits": "unlimited",
|
131
153
|
"plan_credits_text": "Unlimited",
|
132
154
|
"percent_off": "",
|
@@ -186,49 +186,62 @@ class StripeManager:
|
|
186
186
|
def charge_prepaid(self, user_id: str, reference_code: str, amount: Optional[float] = None) -> Dict[str, Any]:
|
187
187
|
"""
|
188
188
|
Charge saved payment method for credit purchase.
|
189
|
-
Supports both fixed-price and
|
189
|
+
Supports both fixed-price and metadata-based variable-amount plans.
|
190
190
|
"""
|
191
191
|
try:
|
192
192
|
customer = self._get_or_create_customer(user_id)
|
193
193
|
|
194
|
-
#
|
195
|
-
|
194
|
+
# Look up price from Stripe
|
195
|
+
prices = stripe.Price.list(active=True, limit=100, expand=["data.product"])
|
196
|
+
price = None
|
196
197
|
|
197
|
-
|
198
|
+
for p in prices.data:
|
199
|
+
metadata = p.metadata or {}
|
200
|
+
# Match by price ID or plan_reference in metadata
|
201
|
+
if (p.id == reference_code or
|
202
|
+
metadata.get("plan_reference") == reference_code or
|
203
|
+
(metadata.get("tier") == reference_code and
|
204
|
+
metadata.get("environment") == self.environment)):
|
205
|
+
price = p
|
206
|
+
break
|
207
|
+
|
208
|
+
if not price:
|
209
|
+
raise ValueError(f"Invalid plan reference: {reference_code}")
|
210
|
+
|
211
|
+
price_metadata = price.metadata or {}
|
212
|
+
|
213
|
+
# Check if this is a variable amount plan
|
214
|
+
if price_metadata.get("variable_amount") == "true":
|
198
215
|
# Variable amount plan - validate amount
|
199
216
|
if not amount:
|
200
217
|
raise ValueError("Amount required for variable-amount plan")
|
201
218
|
|
202
|
-
#
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
if
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
219
|
+
# Get validation rules from metadata
|
220
|
+
min_amount = float(price_metadata.get("min_amount", "5"))
|
221
|
+
if amount < min_amount:
|
222
|
+
raise ValueError(f"Amount ${amount} is below minimum ${min_amount}")
|
223
|
+
|
224
|
+
# Check against default amounts if specified
|
225
|
+
default_amounts_str = price_metadata.get("default_amounts", "")
|
226
|
+
if default_amounts_str:
|
227
|
+
allowed_amounts = [float(x.strip()) for x in default_amounts_str.split(",")]
|
228
|
+
# Allow default amounts OR any amount >= minimum
|
229
|
+
if amount not in allowed_amounts and amount < max(allowed_amounts):
|
230
|
+
logger.info(f"Amount ${amount} not in defaults {allowed_amounts}, but allowed as >= ${min_amount}")
|
231
|
+
|
232
|
+
# Calculate credits based on credits_per_dollar
|
233
|
+
credits_per_dollar = float(price_metadata.get("credits_per_dollar", "285"))
|
214
234
|
credits_to_add = int(amount * credits_per_dollar)
|
215
235
|
charge_amount = int(amount * 100) # Convert to cents
|
216
236
|
|
217
237
|
else:
|
218
|
-
# Fixed price plan
|
219
|
-
prices = stripe.Price.list(active=True, limit=100)
|
220
|
-
price = None
|
221
|
-
|
222
|
-
for p in prices.data:
|
223
|
-
if p.id == reference_code or p.metadata.get("plan_reference") == reference_code:
|
224
|
-
price = p
|
225
|
-
break
|
226
|
-
|
227
|
-
if not price:
|
228
|
-
raise ValueError(f"Invalid plan reference: {reference_code}")
|
229
|
-
|
238
|
+
# Fixed price plan
|
230
239
|
charge_amount = price.unit_amount
|
231
|
-
|
240
|
+
credits_str = price_metadata.get("credits", "0")
|
241
|
+
if credits_str.lower() == "unlimited":
|
242
|
+
credits_to_add = 0 # Subscription handles this differently
|
243
|
+
else:
|
244
|
+
credits_to_add = int(credits_str)
|
232
245
|
|
233
246
|
# Get default payment method
|
234
247
|
default_pm = customer.invoice_settings.get("default_payment_method")
|
@@ -49,7 +49,10 @@ class WebhookProcessor:
|
|
49
49
|
|
50
50
|
logger.info(f"Processing webhook event: {event_type}")
|
51
51
|
|
52
|
-
if event_type == "
|
52
|
+
if event_type == "payment_intent.succeeded":
|
53
|
+
return self._handle_payment_intent_succeeded(event_data)
|
54
|
+
|
55
|
+
elif event_type == "checkout.session.completed":
|
53
56
|
return self._handle_checkout_completed(event_data)
|
54
57
|
|
55
58
|
elif event_type == "customer.subscription.created":
|
@@ -67,6 +70,9 @@ class WebhookProcessor:
|
|
67
70
|
elif event_type == "invoice.payment_failed":
|
68
71
|
return self._handle_invoice_failed(event_data)
|
69
72
|
|
73
|
+
elif event_type == "charge.dispute.created":
|
74
|
+
return self._handle_dispute_created(event_data)
|
75
|
+
|
70
76
|
else:
|
71
77
|
logger.info(f"Unhandled event type: {event_type}")
|
72
78
|
return {"message": f"Event {event_type} received but not processed"}
|
@@ -161,3 +167,49 @@ class WebhookProcessor:
|
|
161
167
|
logger.warning(f"Invoice payment failed for customer {customer_id}")
|
162
168
|
# Could pause subscription or send notification here
|
163
169
|
return {"status": "payment_failed"}
|
170
|
+
|
171
|
+
def _handle_payment_intent_succeeded(self, payment_intent: Dict[str, Any]) -> Dict[str, Any]:
|
172
|
+
"""Handle successful payment intent (credit purchase)."""
|
173
|
+
metadata = payment_intent.get("metadata", {})
|
174
|
+
user_id = metadata.get("user_id")
|
175
|
+
|
176
|
+
if not user_id:
|
177
|
+
logger.error("No user_id in payment_intent metadata")
|
178
|
+
return {"error": "Missing user_id"}
|
179
|
+
|
180
|
+
# Check if this is a verification charge ($1)
|
181
|
+
if metadata.get("type") == "verification":
|
182
|
+
# This was the $1 verification, credits already added in payment_setup handler
|
183
|
+
logger.info(f"Verification charge completed for user {user_id}")
|
184
|
+
return {"type": "verification", "status": "completed"}
|
185
|
+
|
186
|
+
# Get credits from metadata (set during payment creation)
|
187
|
+
credits = int(metadata.get("credits", 0))
|
188
|
+
|
189
|
+
if credits > 0:
|
190
|
+
new_balance = self.credit_manager.add_credits(user_id, credits)
|
191
|
+
logger.info(f"Added {credits} credits to user {user_id}, new balance: {new_balance}")
|
192
|
+
return {"credits_added": credits, "new_balance": new_balance}
|
193
|
+
|
194
|
+
return {"message": "Payment processed"}
|
195
|
+
|
196
|
+
def _handle_dispute_created(self, dispute: Dict[str, Any]) -> Dict[str, Any]:
|
197
|
+
"""Handle charge dispute (mark account as disputed)."""
|
198
|
+
# Get the charge and its metadata
|
199
|
+
charge_id = dispute.get("charge")
|
200
|
+
|
201
|
+
if not charge_id:
|
202
|
+
logger.error("No charge_id in dispute")
|
203
|
+
return {"error": "Missing charge_id"}
|
204
|
+
|
205
|
+
# In production, would fetch the charge from Stripe to get metadata
|
206
|
+
# For now, log the dispute for manual handling
|
207
|
+
amount = dispute.get("amount", 0) / 100.0
|
208
|
+
reason = dispute.get("reason", "unknown")
|
209
|
+
|
210
|
+
logger.warning(f"Dispute created for charge {charge_id}: ${amount}, reason: {reason}")
|
211
|
+
|
212
|
+
# TODO: Mark user account as disputed in CreditsTable
|
213
|
+
# This would prevent new purchases until resolved
|
214
|
+
|
215
|
+
return {"dispute_id": dispute.get("id"), "status": "created", "amount": amount}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
ai_lls_lib/__init__.py,sha256=
|
1
|
+
ai_lls_lib/__init__.py,sha256=qeDhkuaSaxKB6Moq7QFQcEkVg-preOmw_-zvG41ORc4,584
|
2
2
|
ai_lls_lib/auth/__init__.py,sha256=c6zomHSB6y9Seakf84ciGsD3XgWarIty9xty6P8fxVw,194
|
3
3
|
ai_lls_lib/auth/context_parser.py,sha256=8I0vGbtykNLWqm8ldedxXjE-E3nqsCy113JgeyuiJoM,2222
|
4
4
|
ai_lls_lib/cli/__init__.py,sha256=m9qjZTW1jpENwXAUeuRrlP0b66BWRcqSO28MSjvOyCs,74
|
@@ -7,7 +7,7 @@ ai_lls_lib/cli/aws_client.py,sha256=YcCWCpTNOW9JPLxSNLRy5-F5HPKguJJk7dPNrPqhJv0,
|
|
7
7
|
ai_lls_lib/cli/commands/__init__.py,sha256=_kROrYuR_p2i110c0OvNeArfEFQbn15zR1c3pdeZOoo,28
|
8
8
|
ai_lls_lib/cli/commands/admin.py,sha256=bNBJi2fZBP0J40JQP6HP7NYadNmI214iII1TeLhooyE,6687
|
9
9
|
ai_lls_lib/cli/commands/cache.py,sha256=vWt0vy4L9CEgUEWUzfdehU6u43PE8vUvHx7xxg4e5tw,5680
|
10
|
-
ai_lls_lib/cli/commands/stripe.py,sha256=
|
10
|
+
ai_lls_lib/cli/commands/stripe.py,sha256=Iccp8ZmNE18q9rvoox-Mo7dZgByEhIVP7otRklgr4uI,12707
|
11
11
|
ai_lls_lib/cli/commands/test_stack.py,sha256=rNq4mhRXX7Ixo67kSoEPWlxqgXCzM9e2PR96qTAG7pE,7378
|
12
12
|
ai_lls_lib/cli/commands/verify.py,sha256=V5ucjmjCUxqN8_AeEJWWgrmYin8BNV3h4WbZ-iX3loU,4238
|
13
13
|
ai_lls_lib/cli/env_loader.py,sha256=YTCB6QDyknOuedPbQsW4bezB5SgHzJMGjhpSPArHFVc,3762
|
@@ -19,15 +19,15 @@ ai_lls_lib/core/verifier.py,sha256=6uB_jawWoIqsNYAadTKr0lSolIWygg2gK6ykf8lrul0,2
|
|
19
19
|
ai_lls_lib/payment/__init__.py,sha256=xhUWgfLnk3syXXQItswmDXdfXUyJTXTQAA0zIUuCVII,295
|
20
20
|
ai_lls_lib/payment/credit_manager.py,sha256=TDrdW7BYjhtlg8MX_EJEnMNct1SU_6Tdb_k19_e8kpo,6343
|
21
21
|
ai_lls_lib/payment/models.py,sha256=JjSmWKwpuFF85Jzmabj6y7UyolJlJlsh5CmOWRg21B8,3339
|
22
|
-
ai_lls_lib/payment/stripe_manager.py,sha256
|
23
|
-
ai_lls_lib/payment/webhook_processor.py,sha256=
|
22
|
+
ai_lls_lib/payment/stripe_manager.py,sha256=Yc-2gAlGIf1EtBPpVUAQYH7oyis2Fk8k_9qAwoWtlYE,17969
|
23
|
+
ai_lls_lib/payment/webhook_processor.py,sha256=cIZqCS98Q305SCI3-4AJ1h-IJ-l-7fMby1FFy1ckP6k,8765
|
24
24
|
ai_lls_lib/providers/__init__.py,sha256=AEv3ARenWDwDo5PLCoszP2fQ70RgSHkrSLSUz7xHJDk,179
|
25
25
|
ai_lls_lib/providers/base.py,sha256=344XYOg7bxDQMWJ6Lle8U7NOHpabnCp0XYbZpeWpPAk,681
|
26
26
|
ai_lls_lib/providers/external.py,sha256=-Hhnlm8lQWRcWr5vG0dmD3sca2rohURrx0usOR2y1MM,2623
|
27
27
|
ai_lls_lib/providers/stub.py,sha256=847Tmw522B3HQ2j38BH1sdcZQy--RdtDcXsrIrFKNBQ,1150
|
28
28
|
ai_lls_lib/testing/__init__.py,sha256=RUxRYBzzPCPS15Umb6bUrE6rL5BQXBQf4SJM2E3ffrg,39
|
29
29
|
ai_lls_lib/testing/fixtures.py,sha256=_n6bbr95LnQf9Dvu1qKs2HsvHEA7AAbe59B75qxE10w,3310
|
30
|
-
ai_lls_lib-1.
|
31
|
-
ai_lls_lib-1.
|
32
|
-
ai_lls_lib-1.
|
33
|
-
ai_lls_lib-1.
|
30
|
+
ai_lls_lib-1.3.0.dist-info/METADATA,sha256=3rY5rpASz-JFelJRnWL9yM9o9vrFfRuu4AuGn0IcyXE,7248
|
31
|
+
ai_lls_lib-1.3.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
32
|
+
ai_lls_lib-1.3.0.dist-info/entry_points.txt,sha256=Pi0V_HBViEKGFbNQKatl5lhhnHHBXlxaom-5gH9gXZ0,55
|
33
|
+
ai_lls_lib-1.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|