svc-infra 0.1.589__py3-none-any.whl → 0.1.706__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 svc-infra might be problematic. Click here for more details.
- svc_infra/__init__.py +58 -2
- svc_infra/apf_payments/README.md +732 -0
- svc_infra/apf_payments/models.py +133 -42
- svc_infra/apf_payments/provider/__init__.py +4 -0
- svc_infra/apf_payments/provider/aiydan.py +871 -0
- svc_infra/apf_payments/provider/base.py +30 -9
- svc_infra/apf_payments/provider/stripe.py +156 -62
- svc_infra/apf_payments/schemas.py +19 -10
- svc_infra/apf_payments/service.py +211 -68
- svc_infra/apf_payments/settings.py +27 -3
- svc_infra/api/__init__.py +61 -0
- svc_infra/api/fastapi/__init__.py +15 -0
- svc_infra/api/fastapi/admin/__init__.py +3 -0
- svc_infra/api/fastapi/admin/add.py +245 -0
- svc_infra/api/fastapi/apf_payments/router.py +145 -46
- svc_infra/api/fastapi/apf_payments/setup.py +26 -8
- svc_infra/api/fastapi/auth/__init__.py +65 -0
- svc_infra/api/fastapi/auth/_cookies.py +6 -2
- svc_infra/api/fastapi/auth/add.py +27 -14
- svc_infra/api/fastapi/auth/gaurd.py +104 -13
- svc_infra/api/fastapi/auth/mfa/models.py +3 -1
- svc_infra/api/fastapi/auth/mfa/pre_auth.py +10 -6
- svc_infra/api/fastapi/auth/mfa/router.py +15 -8
- svc_infra/api/fastapi/auth/mfa/security.py +1 -2
- svc_infra/api/fastapi/auth/mfa/utils.py +2 -1
- svc_infra/api/fastapi/auth/mfa/verify.py +9 -2
- svc_infra/api/fastapi/auth/policy.py +0 -1
- svc_infra/api/fastapi/auth/providers.py +3 -1
- svc_infra/api/fastapi/auth/routers/apikey_router.py +6 -6
- svc_infra/api/fastapi/auth/routers/oauth_router.py +214 -75
- svc_infra/api/fastapi/auth/routers/session_router.py +67 -0
- svc_infra/api/fastapi/auth/security.py +31 -10
- svc_infra/api/fastapi/auth/sender.py +8 -1
- svc_infra/api/fastapi/auth/settings.py +2 -0
- svc_infra/api/fastapi/auth/state.py +3 -1
- svc_infra/api/fastapi/auth/ws_security.py +275 -0
- svc_infra/api/fastapi/billing/router.py +73 -0
- svc_infra/api/fastapi/billing/setup.py +19 -0
- svc_infra/api/fastapi/cache/add.py +9 -5
- svc_infra/api/fastapi/db/__init__.py +5 -1
- svc_infra/api/fastapi/db/http.py +3 -1
- svc_infra/api/fastapi/db/nosql/__init__.py +39 -1
- svc_infra/api/fastapi/db/nosql/mongo/add.py +47 -32
- svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +30 -11
- svc_infra/api/fastapi/db/sql/__init__.py +5 -1
- svc_infra/api/fastapi/db/sql/add.py +71 -26
- svc_infra/api/fastapi/db/sql/crud_router.py +210 -22
- svc_infra/api/fastapi/db/sql/health.py +3 -1
- svc_infra/api/fastapi/db/sql/session.py +18 -0
- svc_infra/api/fastapi/db/sql/users.py +29 -5
- svc_infra/api/fastapi/dependencies/ratelimit.py +130 -0
- svc_infra/api/fastapi/docs/add.py +173 -0
- svc_infra/api/fastapi/docs/landing.py +4 -2
- svc_infra/api/fastapi/docs/scoped.py +62 -15
- svc_infra/api/fastapi/dual/__init__.py +12 -2
- svc_infra/api/fastapi/dual/dualize.py +1 -1
- svc_infra/api/fastapi/dual/protected.py +126 -4
- svc_infra/api/fastapi/dual/public.py +25 -0
- svc_infra/api/fastapi/dual/router.py +40 -13
- svc_infra/api/fastapi/dx.py +33 -2
- svc_infra/api/fastapi/ease.py +10 -2
- svc_infra/api/fastapi/http/concurrency.py +2 -1
- svc_infra/api/fastapi/http/conditional.py +3 -1
- svc_infra/api/fastapi/middleware/debug.py +4 -1
- svc_infra/api/fastapi/middleware/errors/catchall.py +6 -2
- svc_infra/api/fastapi/middleware/errors/exceptions.py +1 -1
- svc_infra/api/fastapi/middleware/errors/handlers.py +54 -8
- svc_infra/api/fastapi/middleware/graceful_shutdown.py +104 -0
- svc_infra/api/fastapi/middleware/idempotency.py +197 -70
- svc_infra/api/fastapi/middleware/idempotency_store.py +187 -0
- svc_infra/api/fastapi/middleware/optimistic_lock.py +42 -0
- svc_infra/api/fastapi/middleware/ratelimit.py +143 -31
- svc_infra/api/fastapi/middleware/ratelimit_store.py +111 -0
- svc_infra/api/fastapi/middleware/request_id.py +27 -11
- svc_infra/api/fastapi/middleware/request_size_limit.py +36 -0
- svc_infra/api/fastapi/middleware/timeout.py +177 -0
- svc_infra/api/fastapi/openapi/apply.py +5 -3
- svc_infra/api/fastapi/openapi/conventions.py +9 -2
- svc_infra/api/fastapi/openapi/mutators.py +165 -20
- svc_infra/api/fastapi/openapi/pipeline.py +1 -1
- svc_infra/api/fastapi/openapi/security.py +3 -1
- svc_infra/api/fastapi/ops/add.py +75 -0
- svc_infra/api/fastapi/pagination.py +47 -20
- svc_infra/api/fastapi/routers/__init__.py +43 -15
- svc_infra/api/fastapi/routers/ping.py +1 -0
- svc_infra/api/fastapi/setup.py +188 -56
- svc_infra/api/fastapi/tenancy/add.py +19 -0
- svc_infra/api/fastapi/tenancy/context.py +112 -0
- svc_infra/api/fastapi/versioned.py +101 -0
- svc_infra/app/README.md +5 -5
- svc_infra/app/__init__.py +3 -1
- svc_infra/app/env.py +69 -1
- svc_infra/app/logging/add.py +9 -2
- svc_infra/app/logging/formats.py +12 -5
- svc_infra/billing/__init__.py +23 -0
- svc_infra/billing/async_service.py +147 -0
- svc_infra/billing/jobs.py +241 -0
- svc_infra/billing/models.py +177 -0
- svc_infra/billing/quotas.py +103 -0
- svc_infra/billing/schemas.py +36 -0
- svc_infra/billing/service.py +123 -0
- svc_infra/bundled_docs/README.md +5 -0
- svc_infra/bundled_docs/__init__.py +1 -0
- svc_infra/bundled_docs/getting-started.md +6 -0
- svc_infra/cache/__init__.py +9 -0
- svc_infra/cache/add.py +170 -0
- svc_infra/cache/backend.py +7 -6
- svc_infra/cache/decorators.py +81 -15
- svc_infra/cache/demo.py +2 -2
- svc_infra/cache/keys.py +24 -4
- svc_infra/cache/recache.py +26 -14
- svc_infra/cache/resources.py +14 -5
- svc_infra/cache/tags.py +19 -44
- svc_infra/cache/utils.py +3 -1
- svc_infra/cli/__init__.py +52 -8
- svc_infra/cli/__main__.py +4 -0
- svc_infra/cli/cmds/__init__.py +39 -2
- svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +7 -4
- svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +7 -5
- svc_infra/cli/cmds/db/ops_cmds.py +270 -0
- svc_infra/cli/cmds/db/sql/alembic_cmds.py +103 -18
- svc_infra/cli/cmds/db/sql/sql_export_cmds.py +88 -0
- svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +3 -3
- svc_infra/cli/cmds/docs/docs_cmds.py +142 -0
- svc_infra/cli/cmds/dx/__init__.py +12 -0
- svc_infra/cli/cmds/dx/dx_cmds.py +116 -0
- svc_infra/cli/cmds/health/__init__.py +179 -0
- svc_infra/cli/cmds/health/health_cmds.py +8 -0
- svc_infra/cli/cmds/help.py +4 -0
- svc_infra/cli/cmds/jobs/__init__.py +1 -0
- svc_infra/cli/cmds/jobs/jobs_cmds.py +47 -0
- svc_infra/cli/cmds/obs/obs_cmds.py +36 -15
- svc_infra/cli/cmds/sdk/__init__.py +0 -0
- svc_infra/cli/cmds/sdk/sdk_cmds.py +112 -0
- svc_infra/cli/foundation/runner.py +6 -2
- svc_infra/data/add.py +61 -0
- svc_infra/data/backup.py +58 -0
- svc_infra/data/erasure.py +45 -0
- svc_infra/data/fixtures.py +42 -0
- svc_infra/data/retention.py +61 -0
- svc_infra/db/__init__.py +15 -0
- svc_infra/db/crud_schema.py +9 -9
- svc_infra/db/inbox.py +67 -0
- svc_infra/db/nosql/__init__.py +3 -0
- svc_infra/db/nosql/core.py +30 -9
- svc_infra/db/nosql/indexes.py +3 -1
- svc_infra/db/nosql/management.py +1 -1
- svc_infra/db/nosql/mongo/README.md +13 -13
- svc_infra/db/nosql/mongo/client.py +19 -2
- svc_infra/db/nosql/mongo/settings.py +6 -2
- svc_infra/db/nosql/repository.py +35 -15
- svc_infra/db/nosql/resource.py +20 -3
- svc_infra/db/nosql/scaffold.py +9 -3
- svc_infra/db/nosql/service.py +3 -1
- svc_infra/db/nosql/types.py +6 -2
- svc_infra/db/ops.py +384 -0
- svc_infra/db/outbox.py +108 -0
- svc_infra/db/sql/apikey.py +37 -9
- svc_infra/db/sql/authref.py +9 -3
- svc_infra/db/sql/constants.py +12 -8
- svc_infra/db/sql/core.py +2 -2
- svc_infra/db/sql/management.py +11 -8
- svc_infra/db/sql/repository.py +99 -26
- svc_infra/db/sql/resource.py +5 -0
- svc_infra/db/sql/scaffold.py +6 -2
- svc_infra/db/sql/service.py +15 -5
- svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
- svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +1 -1
- svc_infra/db/sql/templates/setup/env_async.py.tmpl +34 -12
- svc_infra/db/sql/templates/setup/env_sync.py.tmpl +29 -7
- svc_infra/db/sql/tenant.py +88 -0
- svc_infra/db/sql/uniq_hooks.py +9 -3
- svc_infra/db/sql/utils.py +138 -51
- svc_infra/db/sql/versioning.py +14 -0
- svc_infra/deploy/__init__.py +538 -0
- svc_infra/documents/__init__.py +100 -0
- svc_infra/documents/add.py +264 -0
- svc_infra/documents/ease.py +233 -0
- svc_infra/documents/models.py +114 -0
- svc_infra/documents/storage.py +264 -0
- svc_infra/dx/add.py +65 -0
- svc_infra/dx/changelog.py +74 -0
- svc_infra/dx/checks.py +68 -0
- svc_infra/exceptions.py +141 -0
- svc_infra/health/__init__.py +864 -0
- svc_infra/http/__init__.py +13 -0
- svc_infra/http/client.py +105 -0
- svc_infra/jobs/builtins/outbox_processor.py +40 -0
- svc_infra/jobs/builtins/webhook_delivery.py +95 -0
- svc_infra/jobs/easy.py +33 -0
- svc_infra/jobs/loader.py +50 -0
- svc_infra/jobs/queue.py +116 -0
- svc_infra/jobs/redis_queue.py +256 -0
- svc_infra/jobs/runner.py +79 -0
- svc_infra/jobs/scheduler.py +53 -0
- svc_infra/jobs/worker.py +40 -0
- svc_infra/loaders/__init__.py +186 -0
- svc_infra/loaders/base.py +142 -0
- svc_infra/loaders/github.py +311 -0
- svc_infra/loaders/models.py +147 -0
- svc_infra/loaders/url.py +235 -0
- svc_infra/logging/__init__.py +374 -0
- svc_infra/mcp/svc_infra_mcp.py +91 -33
- svc_infra/obs/README.md +2 -0
- svc_infra/obs/add.py +65 -9
- svc_infra/obs/cloud_dash.py +2 -1
- svc_infra/obs/grafana/dashboards/http-overview.json +45 -0
- svc_infra/obs/metrics/__init__.py +52 -0
- svc_infra/obs/metrics/asgi.py +13 -7
- svc_infra/obs/metrics/http.py +9 -5
- svc_infra/obs/metrics/sqlalchemy.py +13 -9
- svc_infra/obs/metrics.py +53 -0
- svc_infra/obs/settings.py +6 -2
- svc_infra/security/add.py +217 -0
- svc_infra/security/audit.py +212 -0
- svc_infra/security/audit_service.py +74 -0
- svc_infra/security/headers.py +52 -0
- svc_infra/security/hibp.py +101 -0
- svc_infra/security/jwt_rotation.py +105 -0
- svc_infra/security/lockout.py +102 -0
- svc_infra/security/models.py +287 -0
- svc_infra/security/oauth_models.py +73 -0
- svc_infra/security/org_invites.py +130 -0
- svc_infra/security/passwords.py +79 -0
- svc_infra/security/permissions.py +171 -0
- svc_infra/security/session.py +98 -0
- svc_infra/security/signed_cookies.py +100 -0
- svc_infra/storage/__init__.py +93 -0
- svc_infra/storage/add.py +253 -0
- svc_infra/storage/backends/__init__.py +11 -0
- svc_infra/storage/backends/local.py +339 -0
- svc_infra/storage/backends/memory.py +216 -0
- svc_infra/storage/backends/s3.py +353 -0
- svc_infra/storage/base.py +239 -0
- svc_infra/storage/easy.py +185 -0
- svc_infra/storage/settings.py +195 -0
- svc_infra/testing/__init__.py +685 -0
- svc_infra/utils.py +7 -3
- svc_infra/webhooks/__init__.py +69 -0
- svc_infra/webhooks/add.py +339 -0
- svc_infra/webhooks/encryption.py +115 -0
- svc_infra/webhooks/fastapi.py +39 -0
- svc_infra/webhooks/router.py +55 -0
- svc_infra/webhooks/service.py +70 -0
- svc_infra/webhooks/signing.py +34 -0
- svc_infra/websocket/__init__.py +79 -0
- svc_infra/websocket/add.py +140 -0
- svc_infra/websocket/client.py +282 -0
- svc_infra/websocket/config.py +69 -0
- svc_infra/websocket/easy.py +76 -0
- svc_infra/websocket/exceptions.py +61 -0
- svc_infra/websocket/manager.py +344 -0
- svc_infra/websocket/models.py +49 -0
- svc_infra-0.1.706.dist-info/LICENSE +21 -0
- svc_infra-0.1.706.dist-info/METADATA +356 -0
- svc_infra-0.1.706.dist-info/RECORD +357 -0
- svc_infra-0.1.589.dist-info/METADATA +0 -79
- svc_infra-0.1.589.dist-info/RECORD +0 -234
- {svc_infra-0.1.589.dist-info → svc_infra-0.1.706.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.589.dist-info → svc_infra-0.1.706.dist-info}/entry_points.txt +0 -0
|
@@ -42,10 +42,14 @@ class ProviderAdapter(Protocol):
|
|
|
42
42
|
async def ensure_customer(self, data: CustomerUpsertIn) -> CustomerOut:
|
|
43
43
|
pass
|
|
44
44
|
|
|
45
|
-
async def attach_payment_method(
|
|
45
|
+
async def attach_payment_method(
|
|
46
|
+
self, data: PaymentMethodAttachIn
|
|
47
|
+
) -> PaymentMethodOut:
|
|
46
48
|
pass
|
|
47
49
|
|
|
48
|
-
async def list_payment_methods(
|
|
50
|
+
async def list_payment_methods(
|
|
51
|
+
self, provider_customer_id: str
|
|
52
|
+
) -> list[PaymentMethodOut]:
|
|
49
53
|
pass
|
|
50
54
|
|
|
51
55
|
async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
@@ -87,7 +91,9 @@ class ProviderAdapter(Protocol):
|
|
|
87
91
|
async def pay_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
88
92
|
pass
|
|
89
93
|
|
|
90
|
-
async def create_intent(
|
|
94
|
+
async def create_intent(
|
|
95
|
+
self, data: IntentCreateIn, *, user_id: str | None
|
|
96
|
+
) -> IntentOut:
|
|
91
97
|
pass
|
|
92
98
|
|
|
93
99
|
async def confirm_intent(self, provider_intent_id: str) -> IntentOut:
|
|
@@ -107,7 +113,9 @@ class ProviderAdapter(Protocol):
|
|
|
107
113
|
) -> dict[str, Any]:
|
|
108
114
|
pass
|
|
109
115
|
|
|
110
|
-
async def capture_intent(
|
|
116
|
+
async def capture_intent(
|
|
117
|
+
self, provider_intent_id: str, *, amount: int | None
|
|
118
|
+
) -> IntentOut:
|
|
111
119
|
pass
|
|
112
120
|
|
|
113
121
|
async def list_intents(
|
|
@@ -150,7 +158,9 @@ class ProviderAdapter(Protocol):
|
|
|
150
158
|
async def create_setup_intent(self, data: SetupIntentCreateIn) -> SetupIntentOut:
|
|
151
159
|
pass
|
|
152
160
|
|
|
153
|
-
async def confirm_setup_intent(
|
|
161
|
+
async def confirm_setup_intent(
|
|
162
|
+
self, provider_setup_intent_id: str
|
|
163
|
+
) -> SetupIntentOut:
|
|
154
164
|
pass
|
|
155
165
|
|
|
156
166
|
async def get_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
@@ -169,7 +179,9 @@ class ProviderAdapter(Protocol):
|
|
|
169
179
|
async def get_dispute(self, provider_dispute_id: str) -> DisputeOut:
|
|
170
180
|
pass
|
|
171
181
|
|
|
172
|
-
async def submit_dispute_evidence(
|
|
182
|
+
async def submit_dispute_evidence(
|
|
183
|
+
self, provider_dispute_id: str, evidence: dict
|
|
184
|
+
) -> DisputeOut:
|
|
173
185
|
pass
|
|
174
186
|
|
|
175
187
|
# --- Balance & Payouts ---
|
|
@@ -186,7 +198,12 @@ class ProviderAdapter(Protocol):
|
|
|
186
198
|
|
|
187
199
|
# --- Customers ---
|
|
188
200
|
async def list_customers(
|
|
189
|
-
self,
|
|
201
|
+
self,
|
|
202
|
+
*,
|
|
203
|
+
provider: str | None,
|
|
204
|
+
user_id: str | None,
|
|
205
|
+
limit: int,
|
|
206
|
+
cursor: str | None,
|
|
190
207
|
) -> tuple[list[CustomerOut], str | None]:
|
|
191
208
|
"""Optional: if not implemented, the service will list from local DB."""
|
|
192
209
|
pass
|
|
@@ -203,7 +220,9 @@ class ProviderAdapter(Protocol):
|
|
|
203
220
|
) -> tuple[list[ProductOut], str | None]:
|
|
204
221
|
pass
|
|
205
222
|
|
|
206
|
-
async def update_product(
|
|
223
|
+
async def update_product(
|
|
224
|
+
self, provider_product_id: str, data: ProductUpdateIn
|
|
225
|
+
) -> ProductOut:
|
|
207
226
|
pass
|
|
208
227
|
|
|
209
228
|
async def get_price(self, provider_price_id: str) -> PriceOut:
|
|
@@ -219,7 +238,9 @@ class ProviderAdapter(Protocol):
|
|
|
219
238
|
) -> tuple[list[PriceOut], str | None]:
|
|
220
239
|
pass
|
|
221
240
|
|
|
222
|
-
async def update_price(
|
|
241
|
+
async def update_price(
|
|
242
|
+
self, provider_price_id: str, data: PriceUpdateIn
|
|
243
|
+
) -> PriceOut:
|
|
223
244
|
pass
|
|
224
245
|
|
|
225
246
|
# --- Subscriptions ---
|
|
@@ -44,7 +44,7 @@ from .base import ProviderAdapter
|
|
|
44
44
|
try:
|
|
45
45
|
import stripe
|
|
46
46
|
except Exception: # pragma: no cover
|
|
47
|
-
stripe = None # type: ignore
|
|
47
|
+
stripe = None # type: ignore[assignment]
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
async def _acall(fn, /, *args, **kwargs):
|
|
@@ -60,7 +60,9 @@ def _pi_to_out(pi) -> IntentOut:
|
|
|
60
60
|
amount=int(pi.amount),
|
|
61
61
|
currency=str(pi.currency).upper(),
|
|
62
62
|
client_secret=getattr(pi, "client_secret", None),
|
|
63
|
-
next_action=NextAction(
|
|
63
|
+
next_action=NextAction(
|
|
64
|
+
type=getattr(getattr(pi, "next_action", None), "type", None)
|
|
65
|
+
),
|
|
64
66
|
)
|
|
65
67
|
|
|
66
68
|
|
|
@@ -134,7 +136,9 @@ def _sub_to_out(s) -> SubscriptionOut:
|
|
|
134
136
|
quantity=int(qty or 0),
|
|
135
137
|
cancel_at_period_end=bool(s.cancel_at_period_end),
|
|
136
138
|
current_period_end=(
|
|
137
|
-
str(s.current_period_end)
|
|
139
|
+
str(s.current_period_end)
|
|
140
|
+
if getattr(s, "current_period_end", None)
|
|
141
|
+
else None
|
|
138
142
|
),
|
|
139
143
|
)
|
|
140
144
|
|
|
@@ -163,7 +167,9 @@ def _dispute_to_out(d) -> DisputeOut:
|
|
|
163
167
|
reason=getattr(d, "reason", None),
|
|
164
168
|
status=d.status,
|
|
165
169
|
evidence_due_by=(
|
|
166
|
-
str(d.evidence_details.get("due_by"))
|
|
170
|
+
str(d.evidence_details.get("due_by"))
|
|
171
|
+
if getattr(d, "evidence_details", None)
|
|
172
|
+
else None
|
|
167
173
|
),
|
|
168
174
|
created_at=str(d.created) if getattr(d, "created", None) else None,
|
|
169
175
|
)
|
|
@@ -193,7 +199,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
193
199
|
raise RuntimeError("stripe SDK is not installed. pip install stripe")
|
|
194
200
|
stripe.api_key = st.stripe.secret_key.get_secret_value()
|
|
195
201
|
self._wh_secret = (
|
|
196
|
-
st.stripe.webhook_secret.get_secret_value()
|
|
202
|
+
st.stripe.webhook_secret.get_secret_value()
|
|
203
|
+
if st.stripe.webhook_secret
|
|
204
|
+
else None
|
|
197
205
|
)
|
|
198
206
|
|
|
199
207
|
# -------- Customers --------
|
|
@@ -235,9 +243,14 @@ class StripeAdapter(ProviderAdapter):
|
|
|
235
243
|
)
|
|
236
244
|
|
|
237
245
|
async def list_customers(
|
|
238
|
-
self,
|
|
246
|
+
self,
|
|
247
|
+
*,
|
|
248
|
+
provider: str | None,
|
|
249
|
+
user_id: str | None,
|
|
250
|
+
limit: int,
|
|
251
|
+
cursor: str | None,
|
|
239
252
|
) -> tuple[list[CustomerOut], str | None]:
|
|
240
|
-
params = {"limit": int(limit)}
|
|
253
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
241
254
|
if cursor:
|
|
242
255
|
params["starting_after"] = cursor
|
|
243
256
|
# Stripe has no direct filter for our custom user_id; many teams store mapping in DB.
|
|
@@ -253,11 +266,15 @@ class StripeAdapter(ProviderAdapter):
|
|
|
253
266
|
)
|
|
254
267
|
for c in res.data
|
|
255
268
|
]
|
|
256
|
-
next_cursor =
|
|
269
|
+
next_cursor = (
|
|
270
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
271
|
+
)
|
|
257
272
|
return items, next_cursor
|
|
258
273
|
|
|
259
274
|
# -------- Payment Methods --------
|
|
260
|
-
async def attach_payment_method(
|
|
275
|
+
async def attach_payment_method(
|
|
276
|
+
self, data: PaymentMethodAttachIn
|
|
277
|
+
) -> PaymentMethodOut:
|
|
261
278
|
pm = await _acall(
|
|
262
279
|
stripe.PaymentMethod.attach,
|
|
263
280
|
data.payment_method_token,
|
|
@@ -271,23 +288,35 @@ class StripeAdapter(ProviderAdapter):
|
|
|
271
288
|
invoice_settings={"default_payment_method": pm.id},
|
|
272
289
|
)
|
|
273
290
|
is_default = (
|
|
274
|
-
getattr(
|
|
291
|
+
getattr(
|
|
292
|
+
getattr(cust, "invoice_settings", None),
|
|
293
|
+
"default_payment_method",
|
|
294
|
+
None,
|
|
295
|
+
)
|
|
275
296
|
== pm.id
|
|
276
297
|
)
|
|
277
298
|
else:
|
|
278
299
|
cust = await _acall(stripe.Customer.retrieve, data.customer_provider_id)
|
|
279
300
|
is_default = (
|
|
280
|
-
getattr(
|
|
301
|
+
getattr(
|
|
302
|
+
getattr(cust, "invoice_settings", None),
|
|
303
|
+
"default_payment_method",
|
|
304
|
+
None,
|
|
305
|
+
)
|
|
281
306
|
== pm.id
|
|
282
307
|
)
|
|
283
308
|
return _pm_to_out(pm, is_default=is_default)
|
|
284
309
|
|
|
285
|
-
async def list_payment_methods(
|
|
310
|
+
async def list_payment_methods(
|
|
311
|
+
self, provider_customer_id: str
|
|
312
|
+
) -> list[PaymentMethodOut]:
|
|
286
313
|
cust = await _acall(stripe.Customer.retrieve, provider_customer_id)
|
|
287
314
|
default_pm = getattr(
|
|
288
315
|
getattr(cust, "invoice_settings", None), "default_payment_method", None
|
|
289
316
|
)
|
|
290
|
-
res = await _acall(
|
|
317
|
+
res = await _acall(
|
|
318
|
+
stripe.PaymentMethod.list, customer=provider_customer_id, type="card"
|
|
319
|
+
)
|
|
291
320
|
return [_pm_to_out(pm, is_default=(pm.id == default_pm)) for pm in res.data]
|
|
292
321
|
|
|
293
322
|
async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
@@ -312,7 +341,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
312
341
|
)
|
|
313
342
|
pm = await _acall(stripe.PaymentMethod.retrieve, provider_method_id)
|
|
314
343
|
is_default = (
|
|
315
|
-
getattr(
|
|
344
|
+
getattr(
|
|
345
|
+
getattr(cust, "invoice_settings", None), "default_payment_method", None
|
|
346
|
+
)
|
|
316
347
|
== pm.id
|
|
317
348
|
)
|
|
318
349
|
return _pm_to_out(pm, is_default=is_default)
|
|
@@ -356,7 +387,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
356
387
|
|
|
357
388
|
# -------- Products / Prices --------
|
|
358
389
|
async def create_product(self, data: ProductCreateIn) -> ProductOut:
|
|
359
|
-
p = await _acall(
|
|
390
|
+
p = await _acall(
|
|
391
|
+
stripe.Product.create, name=data.name, active=bool(data.active)
|
|
392
|
+
)
|
|
360
393
|
return _product_to_out(p)
|
|
361
394
|
|
|
362
395
|
async def get_product(self, provider_product_id: str) -> ProductOut:
|
|
@@ -366,17 +399,21 @@ class StripeAdapter(ProviderAdapter):
|
|
|
366
399
|
async def list_products(
|
|
367
400
|
self, *, active: bool | None, limit: int, cursor: str | None
|
|
368
401
|
) -> tuple[list[ProductOut], str | None]:
|
|
369
|
-
params = {"limit": int(limit)}
|
|
402
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
370
403
|
if active is not None:
|
|
371
404
|
params["active"] = bool(active)
|
|
372
405
|
if cursor:
|
|
373
406
|
params["starting_after"] = cursor
|
|
374
407
|
res = await _acall(stripe.Product.list, **params)
|
|
375
408
|
items = [_product_to_out(p) for p in res.data]
|
|
376
|
-
next_cursor =
|
|
409
|
+
next_cursor = (
|
|
410
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
411
|
+
)
|
|
377
412
|
return items, next_cursor
|
|
378
413
|
|
|
379
|
-
async def update_product(
|
|
414
|
+
async def update_product(
|
|
415
|
+
self, provider_product_id: str, data: ProductUpdateIn
|
|
416
|
+
) -> ProductOut:
|
|
380
417
|
update: dict[str, Any] = {}
|
|
381
418
|
if data.name is not None:
|
|
382
419
|
update["name"] = data.name
|
|
@@ -415,7 +452,7 @@ class StripeAdapter(ProviderAdapter):
|
|
|
415
452
|
limit: int,
|
|
416
453
|
cursor: str | None,
|
|
417
454
|
) -> tuple[list[PriceOut], str | None]:
|
|
418
|
-
params = {"limit": int(limit)}
|
|
455
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
419
456
|
if provider_product_id:
|
|
420
457
|
params["product"] = provider_product_id
|
|
421
458
|
if active is not None:
|
|
@@ -424,10 +461,14 @@ class StripeAdapter(ProviderAdapter):
|
|
|
424
461
|
params["starting_after"] = cursor
|
|
425
462
|
res = await _acall(stripe.Price.list, **params)
|
|
426
463
|
items = [_price_to_out(p) for p in res.data]
|
|
427
|
-
next_cursor =
|
|
464
|
+
next_cursor = (
|
|
465
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
466
|
+
)
|
|
428
467
|
return items, next_cursor
|
|
429
468
|
|
|
430
|
-
async def update_price(
|
|
469
|
+
async def update_price(
|
|
470
|
+
self, provider_price_id: str, data: PriceUpdateIn
|
|
471
|
+
) -> PriceOut:
|
|
431
472
|
# Stripe allows toggling `active` and updating metadata, but not amount/currency/product.
|
|
432
473
|
update: dict[str, Any] = {}
|
|
433
474
|
if data.active is not None:
|
|
@@ -454,7 +495,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
454
495
|
async def update_subscription(
|
|
455
496
|
self, provider_subscription_id: str, data: SubscriptionUpdateIn
|
|
456
497
|
) -> SubscriptionOut:
|
|
457
|
-
s = await _acall(
|
|
498
|
+
s = await _acall(
|
|
499
|
+
stripe.Subscription.retrieve, provider_subscription_id, expand=["items"]
|
|
500
|
+
)
|
|
458
501
|
items = s.items.data
|
|
459
502
|
update_kwargs: dict[str, Any] = {"proration_behavior": data.proration_behavior}
|
|
460
503
|
# update first item (simple plan model)
|
|
@@ -468,21 +511,27 @@ class StripeAdapter(ProviderAdapter):
|
|
|
468
511
|
update_kwargs["items"] = [item_update]
|
|
469
512
|
if data.cancel_at_period_end is not None:
|
|
470
513
|
update_kwargs["cancel_at_period_end"] = bool(data.cancel_at_period_end)
|
|
471
|
-
s2 = await _acall(
|
|
514
|
+
s2 = await _acall(
|
|
515
|
+
stripe.Subscription.modify, provider_subscription_id, **update_kwargs
|
|
516
|
+
)
|
|
472
517
|
return _sub_to_out(s2)
|
|
473
518
|
|
|
474
519
|
async def cancel_subscription(
|
|
475
520
|
self, provider_subscription_id: str, at_period_end: bool = True
|
|
476
521
|
) -> SubscriptionOut:
|
|
477
522
|
s = await _acall(
|
|
478
|
-
stripe.Subscription.cancel
|
|
523
|
+
stripe.Subscription.cancel
|
|
524
|
+
if not at_period_end
|
|
525
|
+
else stripe.Subscription.modify,
|
|
479
526
|
provider_subscription_id,
|
|
480
527
|
**({} if not at_period_end else {"cancel_at_period_end": True}),
|
|
481
528
|
)
|
|
482
529
|
return _sub_to_out(s)
|
|
483
530
|
|
|
484
531
|
async def get_subscription(self, provider_subscription_id: str) -> SubscriptionOut:
|
|
485
|
-
s = await _acall(
|
|
532
|
+
s = await _acall(
|
|
533
|
+
stripe.Subscription.retrieve, provider_subscription_id, expand=["items"]
|
|
534
|
+
)
|
|
486
535
|
return _sub_to_out(s)
|
|
487
536
|
|
|
488
537
|
async def list_subscriptions(
|
|
@@ -493,7 +542,7 @@ class StripeAdapter(ProviderAdapter):
|
|
|
493
542
|
limit: int,
|
|
494
543
|
cursor: str | None,
|
|
495
544
|
) -> tuple[list[SubscriptionOut], str | None]:
|
|
496
|
-
params = {"limit": int(limit)}
|
|
545
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
497
546
|
if customer_provider_id:
|
|
498
547
|
params["customer"] = customer_provider_id
|
|
499
548
|
if status:
|
|
@@ -502,7 +551,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
502
551
|
params["starting_after"] = cursor
|
|
503
552
|
res = await _acall(stripe.Subscription.list, **params)
|
|
504
553
|
items = [_sub_to_out(s) for s in res.data]
|
|
505
|
-
next_cursor =
|
|
554
|
+
next_cursor = (
|
|
555
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
556
|
+
)
|
|
506
557
|
return items, next_cursor
|
|
507
558
|
|
|
508
559
|
# -------- Invoices --------
|
|
@@ -542,7 +593,8 @@ class StripeAdapter(ProviderAdapter):
|
|
|
542
593
|
else:
|
|
543
594
|
kwargs["unit_amount"] = int(data.unit_amount)
|
|
544
595
|
await _acall(
|
|
545
|
-
stripe.InvoiceItem.create,
|
|
596
|
+
stripe.InvoiceItem.create,
|
|
597
|
+
**{k: v for k, v in kwargs.items() if v is not None},
|
|
546
598
|
)
|
|
547
599
|
inv = await _acall(stripe.Invoice.retrieve, provider_invoice_id)
|
|
548
600
|
return _inv_to_out(inv)
|
|
@@ -555,7 +607,7 @@ class StripeAdapter(ProviderAdapter):
|
|
|
555
607
|
limit: int,
|
|
556
608
|
cursor: str | None,
|
|
557
609
|
) -> tuple[list[InvoiceOut], str | None]:
|
|
558
|
-
params = {"limit": int(limit)}
|
|
610
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
559
611
|
if customer_provider_id:
|
|
560
612
|
params["customer"] = customer_provider_id
|
|
561
613
|
if status:
|
|
@@ -564,7 +616,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
564
616
|
params["starting_after"] = cursor
|
|
565
617
|
res = await _acall(stripe.Invoice.list, **params)
|
|
566
618
|
items = [_inv_to_out(inv) for inv in res.data]
|
|
567
|
-
next_cursor =
|
|
619
|
+
next_cursor = (
|
|
620
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
621
|
+
)
|
|
568
622
|
return items, next_cursor
|
|
569
623
|
|
|
570
624
|
async def get_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
@@ -577,13 +631,13 @@ class StripeAdapter(ProviderAdapter):
|
|
|
577
631
|
params = {"customer": customer_provider_id}
|
|
578
632
|
if subscription_id:
|
|
579
633
|
params["subscription"] = subscription_id
|
|
580
|
-
inv = await _acall(stripe.Invoice.upcoming, **params)
|
|
634
|
+
inv = await _acall(stripe.Invoice.upcoming, **params) # type: ignore[attr-defined]
|
|
581
635
|
return _inv_to_out(inv)
|
|
582
636
|
|
|
583
637
|
async def list_invoice_line_items(
|
|
584
638
|
self, provider_invoice_id: str, *, limit: int, cursor: str | None
|
|
585
639
|
) -> tuple[list[InvoiceLineItemOut], str | None]:
|
|
586
|
-
params = {"limit": int(limit)}
|
|
640
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
587
641
|
if cursor:
|
|
588
642
|
params["starting_after"] = cursor
|
|
589
643
|
res = await _acall(stripe.Invoice.list_lines, provider_invoice_id, **params)
|
|
@@ -602,22 +656,29 @@ class StripeAdapter(ProviderAdapter):
|
|
|
602
656
|
provider_price_id=price_id,
|
|
603
657
|
)
|
|
604
658
|
)
|
|
605
|
-
next_cursor =
|
|
659
|
+
next_cursor = (
|
|
660
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
661
|
+
)
|
|
606
662
|
return items, next_cursor
|
|
607
663
|
|
|
608
664
|
# -------- Intents --------
|
|
609
|
-
async def create_intent(
|
|
665
|
+
async def create_intent(
|
|
666
|
+
self, data: IntentCreateIn, *, user_id: str | None
|
|
667
|
+
) -> IntentOut:
|
|
610
668
|
kwargs: dict[str, Any] = dict(
|
|
611
669
|
amount=int(data.amount),
|
|
612
670
|
currency=data.currency.lower(),
|
|
613
671
|
description=data.description or None,
|
|
614
672
|
capture_method="manual" if data.capture_method == "manual" else "automatic",
|
|
615
|
-
automatic_payment_methods={"enabled": True}
|
|
673
|
+
automatic_payment_methods={"enabled": True}
|
|
674
|
+
if not data.payment_method_types
|
|
675
|
+
else None,
|
|
616
676
|
)
|
|
617
677
|
if data.payment_method_types:
|
|
618
678
|
kwargs["payment_method_types"] = data.payment_method_types
|
|
619
679
|
pi = await _acall(
|
|
620
|
-
stripe.PaymentIntent.create,
|
|
680
|
+
stripe.PaymentIntent.create,
|
|
681
|
+
**{k: v for k, v in kwargs.items() if v is not None},
|
|
621
682
|
)
|
|
622
683
|
return _pi_to_out(pi)
|
|
623
684
|
|
|
@@ -648,7 +709,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
648
709
|
pi = await _acall(stripe.PaymentIntent.retrieve, provider_intent_id)
|
|
649
710
|
return _pi_to_out(pi)
|
|
650
711
|
|
|
651
|
-
async def capture_intent(
|
|
712
|
+
async def capture_intent(
|
|
713
|
+
self, provider_intent_id: str, *, amount: int | None
|
|
714
|
+
) -> IntentOut:
|
|
652
715
|
kwargs = {}
|
|
653
716
|
if amount is not None:
|
|
654
717
|
kwargs["amount_to_capture"] = int(amount)
|
|
@@ -663,7 +726,7 @@ class StripeAdapter(ProviderAdapter):
|
|
|
663
726
|
limit: int,
|
|
664
727
|
cursor: str | None,
|
|
665
728
|
) -> tuple[list[IntentOut], str | None]:
|
|
666
|
-
params = {"limit": int(limit)}
|
|
729
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
667
730
|
if customer_provider_id:
|
|
668
731
|
params["customer"] = customer_provider_id
|
|
669
732
|
if status:
|
|
@@ -672,13 +735,16 @@ class StripeAdapter(ProviderAdapter):
|
|
|
672
735
|
params["starting_after"] = cursor
|
|
673
736
|
res = await _acall(stripe.PaymentIntent.list, **params)
|
|
674
737
|
items = [_pi_to_out(pi) for pi in res.data]
|
|
675
|
-
next_cursor =
|
|
738
|
+
next_cursor = (
|
|
739
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
740
|
+
)
|
|
676
741
|
return items, next_cursor
|
|
677
742
|
|
|
678
743
|
# ---- Setup Intents (off-session readiness) ----
|
|
679
744
|
async def create_setup_intent(self, data: SetupIntentCreateIn) -> SetupIntentOut:
|
|
680
745
|
si = await _acall(
|
|
681
|
-
stripe.SetupIntent.create,
|
|
746
|
+
stripe.SetupIntent.create,
|
|
747
|
+
payment_method_types=data.payment_method_types or ["card"],
|
|
682
748
|
)
|
|
683
749
|
return SetupIntentOut(
|
|
684
750
|
id=si.id,
|
|
@@ -686,10 +752,14 @@ class StripeAdapter(ProviderAdapter):
|
|
|
686
752
|
provider_setup_intent_id=si.id,
|
|
687
753
|
status=si.status,
|
|
688
754
|
client_secret=getattr(si, "client_secret", None),
|
|
689
|
-
next_action=NextAction(
|
|
755
|
+
next_action=NextAction(
|
|
756
|
+
type=getattr(getattr(si, "next_action", None), "type", None)
|
|
757
|
+
),
|
|
690
758
|
)
|
|
691
759
|
|
|
692
|
-
async def confirm_setup_intent(
|
|
760
|
+
async def confirm_setup_intent(
|
|
761
|
+
self, provider_setup_intent_id: str
|
|
762
|
+
) -> SetupIntentOut:
|
|
693
763
|
si = await _acall(stripe.SetupIntent.confirm, provider_setup_intent_id)
|
|
694
764
|
return SetupIntentOut(
|
|
695
765
|
id=si.id,
|
|
@@ -697,7 +767,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
697
767
|
provider_setup_intent_id=si.id,
|
|
698
768
|
status=si.status,
|
|
699
769
|
client_secret=getattr(si, "client_secret", None),
|
|
700
|
-
next_action=NextAction(
|
|
770
|
+
next_action=NextAction(
|
|
771
|
+
type=getattr(getattr(si, "next_action", None), "type", None)
|
|
772
|
+
),
|
|
701
773
|
)
|
|
702
774
|
|
|
703
775
|
async def get_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
@@ -708,7 +780,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
708
780
|
provider_setup_intent_id=si.id,
|
|
709
781
|
status=si.status,
|
|
710
782
|
client_secret=getattr(si, "client_secret", None),
|
|
711
|
-
next_action=NextAction(
|
|
783
|
+
next_action=NextAction(
|
|
784
|
+
type=getattr(getattr(si, "next_action", None), "type", None)
|
|
785
|
+
),
|
|
712
786
|
)
|
|
713
787
|
|
|
714
788
|
# ---- 3DS/SCA resume ----
|
|
@@ -720,25 +794,29 @@ class StripeAdapter(ProviderAdapter):
|
|
|
720
794
|
async def list_disputes(
|
|
721
795
|
self, *, status: str | None, limit: int, cursor: str | None
|
|
722
796
|
) -> tuple[list[DisputeOut], str | None]:
|
|
723
|
-
params = {"limit": int(limit)}
|
|
797
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
724
798
|
if status:
|
|
725
799
|
params["status"] = status
|
|
726
800
|
if cursor:
|
|
727
801
|
params["starting_after"] = cursor
|
|
728
802
|
res = await _acall(stripe.Dispute.list, **params)
|
|
729
803
|
items = [_dispute_to_out(d) for d in res.data]
|
|
730
|
-
next_cursor =
|
|
804
|
+
next_cursor = (
|
|
805
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
806
|
+
)
|
|
731
807
|
return items, next_cursor
|
|
732
808
|
|
|
733
809
|
async def get_dispute(self, provider_dispute_id: str) -> DisputeOut:
|
|
734
810
|
d = await _acall(stripe.Dispute.retrieve, provider_dispute_id)
|
|
735
811
|
return _dispute_to_out(d)
|
|
736
812
|
|
|
737
|
-
async def submit_dispute_evidence(
|
|
813
|
+
async def submit_dispute_evidence(
|
|
814
|
+
self, provider_dispute_id: str, evidence: dict
|
|
815
|
+
) -> DisputeOut:
|
|
738
816
|
d = await _acall(stripe.Dispute.modify, provider_dispute_id, evidence=evidence)
|
|
739
817
|
# Some disputes require explicit submit call:
|
|
740
818
|
try:
|
|
741
|
-
d = await _acall(stripe.Dispute.submit, provider_dispute_id)
|
|
819
|
+
d = await _acall(stripe.Dispute.submit, provider_dispute_id) # type: ignore[attr-defined]
|
|
742
820
|
except Exception:
|
|
743
821
|
pass
|
|
744
822
|
return _dispute_to_out(d)
|
|
@@ -750,7 +828,9 @@ class StripeAdapter(ProviderAdapter):
|
|
|
750
828
|
def _bucket(entries):
|
|
751
829
|
out = []
|
|
752
830
|
for b in entries or []:
|
|
753
|
-
out.append(
|
|
831
|
+
out.append(
|
|
832
|
+
{"currency": str(b.currency).upper(), "amount": int(b.amount)}
|
|
833
|
+
)
|
|
754
834
|
return out
|
|
755
835
|
|
|
756
836
|
return BalanceSnapshotOut(
|
|
@@ -761,12 +841,14 @@ class StripeAdapter(ProviderAdapter):
|
|
|
761
841
|
async def list_payouts(
|
|
762
842
|
self, *, limit: int, cursor: str | None
|
|
763
843
|
) -> tuple[list[PayoutOut], str | None]:
|
|
764
|
-
params = {"limit": int(limit)}
|
|
844
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
765
845
|
if cursor:
|
|
766
846
|
params["starting_after"] = cursor
|
|
767
847
|
res = await _acall(stripe.Payout.list, **params)
|
|
768
848
|
items = [_payout_to_out(p) for p in res.data]
|
|
769
|
-
next_cursor =
|
|
849
|
+
next_cursor = (
|
|
850
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
851
|
+
)
|
|
770
852
|
return items, next_cursor
|
|
771
853
|
|
|
772
854
|
async def get_payout(self, provider_payout_id: str) -> PayoutOut:
|
|
@@ -777,14 +859,16 @@ class StripeAdapter(ProviderAdapter):
|
|
|
777
859
|
async def list_refunds(
|
|
778
860
|
self, *, provider_payment_intent_id: str | None, limit: int, cursor: str | None
|
|
779
861
|
) -> tuple[list[RefundOut], str | None]:
|
|
780
|
-
params = {"limit": int(limit)}
|
|
862
|
+
params: dict[str, Any] = {"limit": int(limit)}
|
|
781
863
|
if provider_payment_intent_id:
|
|
782
864
|
params["payment_intent"] = provider_payment_intent_id
|
|
783
865
|
if cursor:
|
|
784
866
|
params["starting_after"] = cursor
|
|
785
867
|
res = await _acall(stripe.Refund.list, **params)
|
|
786
868
|
items = [_refund_to_out(r) for r in res.data]
|
|
787
|
-
next_cursor =
|
|
869
|
+
next_cursor = (
|
|
870
|
+
res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
871
|
+
)
|
|
788
872
|
return items, next_cursor
|
|
789
873
|
|
|
790
874
|
async def get_refund(self, provider_refund_id: str) -> RefundOut:
|
|
@@ -810,7 +894,7 @@ class StripeAdapter(ProviderAdapter):
|
|
|
810
894
|
}
|
|
811
895
|
if data.timestamp:
|
|
812
896
|
body["timestamp"] = int(data.timestamp)
|
|
813
|
-
rec = await _acall(stripe.UsageRecord.create, **body)
|
|
897
|
+
rec = await _acall(stripe.UsageRecord.create, **body) # type: ignore[attr-defined]
|
|
814
898
|
return UsageRecordOut(
|
|
815
899
|
id=rec.id,
|
|
816
900
|
quantity=int(rec.quantity),
|
|
@@ -825,19 +909,25 @@ class StripeAdapter(ProviderAdapter):
|
|
|
825
909
|
# Stripe exposes *summaries* per period. We surface them as list results.
|
|
826
910
|
sub_item = f.subscription_item
|
|
827
911
|
if not sub_item and f.provider_price_id:
|
|
828
|
-
items = await _acall(
|
|
912
|
+
items = await _acall(
|
|
913
|
+
stripe.SubscriptionItem.list, price=f.provider_price_id, limit=1
|
|
914
|
+
)
|
|
829
915
|
sub_item = items.data[0].id if items.data else None
|
|
830
916
|
if not sub_item:
|
|
831
917
|
return [], None
|
|
832
|
-
params = {"limit": int(f.limit or 50)}
|
|
918
|
+
params: dict[str, Any] = {"limit": int(f.limit or 50)}
|
|
833
919
|
if f.cursor:
|
|
834
920
|
params["starting_after"] = f.cursor
|
|
835
|
-
res = await _acall(
|
|
836
|
-
|
|
921
|
+
res = await _acall(
|
|
922
|
+
stripe.SubscriptionItem.list_usage_record_summaries, # type: ignore[attr-defined]
|
|
923
|
+
sub_item,
|
|
924
|
+
**params,
|
|
925
|
+
)
|
|
926
|
+
usage_records: list[UsageRecordOut] = []
|
|
837
927
|
for s in res.data:
|
|
838
928
|
# No record id in summaries—synthesize a stable id from period start.
|
|
839
929
|
synthesized_id = f"{sub_item}:{getattr(s, 'period', {}).get('start')}"
|
|
840
|
-
|
|
930
|
+
usage_records.append(
|
|
841
931
|
UsageRecordOut(
|
|
842
932
|
id=synthesized_id,
|
|
843
933
|
quantity=int(getattr(s, "total_usage", 0)),
|
|
@@ -848,15 +938,19 @@ class StripeAdapter(ProviderAdapter):
|
|
|
848
938
|
)
|
|
849
939
|
next_cursor = (
|
|
850
940
|
res.data[-1].id
|
|
851
|
-
if getattr(res, "has_more", False)
|
|
941
|
+
if getattr(res, "has_more", False)
|
|
942
|
+
and res.data
|
|
943
|
+
and hasattr(res.data[-1], "id")
|
|
852
944
|
else None
|
|
853
945
|
)
|
|
854
|
-
return
|
|
946
|
+
return usage_records, next_cursor
|
|
855
947
|
|
|
856
948
|
async def get_usage_record(self, usage_record_id: str) -> UsageRecordOut:
|
|
857
949
|
# Stripe has no direct "retrieve usage record by id" API.
|
|
858
950
|
# You can reconstruct via list summaries or store records locally when creating.
|
|
859
|
-
raise NotImplementedError(
|
|
951
|
+
raise NotImplementedError(
|
|
952
|
+
"Stripe does not support retrieving a single usage record by id"
|
|
953
|
+
)
|
|
860
954
|
|
|
861
955
|
# -------- Webhooks --------
|
|
862
956
|
async def verify_and_parse_webhook(
|