quantumflow-sdk 0.1.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.
- api/__init__.py +1 -0
- api/auth.py +208 -0
- api/main.py +403 -0
- api/models.py +137 -0
- api/routes/__init__.py +1 -0
- api/routes/auth_routes.py +234 -0
- api/routes/teleport_routes.py +415 -0
- db/__init__.py +15 -0
- db/crud.py +319 -0
- db/database.py +93 -0
- db/models.py +197 -0
- quantumflow/__init__.py +47 -0
- quantumflow/algorithms/__init__.py +48 -0
- quantumflow/algorithms/compression/__init__.py +7 -0
- quantumflow/algorithms/compression/amplitude_amplification.py +189 -0
- quantumflow/algorithms/compression/qft_compression.py +133 -0
- quantumflow/algorithms/compression/token_compression.py +261 -0
- quantumflow/algorithms/cryptography/__init__.py +6 -0
- quantumflow/algorithms/cryptography/qkd.py +205 -0
- quantumflow/algorithms/cryptography/qrng.py +231 -0
- quantumflow/algorithms/machine_learning/__init__.py +7 -0
- quantumflow/algorithms/machine_learning/qnn.py +276 -0
- quantumflow/algorithms/machine_learning/qsvm.py +249 -0
- quantumflow/algorithms/machine_learning/vqe.py +229 -0
- quantumflow/algorithms/optimization/__init__.py +7 -0
- quantumflow/algorithms/optimization/grover.py +223 -0
- quantumflow/algorithms/optimization/qaoa.py +251 -0
- quantumflow/algorithms/optimization/quantum_annealing.py +237 -0
- quantumflow/algorithms/utility/__init__.py +6 -0
- quantumflow/algorithms/utility/circuit_optimizer.py +194 -0
- quantumflow/algorithms/utility/error_correction.py +330 -0
- quantumflow/api/__init__.py +1 -0
- quantumflow/api/routes/__init__.py +4 -0
- quantumflow/api/routes/billing_routes.py +520 -0
- quantumflow/backends/__init__.py +33 -0
- quantumflow/backends/base_backend.py +184 -0
- quantumflow/backends/braket_backend.py +345 -0
- quantumflow/backends/ibm_backend.py +112 -0
- quantumflow/backends/simulator_backend.py +86 -0
- quantumflow/billing/__init__.py +25 -0
- quantumflow/billing/models.py +126 -0
- quantumflow/billing/stripe_service.py +619 -0
- quantumflow/core/__init__.py +12 -0
- quantumflow/core/entanglement.py +164 -0
- quantumflow/core/memory.py +147 -0
- quantumflow/core/quantum_backprop.py +394 -0
- quantumflow/core/quantum_compressor.py +309 -0
- quantumflow/core/teleportation.py +386 -0
- quantumflow/integrations/__init__.py +107 -0
- quantumflow/integrations/autogen_tools.py +501 -0
- quantumflow/integrations/crewai_agents.py +425 -0
- quantumflow/integrations/crewai_tools.py +407 -0
- quantumflow/integrations/langchain_memory.py +385 -0
- quantumflow/integrations/langchain_tools.py +366 -0
- quantumflow/integrations/mcp_server.py +575 -0
- quantumflow_sdk-0.1.0.dist-info/METADATA +190 -0
- quantumflow_sdk-0.1.0.dist-info/RECORD +60 -0
- quantumflow_sdk-0.1.0.dist-info/WHEEL +5 -0
- quantumflow_sdk-0.1.0.dist-info/entry_points.txt +2 -0
- quantumflow_sdk-0.1.0.dist-info/top_level.txt +3 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stripe Billing Service for QuantumFlow.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
- Customer management
|
|
6
|
+
- Subscription lifecycle (create, update, cancel)
|
|
7
|
+
- Usage-based billing (metered API calls)
|
|
8
|
+
- Webhook processing
|
|
9
|
+
- Invoice management
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from enum import Enum
|
|
15
|
+
from typing import Optional
|
|
16
|
+
import stripe
|
|
17
|
+
|
|
18
|
+
from quantumflow.billing.models import (
|
|
19
|
+
Customer,
|
|
20
|
+
Subscription,
|
|
21
|
+
Invoice,
|
|
22
|
+
UsageRecord,
|
|
23
|
+
SubscriptionStatus,
|
|
24
|
+
PaymentStatus,
|
|
25
|
+
PriceInfo,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SubscriptionTier(str, Enum):
|
|
30
|
+
"""Available subscription tiers."""
|
|
31
|
+
FREE = "free"
|
|
32
|
+
PRO = "pro"
|
|
33
|
+
ENTERPRISE = "enterprise"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Tier configuration
|
|
37
|
+
TIER_LIMITS = {
|
|
38
|
+
SubscriptionTier.FREE: {
|
|
39
|
+
"api_calls_monthly": 1_000,
|
|
40
|
+
"max_qubits": 20,
|
|
41
|
+
"backends": ["simulator"],
|
|
42
|
+
"support": "community",
|
|
43
|
+
"rate_limit_per_minute": 10,
|
|
44
|
+
},
|
|
45
|
+
SubscriptionTier.PRO: {
|
|
46
|
+
"api_calls_monthly": 50_000,
|
|
47
|
+
"max_qubits": 100,
|
|
48
|
+
"backends": ["simulator", "ibm", "aws"],
|
|
49
|
+
"support": "email",
|
|
50
|
+
"rate_limit_per_minute": 100,
|
|
51
|
+
},
|
|
52
|
+
SubscriptionTier.ENTERPRISE: {
|
|
53
|
+
"api_calls_monthly": 1_000_000,
|
|
54
|
+
"max_qubits": 156,
|
|
55
|
+
"backends": ["simulator", "ibm", "aws", "dedicated"],
|
|
56
|
+
"support": "24/7",
|
|
57
|
+
"rate_limit_per_minute": 1000,
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# Stripe product IDs (set in environment)
|
|
62
|
+
STRIPE_PRODUCTS = {
|
|
63
|
+
SubscriptionTier.FREE: os.getenv("STRIPE_PRODUCT_FREE", "prod_TsZuC9LFEZGZ6v"),
|
|
64
|
+
SubscriptionTier.PRO: os.getenv("STRIPE_PRODUCT_PRO", "prod_TsZujvgfFU5BFj"),
|
|
65
|
+
SubscriptionTier.ENTERPRISE: os.getenv("STRIPE_PRODUCT_ENTERPRISE", "prod_TsZudz3o7YX9t0"),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Stripe price IDs (set these in environment or Stripe dashboard)
|
|
69
|
+
TIER_PRICES = {
|
|
70
|
+
SubscriptionTier.FREE: PriceInfo(
|
|
71
|
+
tier="free",
|
|
72
|
+
stripe_price_id=os.getenv("STRIPE_PRICE_FREE", ""), # Will be fetched from product
|
|
73
|
+
stripe_product_id=STRIPE_PRODUCTS[SubscriptionTier.FREE],
|
|
74
|
+
amount=0,
|
|
75
|
+
api_calls_included=1_000,
|
|
76
|
+
overage_price_per_call=0.01,
|
|
77
|
+
),
|
|
78
|
+
SubscriptionTier.PRO: PriceInfo(
|
|
79
|
+
tier="pro",
|
|
80
|
+
stripe_price_id=os.getenv("STRIPE_PRICE_PRO", ""), # Will be fetched from product
|
|
81
|
+
stripe_product_id=STRIPE_PRODUCTS[SubscriptionTier.PRO],
|
|
82
|
+
amount=4900, # $49/month
|
|
83
|
+
api_calls_included=50_000,
|
|
84
|
+
overage_price_per_call=0.005,
|
|
85
|
+
),
|
|
86
|
+
SubscriptionTier.ENTERPRISE: PriceInfo(
|
|
87
|
+
tier="enterprise",
|
|
88
|
+
stripe_price_id=os.getenv("STRIPE_PRICE_ENTERPRISE", ""), # Will be fetched from product
|
|
89
|
+
stripe_product_id=STRIPE_PRODUCTS[SubscriptionTier.ENTERPRISE],
|
|
90
|
+
amount=19900, # $199/month
|
|
91
|
+
api_calls_included=1_000_000,
|
|
92
|
+
overage_price_per_call=0.001, # $0.00001 per call
|
|
93
|
+
),
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class StripeService:
|
|
98
|
+
"""Service for managing Stripe billing operations."""
|
|
99
|
+
|
|
100
|
+
def __init__(self, api_key: Optional[str] = None):
|
|
101
|
+
"""
|
|
102
|
+
Initialize Stripe service.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
api_key: Stripe secret key. Defaults to STRIPE_SECRET_KEY env var.
|
|
106
|
+
"""
|
|
107
|
+
self.api_key = api_key or os.getenv("STRIPE_SECRET_KEY")
|
|
108
|
+
if not self.api_key:
|
|
109
|
+
raise ValueError("Stripe API key not configured. Set STRIPE_SECRET_KEY.")
|
|
110
|
+
|
|
111
|
+
stripe.api_key = self.api_key
|
|
112
|
+
self.webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET")
|
|
113
|
+
self._price_cache = {} # Cache for price IDs
|
|
114
|
+
|
|
115
|
+
def get_price_id_for_tier(self, tier: SubscriptionTier) -> str:
|
|
116
|
+
"""Get the Stripe price ID for a tier, fetching from product if needed."""
|
|
117
|
+
price_info = TIER_PRICES[tier]
|
|
118
|
+
|
|
119
|
+
# If price ID is set in env, use it
|
|
120
|
+
if price_info.stripe_price_id:
|
|
121
|
+
return price_info.stripe_price_id
|
|
122
|
+
|
|
123
|
+
# Check cache
|
|
124
|
+
if tier in self._price_cache:
|
|
125
|
+
return self._price_cache[tier]
|
|
126
|
+
|
|
127
|
+
# Fetch from product
|
|
128
|
+
product_id = price_info.stripe_product_id
|
|
129
|
+
if not product_id:
|
|
130
|
+
raise ValueError(f"No product ID configured for tier {tier}")
|
|
131
|
+
|
|
132
|
+
# Get the default price for this product
|
|
133
|
+
prices = stripe.Price.list(product=product_id, active=True, limit=1)
|
|
134
|
+
if not prices.data:
|
|
135
|
+
raise ValueError(f"No active price found for product {product_id}")
|
|
136
|
+
|
|
137
|
+
price_id = prices.data[0].id
|
|
138
|
+
self._price_cache[tier] = price_id
|
|
139
|
+
return price_id
|
|
140
|
+
|
|
141
|
+
# ==================== Customer Management ====================
|
|
142
|
+
|
|
143
|
+
def create_customer(
|
|
144
|
+
self,
|
|
145
|
+
email: str,
|
|
146
|
+
name: Optional[str] = None,
|
|
147
|
+
user_id: Optional[str] = None,
|
|
148
|
+
metadata: Optional[dict] = None,
|
|
149
|
+
) -> Customer:
|
|
150
|
+
"""Create a new Stripe customer."""
|
|
151
|
+
customer_data = {
|
|
152
|
+
"email": email,
|
|
153
|
+
"metadata": metadata or {},
|
|
154
|
+
}
|
|
155
|
+
if name:
|
|
156
|
+
customer_data["name"] = name
|
|
157
|
+
if user_id:
|
|
158
|
+
customer_data["metadata"]["user_id"] = user_id
|
|
159
|
+
|
|
160
|
+
stripe_customer = stripe.Customer.create(**customer_data)
|
|
161
|
+
|
|
162
|
+
return Customer(
|
|
163
|
+
id=user_id or stripe_customer.id,
|
|
164
|
+
email=email,
|
|
165
|
+
name=name,
|
|
166
|
+
stripe_customer_id=stripe_customer.id,
|
|
167
|
+
metadata=stripe_customer.metadata,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def get_customer(self, stripe_customer_id: str) -> Optional[Customer]:
|
|
171
|
+
"""Retrieve a customer from Stripe."""
|
|
172
|
+
try:
|
|
173
|
+
stripe_customer = stripe.Customer.retrieve(stripe_customer_id)
|
|
174
|
+
return Customer(
|
|
175
|
+
id=stripe_customer.metadata.get("user_id", stripe_customer.id),
|
|
176
|
+
email=stripe_customer.email,
|
|
177
|
+
name=stripe_customer.name,
|
|
178
|
+
stripe_customer_id=stripe_customer.id,
|
|
179
|
+
default_payment_method=stripe_customer.invoice_settings.default_payment_method,
|
|
180
|
+
metadata=dict(stripe_customer.metadata),
|
|
181
|
+
)
|
|
182
|
+
except stripe.error.StripeError:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def update_customer(
|
|
186
|
+
self,
|
|
187
|
+
stripe_customer_id: str,
|
|
188
|
+
email: Optional[str] = None,
|
|
189
|
+
name: Optional[str] = None,
|
|
190
|
+
metadata: Optional[dict] = None,
|
|
191
|
+
) -> Customer:
|
|
192
|
+
"""Update customer details."""
|
|
193
|
+
update_data = {}
|
|
194
|
+
if email:
|
|
195
|
+
update_data["email"] = email
|
|
196
|
+
if name:
|
|
197
|
+
update_data["name"] = name
|
|
198
|
+
if metadata:
|
|
199
|
+
update_data["metadata"] = metadata
|
|
200
|
+
|
|
201
|
+
stripe_customer = stripe.Customer.modify(stripe_customer_id, **update_data)
|
|
202
|
+
|
|
203
|
+
return Customer(
|
|
204
|
+
id=stripe_customer.metadata.get("user_id", stripe_customer.id),
|
|
205
|
+
email=stripe_customer.email,
|
|
206
|
+
name=stripe_customer.name,
|
|
207
|
+
stripe_customer_id=stripe_customer.id,
|
|
208
|
+
metadata=dict(stripe_customer.metadata),
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# ==================== Subscription Management ====================
|
|
212
|
+
|
|
213
|
+
def create_subscription(
|
|
214
|
+
self,
|
|
215
|
+
customer_id: str,
|
|
216
|
+
tier: SubscriptionTier,
|
|
217
|
+
trial_days: int = 0,
|
|
218
|
+
payment_method_id: Optional[str] = None,
|
|
219
|
+
) -> Subscription:
|
|
220
|
+
"""
|
|
221
|
+
Create a new subscription for a customer.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
customer_id: Stripe customer ID
|
|
225
|
+
tier: Subscription tier
|
|
226
|
+
trial_days: Number of trial days (0 for no trial)
|
|
227
|
+
payment_method_id: Payment method to use
|
|
228
|
+
"""
|
|
229
|
+
price_id = self.get_price_id_for_tier(tier)
|
|
230
|
+
|
|
231
|
+
subscription_data = {
|
|
232
|
+
"customer": customer_id,
|
|
233
|
+
"items": [{"price": price_id}],
|
|
234
|
+
"metadata": {"tier": tier.value},
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if trial_days > 0:
|
|
238
|
+
subscription_data["trial_period_days"] = trial_days
|
|
239
|
+
|
|
240
|
+
if payment_method_id:
|
|
241
|
+
subscription_data["default_payment_method"] = payment_method_id
|
|
242
|
+
|
|
243
|
+
# For free tier, skip payment requirement
|
|
244
|
+
if tier == SubscriptionTier.FREE:
|
|
245
|
+
subscription_data["payment_behavior"] = "default_incomplete"
|
|
246
|
+
|
|
247
|
+
stripe_sub = stripe.Subscription.create(**subscription_data)
|
|
248
|
+
|
|
249
|
+
return self._stripe_sub_to_model(stripe_sub)
|
|
250
|
+
|
|
251
|
+
def get_subscription(self, stripe_subscription_id: str) -> Optional[Subscription]:
|
|
252
|
+
"""Retrieve subscription details."""
|
|
253
|
+
try:
|
|
254
|
+
stripe_sub = stripe.Subscription.retrieve(stripe_subscription_id)
|
|
255
|
+
return self._stripe_sub_to_model(stripe_sub)
|
|
256
|
+
except stripe.error.StripeError:
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
def update_subscription_tier(
|
|
260
|
+
self,
|
|
261
|
+
stripe_subscription_id: str,
|
|
262
|
+
new_tier: SubscriptionTier,
|
|
263
|
+
prorate: bool = True,
|
|
264
|
+
) -> Subscription:
|
|
265
|
+
"""
|
|
266
|
+
Upgrade or downgrade a subscription.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
stripe_subscription_id: Current subscription ID
|
|
270
|
+
new_tier: New tier to switch to
|
|
271
|
+
prorate: Whether to prorate the change
|
|
272
|
+
"""
|
|
273
|
+
stripe_sub = stripe.Subscription.retrieve(stripe_subscription_id)
|
|
274
|
+
new_price_id = self.get_price_id_for_tier(new_tier)
|
|
275
|
+
|
|
276
|
+
# Update the subscription item
|
|
277
|
+
stripe.Subscription.modify(
|
|
278
|
+
stripe_subscription_id,
|
|
279
|
+
items=[{
|
|
280
|
+
"id": stripe_sub["items"]["data"][0].id,
|
|
281
|
+
"price": new_price_id,
|
|
282
|
+
}],
|
|
283
|
+
proration_behavior="create_prorations" if prorate else "none",
|
|
284
|
+
metadata={"tier": new_tier.value},
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
updated_sub = stripe.Subscription.retrieve(stripe_subscription_id)
|
|
288
|
+
return self._stripe_sub_to_model(updated_sub)
|
|
289
|
+
|
|
290
|
+
def cancel_subscription(
|
|
291
|
+
self,
|
|
292
|
+
stripe_subscription_id: str,
|
|
293
|
+
at_period_end: bool = True,
|
|
294
|
+
) -> Subscription:
|
|
295
|
+
"""
|
|
296
|
+
Cancel a subscription.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
stripe_subscription_id: Subscription to cancel
|
|
300
|
+
at_period_end: If True, cancel at end of billing period
|
|
301
|
+
"""
|
|
302
|
+
if at_period_end:
|
|
303
|
+
stripe_sub = stripe.Subscription.modify(
|
|
304
|
+
stripe_subscription_id,
|
|
305
|
+
cancel_at_period_end=True,
|
|
306
|
+
)
|
|
307
|
+
else:
|
|
308
|
+
stripe_sub = stripe.Subscription.delete(stripe_subscription_id)
|
|
309
|
+
|
|
310
|
+
return self._stripe_sub_to_model(stripe_sub)
|
|
311
|
+
|
|
312
|
+
def reactivate_subscription(self, stripe_subscription_id: str) -> Subscription:
|
|
313
|
+
"""Reactivate a subscription scheduled for cancellation."""
|
|
314
|
+
stripe_sub = stripe.Subscription.modify(
|
|
315
|
+
stripe_subscription_id,
|
|
316
|
+
cancel_at_period_end=False,
|
|
317
|
+
)
|
|
318
|
+
return self._stripe_sub_to_model(stripe_sub)
|
|
319
|
+
|
|
320
|
+
# ==================== Usage-Based Billing ====================
|
|
321
|
+
|
|
322
|
+
def report_usage(
|
|
323
|
+
self,
|
|
324
|
+
subscription_item_id: str,
|
|
325
|
+
quantity: int,
|
|
326
|
+
timestamp: Optional[datetime] = None,
|
|
327
|
+
idempotency_key: Optional[str] = None,
|
|
328
|
+
) -> UsageRecord:
|
|
329
|
+
"""
|
|
330
|
+
Report API usage for metered billing.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
subscription_item_id: The subscription item for metered billing
|
|
334
|
+
quantity: Number of API calls to report
|
|
335
|
+
timestamp: When the usage occurred (defaults to now)
|
|
336
|
+
idempotency_key: Prevent duplicate reporting
|
|
337
|
+
"""
|
|
338
|
+
usage_data = {
|
|
339
|
+
"quantity": quantity,
|
|
340
|
+
"action": "increment",
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if timestamp:
|
|
344
|
+
usage_data["timestamp"] = int(timestamp.timestamp())
|
|
345
|
+
|
|
346
|
+
kwargs = {}
|
|
347
|
+
if idempotency_key:
|
|
348
|
+
kwargs["idempotency_key"] = idempotency_key
|
|
349
|
+
|
|
350
|
+
record = stripe.SubscriptionItem.create_usage_record(
|
|
351
|
+
subscription_item_id,
|
|
352
|
+
**usage_data,
|
|
353
|
+
**kwargs,
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
return UsageRecord(
|
|
357
|
+
id=record.id,
|
|
358
|
+
subscription_id=record.subscription_item,
|
|
359
|
+
quantity=record.quantity,
|
|
360
|
+
timestamp=datetime.fromtimestamp(record.timestamp),
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
def get_usage_summary(
|
|
364
|
+
self,
|
|
365
|
+
subscription_item_id: str,
|
|
366
|
+
) -> dict:
|
|
367
|
+
"""Get usage summary for current billing period."""
|
|
368
|
+
records = stripe.SubscriptionItem.list_usage_record_summaries(
|
|
369
|
+
subscription_item_id,
|
|
370
|
+
limit=1,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if records.data:
|
|
374
|
+
summary = records.data[0]
|
|
375
|
+
return {
|
|
376
|
+
"total_usage": summary.total_usage,
|
|
377
|
+
"period_start": datetime.fromtimestamp(summary.period.start),
|
|
378
|
+
"period_end": datetime.fromtimestamp(summary.period.end),
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return {"total_usage": 0, "period_start": None, "period_end": None}
|
|
382
|
+
|
|
383
|
+
# ==================== Payment Methods ====================
|
|
384
|
+
|
|
385
|
+
def create_setup_intent(self, customer_id: str) -> dict:
|
|
386
|
+
"""Create a SetupIntent for adding a payment method."""
|
|
387
|
+
intent = stripe.SetupIntent.create(
|
|
388
|
+
customer=customer_id,
|
|
389
|
+
payment_method_types=["card"],
|
|
390
|
+
)
|
|
391
|
+
return {
|
|
392
|
+
"client_secret": intent.client_secret,
|
|
393
|
+
"setup_intent_id": intent.id,
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
def attach_payment_method(
|
|
397
|
+
self,
|
|
398
|
+
customer_id: str,
|
|
399
|
+
payment_method_id: str,
|
|
400
|
+
set_default: bool = True,
|
|
401
|
+
) -> str:
|
|
402
|
+
"""Attach a payment method to a customer."""
|
|
403
|
+
stripe.PaymentMethod.attach(payment_method_id, customer=customer_id)
|
|
404
|
+
|
|
405
|
+
if set_default:
|
|
406
|
+
stripe.Customer.modify(
|
|
407
|
+
customer_id,
|
|
408
|
+
invoice_settings={"default_payment_method": payment_method_id},
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
return payment_method_id
|
|
412
|
+
|
|
413
|
+
def list_payment_methods(self, customer_id: str) -> list[dict]:
|
|
414
|
+
"""List customer's payment methods."""
|
|
415
|
+
methods = stripe.PaymentMethod.list(customer=customer_id, type="card")
|
|
416
|
+
return [
|
|
417
|
+
{
|
|
418
|
+
"id": pm.id,
|
|
419
|
+
"brand": pm.card.brand,
|
|
420
|
+
"last4": pm.card.last4,
|
|
421
|
+
"exp_month": pm.card.exp_month,
|
|
422
|
+
"exp_year": pm.card.exp_year,
|
|
423
|
+
}
|
|
424
|
+
for pm in methods.data
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
# ==================== Invoices ====================
|
|
428
|
+
|
|
429
|
+
def list_invoices(
|
|
430
|
+
self,
|
|
431
|
+
customer_id: str,
|
|
432
|
+
limit: int = 10,
|
|
433
|
+
) -> list[Invoice]:
|
|
434
|
+
"""List customer invoices."""
|
|
435
|
+
invoices = stripe.Invoice.list(customer=customer_id, limit=limit)
|
|
436
|
+
return [self._stripe_invoice_to_model(inv) for inv in invoices.data]
|
|
437
|
+
|
|
438
|
+
def get_upcoming_invoice(self, customer_id: str) -> Optional[Invoice]:
|
|
439
|
+
"""Get the upcoming invoice for a customer."""
|
|
440
|
+
try:
|
|
441
|
+
invoice = stripe.Invoice.upcoming(customer=customer_id)
|
|
442
|
+
return self._stripe_invoice_to_model(invoice)
|
|
443
|
+
except stripe.error.InvalidRequestError:
|
|
444
|
+
return None
|
|
445
|
+
|
|
446
|
+
# ==================== Webhooks ====================
|
|
447
|
+
|
|
448
|
+
def construct_webhook_event(
|
|
449
|
+
self,
|
|
450
|
+
payload: bytes,
|
|
451
|
+
signature: str,
|
|
452
|
+
) -> stripe.Event:
|
|
453
|
+
"""
|
|
454
|
+
Verify and construct a webhook event.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
payload: Raw request body
|
|
458
|
+
signature: Stripe-Signature header
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Verified Stripe event
|
|
462
|
+
"""
|
|
463
|
+
if not self.webhook_secret:
|
|
464
|
+
raise ValueError("Webhook secret not configured")
|
|
465
|
+
|
|
466
|
+
return stripe.Webhook.construct_event(
|
|
467
|
+
payload,
|
|
468
|
+
signature,
|
|
469
|
+
self.webhook_secret,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
def handle_webhook_event(self, event: stripe.Event) -> dict:
|
|
473
|
+
"""
|
|
474
|
+
Process a webhook event.
|
|
475
|
+
|
|
476
|
+
Returns action to take based on event type.
|
|
477
|
+
"""
|
|
478
|
+
event_type = event.type
|
|
479
|
+
data = event.data.object
|
|
480
|
+
|
|
481
|
+
handlers = {
|
|
482
|
+
"customer.subscription.created": self._handle_subscription_created,
|
|
483
|
+
"customer.subscription.updated": self._handle_subscription_updated,
|
|
484
|
+
"customer.subscription.deleted": self._handle_subscription_deleted,
|
|
485
|
+
"invoice.paid": self._handle_invoice_paid,
|
|
486
|
+
"invoice.payment_failed": self._handle_payment_failed,
|
|
487
|
+
"customer.created": self._handle_customer_created,
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
handler = handlers.get(event_type)
|
|
491
|
+
if handler:
|
|
492
|
+
return handler(data)
|
|
493
|
+
|
|
494
|
+
return {"action": "ignored", "event_type": event_type}
|
|
495
|
+
|
|
496
|
+
# ==================== Checkout Sessions ====================
|
|
497
|
+
|
|
498
|
+
def create_checkout_session(
|
|
499
|
+
self,
|
|
500
|
+
customer_id: str,
|
|
501
|
+
tier: SubscriptionTier,
|
|
502
|
+
success_url: str,
|
|
503
|
+
cancel_url: str,
|
|
504
|
+
) -> dict:
|
|
505
|
+
"""Create a Stripe Checkout session for subscription."""
|
|
506
|
+
# Get price ID (fetches from product if not in env)
|
|
507
|
+
price_id = self.get_price_id_for_tier(tier)
|
|
508
|
+
|
|
509
|
+
session = stripe.checkout.Session.create(
|
|
510
|
+
customer=customer_id,
|
|
511
|
+
payment_method_types=["card"],
|
|
512
|
+
line_items=[{
|
|
513
|
+
"price": price_id,
|
|
514
|
+
"quantity": 1,
|
|
515
|
+
}],
|
|
516
|
+
mode="subscription",
|
|
517
|
+
success_url=success_url,
|
|
518
|
+
cancel_url=cancel_url,
|
|
519
|
+
metadata={"tier": tier.value},
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
"session_id": session.id,
|
|
524
|
+
"url": session.url,
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
def create_billing_portal_session(
|
|
528
|
+
self,
|
|
529
|
+
customer_id: str,
|
|
530
|
+
return_url: str,
|
|
531
|
+
) -> dict:
|
|
532
|
+
"""Create a Stripe Billing Portal session."""
|
|
533
|
+
session = stripe.billing_portal.Session.create(
|
|
534
|
+
customer=customer_id,
|
|
535
|
+
return_url=return_url,
|
|
536
|
+
)
|
|
537
|
+
return {"url": session.url}
|
|
538
|
+
|
|
539
|
+
# ==================== Helper Methods ====================
|
|
540
|
+
|
|
541
|
+
def _stripe_sub_to_model(self, stripe_sub) -> Subscription:
|
|
542
|
+
"""Convert Stripe subscription to our model."""
|
|
543
|
+
return Subscription(
|
|
544
|
+
id=stripe_sub.metadata.get("user_id", stripe_sub.id),
|
|
545
|
+
customer_id=stripe_sub.customer,
|
|
546
|
+
stripe_subscription_id=stripe_sub.id,
|
|
547
|
+
tier=stripe_sub.metadata.get("tier", "free"),
|
|
548
|
+
status=SubscriptionStatus(stripe_sub.status),
|
|
549
|
+
current_period_start=datetime.fromtimestamp(stripe_sub.current_period_start),
|
|
550
|
+
current_period_end=datetime.fromtimestamp(stripe_sub.current_period_end),
|
|
551
|
+
cancel_at_period_end=stripe_sub.cancel_at_period_end,
|
|
552
|
+
canceled_at=datetime.fromtimestamp(stripe_sub.canceled_at) if stripe_sub.canceled_at else None,
|
|
553
|
+
trial_end=datetime.fromtimestamp(stripe_sub.trial_end) if stripe_sub.trial_end else None,
|
|
554
|
+
metadata=dict(stripe_sub.metadata),
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
def _stripe_invoice_to_model(self, stripe_inv) -> Invoice:
|
|
558
|
+
"""Convert Stripe invoice to our model."""
|
|
559
|
+
return Invoice(
|
|
560
|
+
id=stripe_inv.id,
|
|
561
|
+
customer_id=stripe_inv.customer,
|
|
562
|
+
stripe_invoice_id=stripe_inv.id,
|
|
563
|
+
subscription_id=stripe_inv.subscription,
|
|
564
|
+
amount_due=stripe_inv.amount_due,
|
|
565
|
+
amount_paid=stripe_inv.amount_paid,
|
|
566
|
+
currency=stripe_inv.currency,
|
|
567
|
+
status=PaymentStatus(stripe_inv.status) if stripe_inv.status else PaymentStatus.DRAFT,
|
|
568
|
+
invoice_pdf=stripe_inv.invoice_pdf,
|
|
569
|
+
hosted_invoice_url=stripe_inv.hosted_invoice_url,
|
|
570
|
+
period_start=datetime.fromtimestamp(stripe_inv.period_start) if stripe_inv.period_start else None,
|
|
571
|
+
period_end=datetime.fromtimestamp(stripe_inv.period_end) if stripe_inv.period_end else None,
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
# Webhook handlers
|
|
575
|
+
def _handle_subscription_created(self, data) -> dict:
|
|
576
|
+
return {
|
|
577
|
+
"action": "subscription_created",
|
|
578
|
+
"subscription_id": data.id,
|
|
579
|
+
"customer_id": data.customer,
|
|
580
|
+
"tier": data.metadata.get("tier", "free"),
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
def _handle_subscription_updated(self, data) -> dict:
|
|
584
|
+
return {
|
|
585
|
+
"action": "subscription_updated",
|
|
586
|
+
"subscription_id": data.id,
|
|
587
|
+
"status": data.status,
|
|
588
|
+
"tier": data.metadata.get("tier", "free"),
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
def _handle_subscription_deleted(self, data) -> dict:
|
|
592
|
+
return {
|
|
593
|
+
"action": "subscription_deleted",
|
|
594
|
+
"subscription_id": data.id,
|
|
595
|
+
"customer_id": data.customer,
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
def _handle_invoice_paid(self, data) -> dict:
|
|
599
|
+
return {
|
|
600
|
+
"action": "invoice_paid",
|
|
601
|
+
"invoice_id": data.id,
|
|
602
|
+
"customer_id": data.customer,
|
|
603
|
+
"amount_paid": data.amount_paid,
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
def _handle_payment_failed(self, data) -> dict:
|
|
607
|
+
return {
|
|
608
|
+
"action": "payment_failed",
|
|
609
|
+
"invoice_id": data.id,
|
|
610
|
+
"customer_id": data.customer,
|
|
611
|
+
"attempt_count": data.attempt_count,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
def _handle_customer_created(self, data) -> dict:
|
|
615
|
+
return {
|
|
616
|
+
"action": "customer_created",
|
|
617
|
+
"customer_id": data.id,
|
|
618
|
+
"email": data.email,
|
|
619
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""QuantumFlow Core - Quantum computing primitives and algorithms."""
|
|
2
|
+
|
|
3
|
+
from quantumflow.core.quantum_compressor import QuantumCompressor, CompressedResult
|
|
4
|
+
from quantumflow.core.entanglement import Entangler
|
|
5
|
+
from quantumflow.core.memory import QuantumMemory
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"QuantumCompressor",
|
|
9
|
+
"CompressedResult",
|
|
10
|
+
"Entangler",
|
|
11
|
+
"QuantumMemory",
|
|
12
|
+
]
|