svc-infra 0.1.595__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/models.py +133 -42
- svc_infra/apf_payments/provider/aiydan.py +121 -47
- svc_infra/apf_payments/provider/base.py +30 -9
- svc_infra/apf_payments/provider/stripe.py +156 -62
- svc_infra/apf_payments/schemas.py +18 -9
- svc_infra/apf_payments/service.py +98 -41
- svc_infra/apf_payments/settings.py +5 -1
- 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 +128 -70
- svc_infra/api/fastapi/apf_payments/setup.py +13 -6
- 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 +17 -14
- svc_infra/api/fastapi/auth/gaurd.py +45 -16
- 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 +146 -52
- svc_infra/api/fastapi/auth/routers/session_router.py +6 -2
- svc_infra/api/fastapi/auth/security.py +31 -10
- svc_infra/api/fastapi/auth/sender.py +8 -1
- 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 +18 -6
- svc_infra/api/fastapi/dependencies/ratelimit.py +78 -14
- 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 +125 -28
- svc_infra/api/fastapi/middleware/ratelimit_store.py +43 -10
- svc_infra/api/fastapi/middleware/request_id.py +27 -11
- svc_infra/api/fastapi/middleware/request_size_limit.py +3 -3
- 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 -57
- 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/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 +3 -4
- 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 +6 -5
- svc_infra/obs/settings.py +6 -2
- svc_infra/security/add.py +217 -0
- svc_infra/security/audit.py +92 -10
- svc_infra/security/audit_service.py +4 -3
- svc_infra/security/headers.py +15 -2
- svc_infra/security/hibp.py +14 -4
- svc_infra/security/jwt_rotation.py +74 -22
- svc_infra/security/lockout.py +11 -5
- svc_infra/security/models.py +54 -12
- svc_infra/security/oauth_models.py +73 -0
- svc_infra/security/org_invites.py +5 -3
- svc_infra/security/passwords.py +3 -1
- svc_infra/security/permissions.py +25 -2
- svc_infra/security/session.py +1 -1
- svc_infra/security/signed_cookies.py +21 -1
- 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.595.dist-info/METADATA +0 -80
- svc_infra-0.1.595.dist-info/RECORD +0 -253
- {svc_infra-0.1.595.dist-info → svc_infra-0.1.706.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.595.dist-info → svc_infra-0.1.706.dist-info}/entry_points.txt +0 -0
|
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
4
|
from datetime import date, datetime, timezone
|
|
5
|
-
from typing import Any, Optional, Sequence, Tuple
|
|
5
|
+
from typing import Any, Literal, Optional, Sequence, Tuple, cast
|
|
6
6
|
|
|
7
7
|
from svc_infra.apf_payments.schemas import (
|
|
8
8
|
BalanceAmount,
|
|
@@ -43,9 +43,9 @@ from svc_infra.apf_payments.settings import get_payments_settings
|
|
|
43
43
|
from .base import ProviderAdapter
|
|
44
44
|
|
|
45
45
|
try: # pragma: no cover - optional dependency
|
|
46
|
-
import aiydan
|
|
46
|
+
import aiydan
|
|
47
47
|
except Exception: # pragma: no cover - handled at runtime
|
|
48
|
-
aiydan = None
|
|
48
|
+
aiydan = None
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
async def _maybe_await(result: Any) -> Any:
|
|
@@ -62,24 +62,27 @@ def _coerce_id(data: dict[str, Any], *candidates: str) -> str:
|
|
|
62
62
|
raise RuntimeError(f"Aiydan payload missing id fields: {candidates}")
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
def _ensure_utc_isoformat(value: Any) ->
|
|
65
|
+
def _ensure_utc_isoformat(value: Any) -> str | None:
|
|
66
66
|
if value is None:
|
|
67
67
|
return None
|
|
68
68
|
if isinstance(value, str):
|
|
69
69
|
return value
|
|
70
70
|
if isinstance(value, datetime):
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
dt: datetime = value
|
|
72
|
+
if dt.tzinfo is None:
|
|
73
|
+
dt = dt.replace(tzinfo=timezone.utc)
|
|
74
|
+
return dt.astimezone(timezone.utc).isoformat()
|
|
74
75
|
if isinstance(value, date):
|
|
75
|
-
return datetime(
|
|
76
|
+
return datetime(
|
|
77
|
+
value.year, value.month, value.day, tzinfo=timezone.utc
|
|
78
|
+
).isoformat()
|
|
76
79
|
try:
|
|
77
80
|
parsed = datetime.fromisoformat(str(value))
|
|
78
81
|
if parsed.tzinfo is None:
|
|
79
82
|
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
80
83
|
return parsed.astimezone(timezone.utc).isoformat()
|
|
81
84
|
except Exception:
|
|
82
|
-
return str(value)
|
|
85
|
+
return cast(str, str(value)) # Cast needed since value is Any
|
|
83
86
|
|
|
84
87
|
|
|
85
88
|
def _customer_to_out(data: dict[str, Any]) -> CustomerOut:
|
|
@@ -113,7 +116,9 @@ def _payment_method_to_out(data: dict[str, Any]) -> PaymentMethodOut:
|
|
|
113
116
|
return PaymentMethodOut(
|
|
114
117
|
id=method_id,
|
|
115
118
|
provider="aiydan",
|
|
116
|
-
provider_customer_id=str(
|
|
119
|
+
provider_customer_id=str(
|
|
120
|
+
data.get("provider_customer_id") or data.get("customer_id") or ""
|
|
121
|
+
),
|
|
117
122
|
provider_method_id=method_id,
|
|
118
123
|
brand=card.get("brand") or data.get("brand"),
|
|
119
124
|
last4=card.get("last4") or data.get("last4"),
|
|
@@ -194,7 +199,9 @@ def _invoice_to_out(data: dict[str, Any]) -> InvoiceOut:
|
|
|
194
199
|
id=invoice_id,
|
|
195
200
|
provider="aiydan",
|
|
196
201
|
provider_invoice_id=invoice_id,
|
|
197
|
-
provider_customer_id=str(
|
|
202
|
+
provider_customer_id=str(
|
|
203
|
+
data.get("provider_customer_id") or data.get("customer_id") or ""
|
|
204
|
+
),
|
|
198
205
|
status=str(data.get("status", "")),
|
|
199
206
|
amount_due=int(data.get("amount_due", data.get("amount") or 0) or 0),
|
|
200
207
|
currency=str(data.get("currency", "")).upper(),
|
|
@@ -208,12 +215,15 @@ def _invoice_line_item_to_out(data: dict[str, Any]) -> InvoiceLineItemOut:
|
|
|
208
215
|
price = data.get("price") or {}
|
|
209
216
|
if not isinstance(price, dict):
|
|
210
217
|
price = {"id": getattr(price, "id", None)}
|
|
218
|
+
quantity = int(data.get("quantity", 0) or 0)
|
|
219
|
+
unit_amount = int(data.get("unit_amount", 0) or 0)
|
|
220
|
+
amount = int(data.get("amount", unit_amount * quantity) or 0)
|
|
211
221
|
return InvoiceLineItemOut(
|
|
212
222
|
id=line_id,
|
|
213
223
|
description=data.get("description"),
|
|
214
224
|
currency=str(data.get("currency", price.get("currency", ""))).upper(),
|
|
215
|
-
quantity=
|
|
216
|
-
|
|
225
|
+
quantity=quantity,
|
|
226
|
+
amount=amount,
|
|
217
227
|
provider_price_id=price.get("id"),
|
|
218
228
|
)
|
|
219
229
|
|
|
@@ -225,7 +235,9 @@ def _refund_to_out(data: dict[str, Any]) -> RefundOut:
|
|
|
225
235
|
provider="aiydan",
|
|
226
236
|
provider_refund_id=refund_id,
|
|
227
237
|
provider_payment_intent_id=str(
|
|
228
|
-
data.get("provider_payment_intent_id")
|
|
238
|
+
data.get("provider_payment_intent_id")
|
|
239
|
+
or data.get("payment_intent_id")
|
|
240
|
+
or ""
|
|
229
241
|
),
|
|
230
242
|
amount=int(data.get("amount", 0) or 0),
|
|
231
243
|
currency=str(data.get("currency", "")).upper(),
|
|
@@ -268,17 +280,25 @@ def _payout_to_out(data: dict[str, Any]) -> PayoutOut:
|
|
|
268
280
|
|
|
269
281
|
|
|
270
282
|
def _usage_record_to_out(data: dict[str, Any]) -> UsageRecordOut:
|
|
283
|
+
action_raw = data.get("action")
|
|
284
|
+
action: Literal["increment", "set"] | None = None
|
|
285
|
+
if action_raw in ("increment", "set"):
|
|
286
|
+
action = cast(Literal["increment", "set"], action_raw)
|
|
271
287
|
return UsageRecordOut(
|
|
272
288
|
id=str(data.get("id")),
|
|
273
289
|
quantity=int(data.get("quantity", 0) or 0),
|
|
274
290
|
timestamp=data.get("timestamp"),
|
|
275
291
|
subscription_item=(
|
|
276
|
-
str(data.get("subscription_item"))
|
|
292
|
+
str(data.get("subscription_item"))
|
|
293
|
+
if data.get("subscription_item")
|
|
294
|
+
else None
|
|
277
295
|
),
|
|
278
296
|
provider_price_id=(
|
|
279
|
-
str(data.get("provider_price_id"))
|
|
297
|
+
str(data.get("provider_price_id"))
|
|
298
|
+
if data.get("provider_price_id")
|
|
299
|
+
else None
|
|
280
300
|
),
|
|
281
|
-
action=
|
|
301
|
+
action=action,
|
|
282
302
|
)
|
|
283
303
|
|
|
284
304
|
|
|
@@ -297,7 +317,8 @@ def _balance_snapshot_to_out(data: dict[str, Any]) -> BalanceSnapshotOut:
|
|
|
297
317
|
return out
|
|
298
318
|
if isinstance(side, dict):
|
|
299
319
|
return [
|
|
300
|
-
{"currency": str(cur).upper(), "amount": int(amt or 0)}
|
|
320
|
+
{"currency": str(cur).upper(), "amount": int(amt or 0)}
|
|
321
|
+
for cur, amt in side.items()
|
|
301
322
|
]
|
|
302
323
|
return []
|
|
303
324
|
|
|
@@ -315,26 +336,28 @@ def _balance_snapshot_to_out(data: dict[str, Any]) -> BalanceSnapshotOut:
|
|
|
315
336
|
|
|
316
337
|
def _ensure_sequence(result: Any) -> Sequence[dict[str, Any]]:
|
|
317
338
|
if isinstance(result, Sequence):
|
|
318
|
-
return result
|
|
339
|
+
return result
|
|
319
340
|
if isinstance(result, dict):
|
|
320
341
|
items = result.get("items")
|
|
321
342
|
if isinstance(items, Sequence):
|
|
322
|
-
return items
|
|
343
|
+
return items
|
|
323
344
|
raise RuntimeError("Expected sequence payload from Aiydan client")
|
|
324
345
|
|
|
325
346
|
|
|
326
|
-
def _ensure_list_response(
|
|
347
|
+
def _ensure_list_response(
|
|
348
|
+
result: Any,
|
|
349
|
+
) -> Tuple[Sequence[dict[str, Any]], Optional[str]]:
|
|
327
350
|
if isinstance(result, tuple) and len(result) == 2:
|
|
328
351
|
items, cursor = result
|
|
329
352
|
if isinstance(items, Sequence) or items is None:
|
|
330
|
-
return (items or []), cursor
|
|
353
|
+
return (items or []), cursor
|
|
331
354
|
if isinstance(result, dict):
|
|
332
355
|
items = result.get("items")
|
|
333
356
|
cursor = result.get("next_cursor") or result.get("cursor")
|
|
334
357
|
if isinstance(items, Sequence):
|
|
335
358
|
return items, cursor
|
|
336
359
|
if isinstance(result, Sequence):
|
|
337
|
-
return result, None
|
|
360
|
+
return result, None
|
|
338
361
|
raise RuntimeError("Expected iterable response from Aiydan client")
|
|
339
362
|
|
|
340
363
|
|
|
@@ -347,7 +370,9 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
347
370
|
if client is not None:
|
|
348
371
|
self._client = client
|
|
349
372
|
self._webhook_secret = (
|
|
350
|
-
cfg.webhook_secret.get_secret_value()
|
|
373
|
+
cfg.webhook_secret.get_secret_value()
|
|
374
|
+
if cfg and cfg.webhook_secret
|
|
375
|
+
else None
|
|
351
376
|
)
|
|
352
377
|
return
|
|
353
378
|
if cfg is None:
|
|
@@ -367,32 +392,44 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
367
392
|
if cfg.base_url:
|
|
368
393
|
kwargs["base_url"] = cfg.base_url
|
|
369
394
|
self._client = client_class(**kwargs)
|
|
370
|
-
self._webhook_secret =
|
|
395
|
+
self._webhook_secret = (
|
|
396
|
+
cfg.webhook_secret.get_secret_value() if cfg.webhook_secret else None
|
|
397
|
+
)
|
|
371
398
|
|
|
372
399
|
async def ensure_customer(self, data: CustomerUpsertIn) -> CustomerOut:
|
|
373
400
|
payload = data.model_dump(exclude_none=True)
|
|
374
401
|
result = await _maybe_await(self._client.ensure_customer(payload))
|
|
375
402
|
return _customer_to_out(result)
|
|
376
403
|
|
|
377
|
-
async def attach_payment_method(
|
|
404
|
+
async def attach_payment_method(
|
|
405
|
+
self, data: PaymentMethodAttachIn
|
|
406
|
+
) -> PaymentMethodOut:
|
|
378
407
|
payload = data.model_dump(exclude_none=True)
|
|
379
408
|
result = await _maybe_await(self._client.attach_payment_method(payload))
|
|
380
409
|
return _payment_method_to_out(result)
|
|
381
410
|
|
|
382
|
-
async def list_payment_methods(
|
|
383
|
-
|
|
411
|
+
async def list_payment_methods(
|
|
412
|
+
self, provider_customer_id: str
|
|
413
|
+
) -> list[PaymentMethodOut]:
|
|
414
|
+
result = await _maybe_await(
|
|
415
|
+
self._client.list_payment_methods(provider_customer_id)
|
|
416
|
+
)
|
|
384
417
|
methods = _ensure_sequence(result)
|
|
385
418
|
return [_payment_method_to_out(method) for method in methods]
|
|
386
419
|
|
|
387
420
|
async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
388
|
-
result = await _maybe_await(
|
|
421
|
+
result = await _maybe_await(
|
|
422
|
+
self._client.detach_payment_method(provider_method_id)
|
|
423
|
+
)
|
|
389
424
|
return _payment_method_to_out(result)
|
|
390
425
|
|
|
391
426
|
async def set_default_payment_method(
|
|
392
427
|
self, provider_customer_id: str, provider_method_id: str
|
|
393
428
|
) -> PaymentMethodOut:
|
|
394
429
|
result = await _maybe_await(
|
|
395
|
-
self._client.set_default_payment_method(
|
|
430
|
+
self._client.set_default_payment_method(
|
|
431
|
+
provider_customer_id, provider_method_id
|
|
432
|
+
)
|
|
396
433
|
)
|
|
397
434
|
return _payment_method_to_out(result)
|
|
398
435
|
|
|
@@ -404,7 +441,9 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
404
441
|
self, provider_method_id: str, data: PaymentMethodUpdateIn
|
|
405
442
|
) -> PaymentMethodOut:
|
|
406
443
|
payload = data.model_dump(exclude_none=True)
|
|
407
|
-
result = await _maybe_await(
|
|
444
|
+
result = await _maybe_await(
|
|
445
|
+
self._client.update_payment_method(provider_method_id, payload)
|
|
446
|
+
)
|
|
408
447
|
return _payment_method_to_out(result)
|
|
409
448
|
|
|
410
449
|
async def create_product(self, data: ProductCreateIn) -> ProductOut:
|
|
@@ -425,9 +464,13 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
425
464
|
items, next_cursor = _ensure_list_response(result)
|
|
426
465
|
return [_product_to_out(item) for item in items], next_cursor
|
|
427
466
|
|
|
428
|
-
async def update_product(
|
|
467
|
+
async def update_product(
|
|
468
|
+
self, provider_product_id: str, data: ProductUpdateIn
|
|
469
|
+
) -> ProductOut:
|
|
429
470
|
payload = data.model_dump(exclude_none=True)
|
|
430
|
-
result = await _maybe_await(
|
|
471
|
+
result = await _maybe_await(
|
|
472
|
+
self._client.update_product(provider_product_id, payload)
|
|
473
|
+
)
|
|
431
474
|
return _product_to_out(result)
|
|
432
475
|
|
|
433
476
|
async def create_price(self, data: PriceCreateIn) -> PriceOut:
|
|
@@ -458,9 +501,13 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
458
501
|
items, next_cursor = _ensure_list_response(result)
|
|
459
502
|
return [_price_to_out(item) for item in items], next_cursor
|
|
460
503
|
|
|
461
|
-
async def update_price(
|
|
504
|
+
async def update_price(
|
|
505
|
+
self, provider_price_id: str, data: PriceUpdateIn
|
|
506
|
+
) -> PriceOut:
|
|
462
507
|
payload = data.model_dump(exclude_none=True)
|
|
463
|
-
result = await _maybe_await(
|
|
508
|
+
result = await _maybe_await(
|
|
509
|
+
self._client.update_price(provider_price_id, payload)
|
|
510
|
+
)
|
|
464
511
|
return _price_to_out(result)
|
|
465
512
|
|
|
466
513
|
async def create_subscription(self, data: SubscriptionCreateIn) -> SubscriptionOut:
|
|
@@ -486,7 +533,9 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
486
533
|
return _subscription_to_out(result)
|
|
487
534
|
|
|
488
535
|
async def get_subscription(self, provider_subscription_id: str) -> SubscriptionOut:
|
|
489
|
-
result = await _maybe_await(
|
|
536
|
+
result = await _maybe_await(
|
|
537
|
+
self._client.get_subscription(provider_subscription_id)
|
|
538
|
+
)
|
|
490
539
|
return _subscription_to_out(result)
|
|
491
540
|
|
|
492
541
|
async def list_subscriptions(
|
|
@@ -581,7 +630,9 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
581
630
|
items, next_cursor = _ensure_list_response(result)
|
|
582
631
|
return [_invoice_line_item_to_out(item) for item in items], next_cursor
|
|
583
632
|
|
|
584
|
-
async def create_intent(
|
|
633
|
+
async def create_intent(
|
|
634
|
+
self, data: IntentCreateIn, *, user_id: str | None
|
|
635
|
+
) -> IntentOut:
|
|
585
636
|
payload = data.model_dump(exclude_none=True)
|
|
586
637
|
if user_id is not None:
|
|
587
638
|
payload["user_id"] = user_id
|
|
@@ -598,15 +649,21 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
598
649
|
|
|
599
650
|
async def refund(self, provider_intent_id: str, data: RefundIn) -> IntentOut:
|
|
600
651
|
payload = data.model_dump(exclude_none=True)
|
|
601
|
-
result = await _maybe_await(
|
|
652
|
+
result = await _maybe_await(
|
|
653
|
+
self._client.refund_intent(provider_intent_id, payload)
|
|
654
|
+
)
|
|
602
655
|
return _intent_to_out(result)
|
|
603
656
|
|
|
604
657
|
async def hydrate_intent(self, provider_intent_id: str) -> IntentOut:
|
|
605
658
|
result = await _maybe_await(self._client.get_intent(provider_intent_id))
|
|
606
659
|
return _intent_to_out(result)
|
|
607
660
|
|
|
608
|
-
async def capture_intent(
|
|
609
|
-
|
|
661
|
+
async def capture_intent(
|
|
662
|
+
self, provider_intent_id: str, *, amount: int | None
|
|
663
|
+
) -> IntentOut:
|
|
664
|
+
result = await _maybe_await(
|
|
665
|
+
self._client.capture_intent(provider_intent_id, amount=amount)
|
|
666
|
+
)
|
|
610
667
|
return _intent_to_out(result)
|
|
611
668
|
|
|
612
669
|
async def list_intents(
|
|
@@ -666,7 +723,9 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
666
723
|
result = await _maybe_await(self._client.get_dispute(provider_dispute_id))
|
|
667
724
|
return _dispute_to_out(result)
|
|
668
725
|
|
|
669
|
-
async def submit_dispute_evidence(
|
|
726
|
+
async def submit_dispute_evidence(
|
|
727
|
+
self, provider_dispute_id: str, evidence: dict
|
|
728
|
+
) -> DisputeOut:
|
|
670
729
|
result = await _maybe_await(
|
|
671
730
|
self._client.submit_dispute_evidence(provider_dispute_id, evidence)
|
|
672
731
|
)
|
|
@@ -683,7 +742,9 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
683
742
|
async def list_payouts(
|
|
684
743
|
self, *, limit: int, cursor: str | None
|
|
685
744
|
) -> tuple[list[PayoutOut], str | None]:
|
|
686
|
-
result = await _maybe_await(
|
|
745
|
+
result = await _maybe_await(
|
|
746
|
+
self._client.list_payouts(limit=limit, cursor=cursor)
|
|
747
|
+
)
|
|
687
748
|
items, next_cursor = _ensure_list_response(result)
|
|
688
749
|
return [_payout_to_out(item) for item in items], next_cursor
|
|
689
750
|
|
|
@@ -743,8 +804,12 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
743
804
|
next_action=NextAction(type=(result.get("next_action") or {}).get("type")),
|
|
744
805
|
)
|
|
745
806
|
|
|
746
|
-
async def confirm_setup_intent(
|
|
747
|
-
|
|
807
|
+
async def confirm_setup_intent(
|
|
808
|
+
self, provider_setup_intent_id: str
|
|
809
|
+
) -> SetupIntentOut:
|
|
810
|
+
result = await _maybe_await(
|
|
811
|
+
self._client.confirm_setup_intent(provider_setup_intent_id)
|
|
812
|
+
)
|
|
748
813
|
return SetupIntentOut(
|
|
749
814
|
id=_coerce_id(result, "provider_setup_intent_id", "setup_intent_id", "id"),
|
|
750
815
|
provider="aiydan",
|
|
@@ -757,7 +822,9 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
757
822
|
)
|
|
758
823
|
|
|
759
824
|
async def get_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
760
|
-
result = await _maybe_await(
|
|
825
|
+
result = await _maybe_await(
|
|
826
|
+
self._client.get_setup_intent(provider_setup_intent_id)
|
|
827
|
+
)
|
|
761
828
|
return SetupIntentOut(
|
|
762
829
|
id=_coerce_id(result, "provider_setup_intent_id", "setup_intent_id", "id"),
|
|
763
830
|
provider="aiydan",
|
|
@@ -771,13 +838,20 @@ class AiydanAdapter(ProviderAdapter):
|
|
|
771
838
|
|
|
772
839
|
async def resume_intent_after_action(self, provider_intent_id: str) -> IntentOut:
|
|
773
840
|
if hasattr(self._client, "resume_intent_after_action"):
|
|
774
|
-
result = await _maybe_await(
|
|
841
|
+
result = await _maybe_await(
|
|
842
|
+
self._client.resume_intent_after_action(provider_intent_id)
|
|
843
|
+
)
|
|
775
844
|
else:
|
|
776
845
|
result = await _maybe_await(self._client.get_intent(provider_intent_id))
|
|
777
846
|
return _intent_to_out(result)
|
|
778
847
|
|
|
779
848
|
async def list_customers(
|
|
780
|
-
self,
|
|
849
|
+
self,
|
|
850
|
+
*,
|
|
851
|
+
provider: str | None,
|
|
852
|
+
user_id: str | None,
|
|
853
|
+
limit: int,
|
|
854
|
+
cursor: str | None,
|
|
781
855
|
) -> tuple[list[CustomerOut], str | None]:
|
|
782
856
|
result = await _maybe_await(
|
|
783
857
|
self._client.list_customers(
|
|
@@ -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 ---
|