svc-infra 0.1.562__py3-none-any.whl → 0.1.654__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.
- svc_infra/apf_payments/README.md +732 -0
- svc_infra/apf_payments/models.py +142 -4
- svc_infra/apf_payments/provider/__init__.py +4 -0
- svc_infra/apf_payments/provider/aiydan.py +797 -0
- svc_infra/apf_payments/provider/base.py +178 -12
- svc_infra/apf_payments/provider/stripe.py +757 -48
- svc_infra/apf_payments/schemas.py +163 -1
- svc_infra/apf_payments/service.py +582 -42
- svc_infra/apf_payments/settings.py +22 -2
- svc_infra/api/fastapi/admin/__init__.py +3 -0
- svc_infra/api/fastapi/admin/add.py +231 -0
- svc_infra/api/fastapi/apf_payments/router.py +792 -73
- svc_infra/api/fastapi/apf_payments/setup.py +13 -4
- svc_infra/api/fastapi/auth/add.py +10 -4
- svc_infra/api/fastapi/auth/gaurd.py +67 -5
- svc_infra/api/fastapi/auth/routers/oauth_router.py +74 -34
- svc_infra/api/fastapi/auth/routers/session_router.py +63 -0
- svc_infra/api/fastapi/auth/settings.py +2 -0
- svc_infra/api/fastapi/billing/router.py +64 -0
- svc_infra/api/fastapi/billing/setup.py +19 -0
- svc_infra/api/fastapi/cache/add.py +9 -5
- svc_infra/api/fastapi/db/nosql/mongo/add.py +33 -27
- svc_infra/api/fastapi/db/sql/add.py +40 -18
- svc_infra/api/fastapi/db/sql/crud_router.py +176 -14
- svc_infra/api/fastapi/db/sql/session.py +16 -0
- svc_infra/api/fastapi/db/sql/users.py +13 -1
- svc_infra/api/fastapi/dependencies/ratelimit.py +116 -0
- svc_infra/api/fastapi/docs/add.py +160 -0
- svc_infra/api/fastapi/docs/landing.py +1 -1
- svc_infra/api/fastapi/docs/scoped.py +41 -6
- svc_infra/api/fastapi/middleware/errors/handlers.py +45 -7
- svc_infra/api/fastapi/middleware/graceful_shutdown.py +87 -0
- svc_infra/api/fastapi/middleware/idempotency.py +82 -42
- svc_infra/api/fastapi/middleware/idempotency_store.py +187 -0
- svc_infra/api/fastapi/middleware/optimistic_lock.py +37 -0
- svc_infra/api/fastapi/middleware/ratelimit.py +84 -11
- svc_infra/api/fastapi/middleware/ratelimit_store.py +84 -0
- svc_infra/api/fastapi/middleware/request_size_limit.py +36 -0
- svc_infra/api/fastapi/middleware/timeout.py +148 -0
- svc_infra/api/fastapi/openapi/mutators.py +244 -38
- svc_infra/api/fastapi/ops/add.py +73 -0
- svc_infra/api/fastapi/pagination.py +133 -32
- svc_infra/api/fastapi/routers/ping.py +1 -0
- svc_infra/api/fastapi/setup.py +23 -14
- 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/billing/__init__.py +23 -0
- svc_infra/billing/async_service.py +147 -0
- svc_infra/billing/jobs.py +230 -0
- svc_infra/billing/models.py +131 -0
- svc_infra/billing/quotas.py +101 -0
- svc_infra/billing/schemas.py +33 -0
- svc_infra/billing/service.py +115 -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 +4 -0
- svc_infra/cache/add.py +158 -0
- svc_infra/cache/backend.py +5 -2
- svc_infra/cache/decorators.py +19 -1
- svc_infra/cache/keys.py +24 -4
- svc_infra/cli/__init__.py +32 -8
- svc_infra/cli/__main__.py +4 -0
- svc_infra/cli/cmds/__init__.py +10 -0
- svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +4 -3
- svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +4 -4
- svc_infra/cli/cmds/db/sql/alembic_cmds.py +80 -11
- svc_infra/cli/cmds/db/sql/sql_export_cmds.py +80 -0
- svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +3 -3
- svc_infra/cli/cmds/docs/docs_cmds.py +140 -0
- svc_infra/cli/cmds/dx/__init__.py +12 -0
- svc_infra/cli/cmds/dx/dx_cmds.py +99 -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 +43 -0
- svc_infra/cli/cmds/obs/obs_cmds.py +4 -3
- svc_infra/cli/cmds/sdk/__init__.py +0 -0
- svc_infra/cli/cmds/sdk/sdk_cmds.py +102 -0
- svc_infra/data/add.py +61 -0
- svc_infra/data/backup.py +53 -0
- svc_infra/data/erasure.py +45 -0
- svc_infra/data/fixtures.py +40 -0
- svc_infra/data/retention.py +55 -0
- svc_infra/db/inbox.py +67 -0
- svc_infra/db/nosql/mongo/README.md +13 -13
- svc_infra/db/outbox.py +104 -0
- svc_infra/db/sql/repository.py +52 -12
- svc_infra/db/sql/resource.py +5 -0
- svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +1 -1
- svc_infra/db/sql/templates/setup/env_async.py.tmpl +13 -8
- svc_infra/db/sql/templates/setup/env_sync.py.tmpl +9 -5
- svc_infra/db/sql/tenant.py +79 -0
- svc_infra/db/sql/utils.py +18 -4
- svc_infra/db/sql/versioning.py +14 -0
- svc_infra/docs/acceptance-matrix.md +71 -0
- svc_infra/docs/acceptance.md +44 -0
- svc_infra/docs/admin.md +425 -0
- svc_infra/docs/adr/0002-background-jobs-and-scheduling.md +40 -0
- svc_infra/docs/adr/0003-webhooks-framework.md +24 -0
- svc_infra/docs/adr/0004-tenancy-model.md +42 -0
- svc_infra/docs/adr/0005-data-lifecycle.md +86 -0
- svc_infra/docs/adr/0006-ops-slos-and-metrics.md +47 -0
- svc_infra/docs/adr/0007-docs-and-sdks.md +83 -0
- svc_infra/docs/adr/0008-billing-primitives.md +143 -0
- svc_infra/docs/adr/0009-acceptance-harness.md +40 -0
- svc_infra/docs/adr/0010-timeouts-and-resource-limits.md +54 -0
- svc_infra/docs/adr/0011-admin-scope-and-impersonation.md +73 -0
- svc_infra/docs/api.md +59 -0
- svc_infra/docs/auth.md +11 -0
- svc_infra/docs/billing.md +190 -0
- svc_infra/docs/cache.md +76 -0
- svc_infra/docs/cli.md +74 -0
- svc_infra/docs/contributing.md +34 -0
- svc_infra/docs/data-lifecycle.md +52 -0
- svc_infra/docs/database.md +14 -0
- svc_infra/docs/docs-and-sdks.md +62 -0
- svc_infra/docs/environment.md +114 -0
- svc_infra/docs/getting-started.md +63 -0
- svc_infra/docs/idempotency.md +111 -0
- svc_infra/docs/jobs.md +67 -0
- svc_infra/docs/observability.md +16 -0
- svc_infra/docs/ops.md +37 -0
- svc_infra/docs/rate-limiting.md +125 -0
- svc_infra/docs/repo-review.md +48 -0
- svc_infra/docs/security.md +176 -0
- svc_infra/docs/tenancy.md +35 -0
- svc_infra/docs/timeouts-and-resource-limits.md +147 -0
- svc_infra/docs/versioned-integrations.md +146 -0
- svc_infra/docs/webhooks.md +112 -0
- svc_infra/dx/add.py +63 -0
- svc_infra/dx/changelog.py +74 -0
- svc_infra/dx/checks.py +67 -0
- svc_infra/http/__init__.py +13 -0
- svc_infra/http/client.py +72 -0
- svc_infra/jobs/builtins/outbox_processor.py +38 -0
- svc_infra/jobs/builtins/webhook_delivery.py +90 -0
- svc_infra/jobs/easy.py +32 -0
- svc_infra/jobs/loader.py +45 -0
- svc_infra/jobs/queue.py +81 -0
- svc_infra/jobs/redis_queue.py +191 -0
- svc_infra/jobs/runner.py +75 -0
- svc_infra/jobs/scheduler.py +41 -0
- svc_infra/jobs/worker.py +40 -0
- svc_infra/mcp/svc_infra_mcp.py +85 -28
- svc_infra/obs/README.md +2 -0
- svc_infra/obs/add.py +54 -7
- svc_infra/obs/grafana/dashboards/http-overview.json +45 -0
- svc_infra/obs/metrics/__init__.py +53 -0
- svc_infra/obs/metrics.py +52 -0
- svc_infra/security/add.py +201 -0
- svc_infra/security/audit.py +130 -0
- svc_infra/security/audit_service.py +73 -0
- svc_infra/security/headers.py +52 -0
- svc_infra/security/hibp.py +95 -0
- svc_infra/security/jwt_rotation.py +53 -0
- svc_infra/security/lockout.py +96 -0
- svc_infra/security/models.py +255 -0
- svc_infra/security/org_invites.py +128 -0
- svc_infra/security/passwords.py +77 -0
- svc_infra/security/permissions.py +149 -0
- svc_infra/security/session.py +98 -0
- svc_infra/security/signed_cookies.py +80 -0
- svc_infra/webhooks/__init__.py +16 -0
- svc_infra/webhooks/add.py +322 -0
- svc_infra/webhooks/fastapi.py +37 -0
- svc_infra/webhooks/router.py +55 -0
- svc_infra/webhooks/service.py +67 -0
- svc_infra/webhooks/signing.py +30 -0
- svc_infra-0.1.654.dist-info/METADATA +154 -0
- {svc_infra-0.1.562.dist-info → svc_infra-0.1.654.dist-info}/RECORD +174 -56
- svc_infra-0.1.562.dist-info/METADATA +0 -79
- {svc_infra-0.1.562.dist-info → svc_infra-0.1.654.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.562.dist-info → svc_infra-0.1.654.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
from datetime import date, datetime, timezone
|
|
5
|
+
from typing import Any, Optional, Sequence, Tuple
|
|
6
|
+
|
|
7
|
+
from svc_infra.apf_payments.schemas import (
|
|
8
|
+
BalanceAmount,
|
|
9
|
+
BalanceSnapshotOut,
|
|
10
|
+
CustomerOut,
|
|
11
|
+
CustomerUpsertIn,
|
|
12
|
+
DisputeOut,
|
|
13
|
+
IntentCreateIn,
|
|
14
|
+
IntentOut,
|
|
15
|
+
InvoiceCreateIn,
|
|
16
|
+
InvoiceLineItemIn,
|
|
17
|
+
InvoiceLineItemOut,
|
|
18
|
+
InvoiceOut,
|
|
19
|
+
NextAction,
|
|
20
|
+
PaymentMethodAttachIn,
|
|
21
|
+
PaymentMethodOut,
|
|
22
|
+
PaymentMethodUpdateIn,
|
|
23
|
+
PayoutOut,
|
|
24
|
+
PriceCreateIn,
|
|
25
|
+
PriceOut,
|
|
26
|
+
PriceUpdateIn,
|
|
27
|
+
ProductCreateIn,
|
|
28
|
+
ProductOut,
|
|
29
|
+
ProductUpdateIn,
|
|
30
|
+
RefundIn,
|
|
31
|
+
RefundOut,
|
|
32
|
+
SetupIntentCreateIn,
|
|
33
|
+
SetupIntentOut,
|
|
34
|
+
SubscriptionCreateIn,
|
|
35
|
+
SubscriptionOut,
|
|
36
|
+
SubscriptionUpdateIn,
|
|
37
|
+
UsageRecordIn,
|
|
38
|
+
UsageRecordListFilter,
|
|
39
|
+
UsageRecordOut,
|
|
40
|
+
)
|
|
41
|
+
from svc_infra.apf_payments.settings import get_payments_settings
|
|
42
|
+
|
|
43
|
+
from .base import ProviderAdapter
|
|
44
|
+
|
|
45
|
+
try: # pragma: no cover - optional dependency
|
|
46
|
+
import aiydan # type: ignore
|
|
47
|
+
except Exception: # pragma: no cover - handled at runtime
|
|
48
|
+
aiydan = None # type: ignore
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
async def _maybe_await(result: Any) -> Any:
|
|
52
|
+
if inspect.isawaitable(result):
|
|
53
|
+
return await result
|
|
54
|
+
return result
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _coerce_id(data: dict[str, Any], *candidates: str) -> str:
|
|
58
|
+
for key in candidates:
|
|
59
|
+
value = data.get(key)
|
|
60
|
+
if isinstance(value, str) and value:
|
|
61
|
+
return value
|
|
62
|
+
raise RuntimeError(f"Aiydan payload missing id fields: {candidates}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _ensure_utc_isoformat(value: Any) -> Optional[str]:
|
|
66
|
+
if value is None:
|
|
67
|
+
return None
|
|
68
|
+
if isinstance(value, str):
|
|
69
|
+
return value
|
|
70
|
+
if isinstance(value, datetime):
|
|
71
|
+
if value.tzinfo is None:
|
|
72
|
+
value = value.replace(tzinfo=timezone.utc)
|
|
73
|
+
return value.astimezone(timezone.utc).isoformat()
|
|
74
|
+
if isinstance(value, date):
|
|
75
|
+
return datetime(value.year, value.month, value.day, tzinfo=timezone.utc).isoformat()
|
|
76
|
+
try:
|
|
77
|
+
parsed = datetime.fromisoformat(str(value))
|
|
78
|
+
if parsed.tzinfo is None:
|
|
79
|
+
parsed = parsed.replace(tzinfo=timezone.utc)
|
|
80
|
+
return parsed.astimezone(timezone.utc).isoformat()
|
|
81
|
+
except Exception:
|
|
82
|
+
return str(value)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _customer_to_out(data: dict[str, Any]) -> CustomerOut:
|
|
86
|
+
cust_id = _coerce_id(data, "provider_customer_id", "customer_id", "id")
|
|
87
|
+
return CustomerOut(
|
|
88
|
+
id=cust_id,
|
|
89
|
+
provider="aiydan",
|
|
90
|
+
provider_customer_id=cust_id,
|
|
91
|
+
email=data.get("email"),
|
|
92
|
+
name=data.get("name"),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _intent_to_out(data: dict[str, Any]) -> IntentOut:
|
|
97
|
+
intent_id = _coerce_id(data, "provider_intent_id", "intent_id", "id")
|
|
98
|
+
return IntentOut(
|
|
99
|
+
id=intent_id,
|
|
100
|
+
provider="aiydan",
|
|
101
|
+
provider_intent_id=intent_id,
|
|
102
|
+
status=str(data.get("status", "")),
|
|
103
|
+
amount=int(data.get("amount", 0)),
|
|
104
|
+
currency=str(data.get("currency", "")).upper(),
|
|
105
|
+
client_secret=data.get("client_secret"),
|
|
106
|
+
next_action=NextAction(type=(data.get("next_action") or {}).get("type")),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _payment_method_to_out(data: dict[str, Any]) -> PaymentMethodOut:
|
|
111
|
+
method_id = _coerce_id(data, "provider_method_id", "payment_method_id", "id")
|
|
112
|
+
card = data.get("card") or {}
|
|
113
|
+
return PaymentMethodOut(
|
|
114
|
+
id=method_id,
|
|
115
|
+
provider="aiydan",
|
|
116
|
+
provider_customer_id=str(data.get("provider_customer_id") or data.get("customer_id") or ""),
|
|
117
|
+
provider_method_id=method_id,
|
|
118
|
+
brand=card.get("brand") or data.get("brand"),
|
|
119
|
+
last4=card.get("last4") or data.get("last4"),
|
|
120
|
+
exp_month=card.get("exp_month") or data.get("exp_month"),
|
|
121
|
+
exp_year=card.get("exp_year") or data.get("exp_year"),
|
|
122
|
+
is_default=bool(data.get("is_default")),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _product_to_out(data: dict[str, Any]) -> ProductOut:
|
|
127
|
+
product_id = _coerce_id(data, "provider_product_id", "product_id", "id")
|
|
128
|
+
return ProductOut(
|
|
129
|
+
id=product_id,
|
|
130
|
+
provider="aiydan",
|
|
131
|
+
provider_product_id=product_id,
|
|
132
|
+
name=str(data.get("name", "")),
|
|
133
|
+
active=bool(data.get("active", True)),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _price_to_out(data: dict[str, Any]) -> PriceOut:
|
|
138
|
+
price_id = _coerce_id(data, "provider_price_id", "price_id", "id")
|
|
139
|
+
recurring = data.get("recurring") or {}
|
|
140
|
+
return PriceOut(
|
|
141
|
+
id=price_id,
|
|
142
|
+
provider="aiydan",
|
|
143
|
+
provider_price_id=price_id,
|
|
144
|
+
provider_product_id=str(
|
|
145
|
+
data.get("provider_product_id")
|
|
146
|
+
or data.get("product_id")
|
|
147
|
+
or getattr(data.get("product"), "id", "")
|
|
148
|
+
),
|
|
149
|
+
currency=str(data.get("currency", "")).upper(),
|
|
150
|
+
unit_amount=int(data.get("unit_amount", data.get("amount", 0) or 0)),
|
|
151
|
+
interval=str(recurring.get("interval")) if recurring.get("interval") else None,
|
|
152
|
+
trial_days=data.get("trial_days"),
|
|
153
|
+
active=bool(data.get("active", True)),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _subscription_to_out(data: dict[str, Any]) -> SubscriptionOut:
|
|
158
|
+
sub_id = _coerce_id(data, "provider_subscription_id", "subscription_id", "id")
|
|
159
|
+
items = data.get("items") or {}
|
|
160
|
+
first_item = None
|
|
161
|
+
if isinstance(items, dict):
|
|
162
|
+
first_item = (items.get("data") or [None])[0]
|
|
163
|
+
elif isinstance(items, Sequence):
|
|
164
|
+
first_item = items[0] if items else None
|
|
165
|
+
price_id = (
|
|
166
|
+
first_item.get("price")
|
|
167
|
+
if isinstance(first_item, dict)
|
|
168
|
+
else getattr(first_item, "price", None)
|
|
169
|
+
)
|
|
170
|
+
if isinstance(price_id, dict):
|
|
171
|
+
price_id = price_id.get("id")
|
|
172
|
+
elif price_id is not None and not isinstance(price_id, str):
|
|
173
|
+
price_id = getattr(price_id, "id", None)
|
|
174
|
+
quantity = (
|
|
175
|
+
first_item.get("quantity")
|
|
176
|
+
if isinstance(first_item, dict)
|
|
177
|
+
else getattr(first_item, "quantity", 0)
|
|
178
|
+
)
|
|
179
|
+
return SubscriptionOut(
|
|
180
|
+
id=sub_id,
|
|
181
|
+
provider="aiydan",
|
|
182
|
+
provider_subscription_id=sub_id,
|
|
183
|
+
provider_price_id=price_id or "",
|
|
184
|
+
status=str(data.get("status", "")),
|
|
185
|
+
quantity=int(quantity or 0),
|
|
186
|
+
cancel_at_period_end=bool(data.get("cancel_at_period_end", False)),
|
|
187
|
+
current_period_end=_ensure_utc_isoformat(data.get("current_period_end")),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _invoice_to_out(data: dict[str, Any]) -> InvoiceOut:
|
|
192
|
+
invoice_id = _coerce_id(data, "provider_invoice_id", "invoice_id", "id")
|
|
193
|
+
return InvoiceOut(
|
|
194
|
+
id=invoice_id,
|
|
195
|
+
provider="aiydan",
|
|
196
|
+
provider_invoice_id=invoice_id,
|
|
197
|
+
provider_customer_id=str(data.get("provider_customer_id") or data.get("customer_id") or ""),
|
|
198
|
+
status=str(data.get("status", "")),
|
|
199
|
+
amount_due=int(data.get("amount_due", data.get("amount") or 0) or 0),
|
|
200
|
+
currency=str(data.get("currency", "")).upper(),
|
|
201
|
+
hosted_invoice_url=data.get("hosted_invoice_url") or data.get("hosted_url"),
|
|
202
|
+
pdf_url=data.get("pdf_url") or data.get("invoice_pdf"),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _invoice_line_item_to_out(data: dict[str, Any]) -> InvoiceLineItemOut:
|
|
207
|
+
line_id = _coerce_id(data, "provider_invoice_line_item_id", "line_id", "id")
|
|
208
|
+
price = data.get("price") or {}
|
|
209
|
+
if not isinstance(price, dict):
|
|
210
|
+
price = {"id": getattr(price, "id", None)}
|
|
211
|
+
return InvoiceLineItemOut(
|
|
212
|
+
id=line_id,
|
|
213
|
+
description=data.get("description"),
|
|
214
|
+
currency=str(data.get("currency", price.get("currency", ""))).upper(),
|
|
215
|
+
quantity=int(data.get("quantity", 0) or 0),
|
|
216
|
+
unit_amount=int(data.get("unit_amount", data.get("amount", 0) or 0)),
|
|
217
|
+
provider_price_id=price.get("id"),
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _refund_to_out(data: dict[str, Any]) -> RefundOut:
|
|
222
|
+
refund_id = _coerce_id(data, "provider_refund_id", "refund_id", "id")
|
|
223
|
+
return RefundOut(
|
|
224
|
+
id=refund_id,
|
|
225
|
+
provider="aiydan",
|
|
226
|
+
provider_refund_id=refund_id,
|
|
227
|
+
provider_payment_intent_id=str(
|
|
228
|
+
data.get("provider_payment_intent_id") or data.get("payment_intent_id") or ""
|
|
229
|
+
),
|
|
230
|
+
amount=int(data.get("amount", 0) or 0),
|
|
231
|
+
currency=str(data.get("currency", "")).upper(),
|
|
232
|
+
status=str(data.get("status", "")),
|
|
233
|
+
reason=data.get("reason"),
|
|
234
|
+
created_at=_ensure_utc_isoformat(data.get("created_at") or data.get("created")),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _dispute_to_out(data: dict[str, Any]) -> DisputeOut:
|
|
239
|
+
dispute_id = _coerce_id(data, "provider_dispute_id", "dispute_id", "id")
|
|
240
|
+
evidence = data.get("evidence") or {}
|
|
241
|
+
return DisputeOut(
|
|
242
|
+
id=dispute_id,
|
|
243
|
+
provider="aiydan",
|
|
244
|
+
provider_dispute_id=dispute_id,
|
|
245
|
+
amount=int(data.get("amount", 0) or 0),
|
|
246
|
+
currency=str(data.get("currency", "")).upper(),
|
|
247
|
+
reason=data.get("reason"),
|
|
248
|
+
status=str(data.get("status", "")),
|
|
249
|
+
evidence_due_by=_ensure_utc_isoformat(
|
|
250
|
+
evidence.get("due_by") or data.get("evidence_due_by")
|
|
251
|
+
),
|
|
252
|
+
created_at=_ensure_utc_isoformat(data.get("created_at") or data.get("created")),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _payout_to_out(data: dict[str, Any]) -> PayoutOut:
|
|
257
|
+
payout_id = _coerce_id(data, "provider_payout_id", "payout_id", "id")
|
|
258
|
+
return PayoutOut(
|
|
259
|
+
id=payout_id,
|
|
260
|
+
provider="aiydan",
|
|
261
|
+
provider_payout_id=payout_id,
|
|
262
|
+
amount=int(data.get("amount", 0) or 0),
|
|
263
|
+
currency=str(data.get("currency", "")).upper(),
|
|
264
|
+
status=str(data.get("status", "")),
|
|
265
|
+
arrival_date=_ensure_utc_isoformat(data.get("arrival_date")),
|
|
266
|
+
type=data.get("type"),
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _usage_record_to_out(data: dict[str, Any]) -> UsageRecordOut:
|
|
271
|
+
return UsageRecordOut(
|
|
272
|
+
id=str(data.get("id")),
|
|
273
|
+
quantity=int(data.get("quantity", 0) or 0),
|
|
274
|
+
timestamp=data.get("timestamp"),
|
|
275
|
+
subscription_item=(
|
|
276
|
+
str(data.get("subscription_item")) if data.get("subscription_item") else None
|
|
277
|
+
),
|
|
278
|
+
provider_price_id=(
|
|
279
|
+
str(data.get("provider_price_id")) if data.get("provider_price_id") else None
|
|
280
|
+
),
|
|
281
|
+
action=(str(data.get("action")) if data.get("action") else None),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _balance_snapshot_to_out(data: dict[str, Any]) -> BalanceSnapshotOut:
|
|
286
|
+
def _normalize(side: Any) -> list[dict[str, Any]]:
|
|
287
|
+
if isinstance(side, list):
|
|
288
|
+
out: list[dict[str, Any]] = []
|
|
289
|
+
for item in side:
|
|
290
|
+
if isinstance(item, dict) and "currency" in item and "amount" in item:
|
|
291
|
+
out.append(
|
|
292
|
+
{
|
|
293
|
+
"currency": str(item["currency"]).upper(),
|
|
294
|
+
"amount": int(item["amount"] or 0),
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
return out
|
|
298
|
+
if isinstance(side, dict):
|
|
299
|
+
return [
|
|
300
|
+
{"currency": str(cur).upper(), "amount": int(amt or 0)} for cur, amt in side.items()
|
|
301
|
+
]
|
|
302
|
+
return []
|
|
303
|
+
|
|
304
|
+
return BalanceSnapshotOut(
|
|
305
|
+
available=[
|
|
306
|
+
BalanceAmount(currency=i["currency"], amount=i["amount"])
|
|
307
|
+
for i in _normalize(data.get("available"))
|
|
308
|
+
],
|
|
309
|
+
pending=[
|
|
310
|
+
BalanceAmount(currency=i["currency"], amount=i["amount"])
|
|
311
|
+
for i in _normalize(data.get("pending"))
|
|
312
|
+
],
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def _ensure_sequence(result: Any) -> Sequence[dict[str, Any]]:
|
|
317
|
+
if isinstance(result, Sequence):
|
|
318
|
+
return result # type: ignore[arg-type]
|
|
319
|
+
if isinstance(result, dict):
|
|
320
|
+
items = result.get("items")
|
|
321
|
+
if isinstance(items, Sequence):
|
|
322
|
+
return items # type: ignore[arg-type]
|
|
323
|
+
raise RuntimeError("Expected sequence payload from Aiydan client")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _ensure_list_response(result: Any) -> Tuple[Sequence[dict[str, Any]], Optional[str]]:
|
|
327
|
+
if isinstance(result, tuple) and len(result) == 2:
|
|
328
|
+
items, cursor = result
|
|
329
|
+
if isinstance(items, Sequence) or items is None:
|
|
330
|
+
return (items or []), cursor # type: ignore[arg-type]
|
|
331
|
+
if isinstance(result, dict):
|
|
332
|
+
items = result.get("items")
|
|
333
|
+
cursor = result.get("next_cursor") or result.get("cursor")
|
|
334
|
+
if isinstance(items, Sequence):
|
|
335
|
+
return items, cursor
|
|
336
|
+
if isinstance(result, Sequence):
|
|
337
|
+
return result, None # type: ignore[arg-type]
|
|
338
|
+
raise RuntimeError("Expected iterable response from Aiydan client")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class AiydanAdapter(ProviderAdapter):
|
|
342
|
+
name = "aiydan"
|
|
343
|
+
|
|
344
|
+
def __init__(self, *, client: Optional[Any] = None):
|
|
345
|
+
settings = get_payments_settings()
|
|
346
|
+
cfg = settings.aiydan
|
|
347
|
+
if client is not None:
|
|
348
|
+
self._client = client
|
|
349
|
+
self._webhook_secret = (
|
|
350
|
+
cfg.webhook_secret.get_secret_value() if cfg and cfg.webhook_secret else None
|
|
351
|
+
)
|
|
352
|
+
return
|
|
353
|
+
if cfg is None:
|
|
354
|
+
raise RuntimeError("Aiydan settings not configured")
|
|
355
|
+
if aiydan is None:
|
|
356
|
+
raise RuntimeError("aiydan SDK is not installed. pip install aiydan")
|
|
357
|
+
client_class = getattr(aiydan, "Client", None)
|
|
358
|
+
if client_class is None:
|
|
359
|
+
raise RuntimeError("aiydan SDK missing 'Client' class")
|
|
360
|
+
kwargs: dict[str, Any] = {"api_key": cfg.api_key.get_secret_value()}
|
|
361
|
+
if cfg.client_key:
|
|
362
|
+
kwargs["client_key"] = cfg.client_key.get_secret_value()
|
|
363
|
+
if cfg.merchant_account:
|
|
364
|
+
kwargs["merchant_account"] = cfg.merchant_account
|
|
365
|
+
if cfg.hmac_key:
|
|
366
|
+
kwargs["hmac_key"] = cfg.hmac_key.get_secret_value()
|
|
367
|
+
if cfg.base_url:
|
|
368
|
+
kwargs["base_url"] = cfg.base_url
|
|
369
|
+
self._client = client_class(**kwargs)
|
|
370
|
+
self._webhook_secret = cfg.webhook_secret.get_secret_value() if cfg.webhook_secret else None
|
|
371
|
+
|
|
372
|
+
async def ensure_customer(self, data: CustomerUpsertIn) -> CustomerOut:
|
|
373
|
+
payload = data.model_dump(exclude_none=True)
|
|
374
|
+
result = await _maybe_await(self._client.ensure_customer(payload))
|
|
375
|
+
return _customer_to_out(result)
|
|
376
|
+
|
|
377
|
+
async def attach_payment_method(self, data: PaymentMethodAttachIn) -> PaymentMethodOut:
|
|
378
|
+
payload = data.model_dump(exclude_none=True)
|
|
379
|
+
result = await _maybe_await(self._client.attach_payment_method(payload))
|
|
380
|
+
return _payment_method_to_out(result)
|
|
381
|
+
|
|
382
|
+
async def list_payment_methods(self, provider_customer_id: str) -> list[PaymentMethodOut]:
|
|
383
|
+
result = await _maybe_await(self._client.list_payment_methods(provider_customer_id))
|
|
384
|
+
methods = _ensure_sequence(result)
|
|
385
|
+
return [_payment_method_to_out(method) for method in methods]
|
|
386
|
+
|
|
387
|
+
async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
388
|
+
result = await _maybe_await(self._client.detach_payment_method(provider_method_id))
|
|
389
|
+
return _payment_method_to_out(result)
|
|
390
|
+
|
|
391
|
+
async def set_default_payment_method(
|
|
392
|
+
self, provider_customer_id: str, provider_method_id: str
|
|
393
|
+
) -> PaymentMethodOut:
|
|
394
|
+
result = await _maybe_await(
|
|
395
|
+
self._client.set_default_payment_method(provider_customer_id, provider_method_id)
|
|
396
|
+
)
|
|
397
|
+
return _payment_method_to_out(result)
|
|
398
|
+
|
|
399
|
+
async def get_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
400
|
+
result = await _maybe_await(self._client.get_payment_method(provider_method_id))
|
|
401
|
+
return _payment_method_to_out(result)
|
|
402
|
+
|
|
403
|
+
async def update_payment_method(
|
|
404
|
+
self, provider_method_id: str, data: PaymentMethodUpdateIn
|
|
405
|
+
) -> PaymentMethodOut:
|
|
406
|
+
payload = data.model_dump(exclude_none=True)
|
|
407
|
+
result = await _maybe_await(self._client.update_payment_method(provider_method_id, payload))
|
|
408
|
+
return _payment_method_to_out(result)
|
|
409
|
+
|
|
410
|
+
async def create_product(self, data: ProductCreateIn) -> ProductOut:
|
|
411
|
+
payload = data.model_dump(exclude_none=True)
|
|
412
|
+
result = await _maybe_await(self._client.create_product(payload))
|
|
413
|
+
return _product_to_out(result)
|
|
414
|
+
|
|
415
|
+
async def get_product(self, provider_product_id: str) -> ProductOut:
|
|
416
|
+
result = await _maybe_await(self._client.get_product(provider_product_id))
|
|
417
|
+
return _product_to_out(result)
|
|
418
|
+
|
|
419
|
+
async def list_products(
|
|
420
|
+
self, *, active: bool | None, limit: int, cursor: str | None
|
|
421
|
+
) -> tuple[list[ProductOut], str | None]:
|
|
422
|
+
result = await _maybe_await(
|
|
423
|
+
self._client.list_products(active=active, limit=limit, cursor=cursor)
|
|
424
|
+
)
|
|
425
|
+
items, next_cursor = _ensure_list_response(result)
|
|
426
|
+
return [_product_to_out(item) for item in items], next_cursor
|
|
427
|
+
|
|
428
|
+
async def update_product(self, provider_product_id: str, data: ProductUpdateIn) -> ProductOut:
|
|
429
|
+
payload = data.model_dump(exclude_none=True)
|
|
430
|
+
result = await _maybe_await(self._client.update_product(provider_product_id, payload))
|
|
431
|
+
return _product_to_out(result)
|
|
432
|
+
|
|
433
|
+
async def create_price(self, data: PriceCreateIn) -> PriceOut:
|
|
434
|
+
payload = data.model_dump(exclude_none=True)
|
|
435
|
+
result = await _maybe_await(self._client.create_price(payload))
|
|
436
|
+
return _price_to_out(result)
|
|
437
|
+
|
|
438
|
+
async def get_price(self, provider_price_id: str) -> PriceOut:
|
|
439
|
+
result = await _maybe_await(self._client.get_price(provider_price_id))
|
|
440
|
+
return _price_to_out(result)
|
|
441
|
+
|
|
442
|
+
async def list_prices(
|
|
443
|
+
self,
|
|
444
|
+
*,
|
|
445
|
+
provider_product_id: str | None,
|
|
446
|
+
active: bool | None,
|
|
447
|
+
limit: int,
|
|
448
|
+
cursor: str | None,
|
|
449
|
+
) -> tuple[list[PriceOut], str | None]:
|
|
450
|
+
result = await _maybe_await(
|
|
451
|
+
self._client.list_prices(
|
|
452
|
+
provider_product_id=provider_product_id,
|
|
453
|
+
active=active,
|
|
454
|
+
limit=limit,
|
|
455
|
+
cursor=cursor,
|
|
456
|
+
)
|
|
457
|
+
)
|
|
458
|
+
items, next_cursor = _ensure_list_response(result)
|
|
459
|
+
return [_price_to_out(item) for item in items], next_cursor
|
|
460
|
+
|
|
461
|
+
async def update_price(self, provider_price_id: str, data: PriceUpdateIn) -> PriceOut:
|
|
462
|
+
payload = data.model_dump(exclude_none=True)
|
|
463
|
+
result = await _maybe_await(self._client.update_price(provider_price_id, payload))
|
|
464
|
+
return _price_to_out(result)
|
|
465
|
+
|
|
466
|
+
async def create_subscription(self, data: SubscriptionCreateIn) -> SubscriptionOut:
|
|
467
|
+
payload = data.model_dump(exclude_none=True)
|
|
468
|
+
result = await _maybe_await(self._client.create_subscription(payload))
|
|
469
|
+
return _subscription_to_out(result)
|
|
470
|
+
|
|
471
|
+
async def update_subscription(
|
|
472
|
+
self, provider_subscription_id: str, data: SubscriptionUpdateIn
|
|
473
|
+
) -> SubscriptionOut:
|
|
474
|
+
payload = data.model_dump(exclude_none=True)
|
|
475
|
+
result = await _maybe_await(
|
|
476
|
+
self._client.update_subscription(provider_subscription_id, payload)
|
|
477
|
+
)
|
|
478
|
+
return _subscription_to_out(result)
|
|
479
|
+
|
|
480
|
+
async def cancel_subscription(
|
|
481
|
+
self, provider_subscription_id: str, at_period_end: bool = True
|
|
482
|
+
) -> SubscriptionOut:
|
|
483
|
+
result = await _maybe_await(
|
|
484
|
+
self._client.cancel_subscription(provider_subscription_id, at_period_end)
|
|
485
|
+
)
|
|
486
|
+
return _subscription_to_out(result)
|
|
487
|
+
|
|
488
|
+
async def get_subscription(self, provider_subscription_id: str) -> SubscriptionOut:
|
|
489
|
+
result = await _maybe_await(self._client.get_subscription(provider_subscription_id))
|
|
490
|
+
return _subscription_to_out(result)
|
|
491
|
+
|
|
492
|
+
async def list_subscriptions(
|
|
493
|
+
self,
|
|
494
|
+
*,
|
|
495
|
+
customer_provider_id: str | None,
|
|
496
|
+
status: str | None,
|
|
497
|
+
limit: int,
|
|
498
|
+
cursor: str | None,
|
|
499
|
+
) -> tuple[list[SubscriptionOut], str | None]:
|
|
500
|
+
result = await _maybe_await(
|
|
501
|
+
self._client.list_subscriptions(
|
|
502
|
+
customer_provider_id=customer_provider_id,
|
|
503
|
+
status=status,
|
|
504
|
+
limit=limit,
|
|
505
|
+
cursor=cursor,
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
items, next_cursor = _ensure_list_response(result)
|
|
509
|
+
return [_subscription_to_out(item) for item in items], next_cursor
|
|
510
|
+
|
|
511
|
+
async def create_invoice(self, data: InvoiceCreateIn) -> InvoiceOut:
|
|
512
|
+
payload = data.model_dump(exclude_none=True)
|
|
513
|
+
result = await _maybe_await(self._client.create_invoice(payload))
|
|
514
|
+
return _invoice_to_out(result)
|
|
515
|
+
|
|
516
|
+
async def finalize_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
517
|
+
result = await _maybe_await(self._client.finalize_invoice(provider_invoice_id))
|
|
518
|
+
return _invoice_to_out(result)
|
|
519
|
+
|
|
520
|
+
async def void_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
521
|
+
result = await _maybe_await(self._client.void_invoice(provider_invoice_id))
|
|
522
|
+
return _invoice_to_out(result)
|
|
523
|
+
|
|
524
|
+
async def pay_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
525
|
+
result = await _maybe_await(self._client.pay_invoice(provider_invoice_id))
|
|
526
|
+
return _invoice_to_out(result)
|
|
527
|
+
|
|
528
|
+
async def add_invoice_line_item(
|
|
529
|
+
self, provider_invoice_id: str, data: InvoiceLineItemIn
|
|
530
|
+
) -> InvoiceOut:
|
|
531
|
+
payload = data.model_dump(exclude_none=True)
|
|
532
|
+
result = await _maybe_await(
|
|
533
|
+
self._client.add_invoice_line_item(provider_invoice_id, payload)
|
|
534
|
+
)
|
|
535
|
+
return _invoice_to_out(result)
|
|
536
|
+
|
|
537
|
+
async def list_invoices(
|
|
538
|
+
self,
|
|
539
|
+
*,
|
|
540
|
+
customer_provider_id: str | None,
|
|
541
|
+
status: str | None,
|
|
542
|
+
limit: int,
|
|
543
|
+
cursor: str | None,
|
|
544
|
+
) -> tuple[list[InvoiceOut], str | None]:
|
|
545
|
+
result = await _maybe_await(
|
|
546
|
+
self._client.list_invoices(
|
|
547
|
+
customer_provider_id=customer_provider_id,
|
|
548
|
+
status=status,
|
|
549
|
+
limit=limit,
|
|
550
|
+
cursor=cursor,
|
|
551
|
+
)
|
|
552
|
+
)
|
|
553
|
+
items, next_cursor = _ensure_list_response(result)
|
|
554
|
+
return [_invoice_to_out(item) for item in items], next_cursor
|
|
555
|
+
|
|
556
|
+
async def get_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
557
|
+
result = await _maybe_await(self._client.get_invoice(provider_invoice_id))
|
|
558
|
+
return _invoice_to_out(result)
|
|
559
|
+
|
|
560
|
+
async def preview_invoice(
|
|
561
|
+
self, *, customer_provider_id: str, subscription_id: str | None = None
|
|
562
|
+
) -> InvoiceOut:
|
|
563
|
+
result = await _maybe_await(
|
|
564
|
+
self._client.preview_invoice(
|
|
565
|
+
customer_provider_id=customer_provider_id,
|
|
566
|
+
subscription_id=subscription_id,
|
|
567
|
+
)
|
|
568
|
+
)
|
|
569
|
+
return _invoice_to_out(result)
|
|
570
|
+
|
|
571
|
+
async def list_invoice_line_items(
|
|
572
|
+
self, provider_invoice_id: str, *, limit: int, cursor: str | None
|
|
573
|
+
) -> tuple[list[InvoiceLineItemOut], str | None]:
|
|
574
|
+
result = await _maybe_await(
|
|
575
|
+
self._client.list_invoice_line_items(
|
|
576
|
+
provider_invoice_id,
|
|
577
|
+
limit=limit,
|
|
578
|
+
cursor=cursor,
|
|
579
|
+
)
|
|
580
|
+
)
|
|
581
|
+
items, next_cursor = _ensure_list_response(result)
|
|
582
|
+
return [_invoice_line_item_to_out(item) for item in items], next_cursor
|
|
583
|
+
|
|
584
|
+
async def create_intent(self, data: IntentCreateIn, *, user_id: str | None) -> IntentOut:
|
|
585
|
+
payload = data.model_dump(exclude_none=True)
|
|
586
|
+
if user_id is not None:
|
|
587
|
+
payload["user_id"] = user_id
|
|
588
|
+
result = await _maybe_await(self._client.create_intent(payload))
|
|
589
|
+
return _intent_to_out(result)
|
|
590
|
+
|
|
591
|
+
async def confirm_intent(self, provider_intent_id: str) -> IntentOut:
|
|
592
|
+
result = await _maybe_await(self._client.confirm_intent(provider_intent_id))
|
|
593
|
+
return _intent_to_out(result)
|
|
594
|
+
|
|
595
|
+
async def cancel_intent(self, provider_intent_id: str) -> IntentOut:
|
|
596
|
+
result = await _maybe_await(self._client.cancel_intent(provider_intent_id))
|
|
597
|
+
return _intent_to_out(result)
|
|
598
|
+
|
|
599
|
+
async def refund(self, provider_intent_id: str, data: RefundIn) -> IntentOut:
|
|
600
|
+
payload = data.model_dump(exclude_none=True)
|
|
601
|
+
result = await _maybe_await(self._client.refund_intent(provider_intent_id, payload))
|
|
602
|
+
return _intent_to_out(result)
|
|
603
|
+
|
|
604
|
+
async def hydrate_intent(self, provider_intent_id: str) -> IntentOut:
|
|
605
|
+
result = await _maybe_await(self._client.get_intent(provider_intent_id))
|
|
606
|
+
return _intent_to_out(result)
|
|
607
|
+
|
|
608
|
+
async def capture_intent(self, provider_intent_id: str, *, amount: int | None) -> IntentOut:
|
|
609
|
+
result = await _maybe_await(self._client.capture_intent(provider_intent_id, amount=amount))
|
|
610
|
+
return _intent_to_out(result)
|
|
611
|
+
|
|
612
|
+
async def list_intents(
|
|
613
|
+
self,
|
|
614
|
+
*,
|
|
615
|
+
customer_provider_id: str | None,
|
|
616
|
+
status: str | None,
|
|
617
|
+
limit: int,
|
|
618
|
+
cursor: str | None,
|
|
619
|
+
) -> tuple[list[IntentOut], str | None]:
|
|
620
|
+
result = await _maybe_await(
|
|
621
|
+
self._client.list_intents(
|
|
622
|
+
customer_provider_id=customer_provider_id,
|
|
623
|
+
status=status,
|
|
624
|
+
limit=limit,
|
|
625
|
+
cursor=cursor,
|
|
626
|
+
)
|
|
627
|
+
)
|
|
628
|
+
items, next_cursor = _ensure_list_response(result)
|
|
629
|
+
return [_intent_to_out(item) for item in items], next_cursor
|
|
630
|
+
|
|
631
|
+
async def verify_and_parse_webhook(
|
|
632
|
+
self, signature: str | None, payload: bytes
|
|
633
|
+
) -> dict[str, Any]:
|
|
634
|
+
if hasattr(self._client, "verify_and_parse_webhook"):
|
|
635
|
+
result = await _maybe_await(
|
|
636
|
+
self._client.verify_and_parse_webhook(
|
|
637
|
+
signature=signature,
|
|
638
|
+
payload=payload,
|
|
639
|
+
secret=self._webhook_secret,
|
|
640
|
+
)
|
|
641
|
+
)
|
|
642
|
+
elif hasattr(self._client, "verify_webhook"):
|
|
643
|
+
result = await _maybe_await(
|
|
644
|
+
self._client.verify_webhook(
|
|
645
|
+
payload=payload,
|
|
646
|
+
signature=signature,
|
|
647
|
+
secret=self._webhook_secret,
|
|
648
|
+
)
|
|
649
|
+
)
|
|
650
|
+
else:
|
|
651
|
+
raise RuntimeError("Aiydan client missing webhook verification method")
|
|
652
|
+
if not isinstance(result, dict):
|
|
653
|
+
raise RuntimeError("Aiydan client returned unexpected webhook payload")
|
|
654
|
+
return result
|
|
655
|
+
|
|
656
|
+
async def list_disputes(
|
|
657
|
+
self, *, status: str | None, limit: int, cursor: str | None
|
|
658
|
+
) -> tuple[list[DisputeOut], str | None]:
|
|
659
|
+
result = await _maybe_await(
|
|
660
|
+
self._client.list_disputes(status=status, limit=limit, cursor=cursor)
|
|
661
|
+
)
|
|
662
|
+
items, next_cursor = _ensure_list_response(result)
|
|
663
|
+
return [_dispute_to_out(item) for item in items], next_cursor
|
|
664
|
+
|
|
665
|
+
async def get_dispute(self, provider_dispute_id: str) -> DisputeOut:
|
|
666
|
+
result = await _maybe_await(self._client.get_dispute(provider_dispute_id))
|
|
667
|
+
return _dispute_to_out(result)
|
|
668
|
+
|
|
669
|
+
async def submit_dispute_evidence(self, provider_dispute_id: str, evidence: dict) -> DisputeOut:
|
|
670
|
+
result = await _maybe_await(
|
|
671
|
+
self._client.submit_dispute_evidence(provider_dispute_id, evidence)
|
|
672
|
+
)
|
|
673
|
+
return _dispute_to_out(result)
|
|
674
|
+
|
|
675
|
+
async def get_balance_snapshot(self) -> BalanceSnapshotOut:
|
|
676
|
+
result = await _maybe_await(self._client.get_balance_snapshot())
|
|
677
|
+
if isinstance(result, BalanceSnapshotOut):
|
|
678
|
+
return result
|
|
679
|
+
if not isinstance(result, dict):
|
|
680
|
+
raise RuntimeError("Aiydan client returned unexpected balance payload")
|
|
681
|
+
return _balance_snapshot_to_out(result)
|
|
682
|
+
|
|
683
|
+
async def list_payouts(
|
|
684
|
+
self, *, limit: int, cursor: str | None
|
|
685
|
+
) -> tuple[list[PayoutOut], str | None]:
|
|
686
|
+
result = await _maybe_await(self._client.list_payouts(limit=limit, cursor=cursor))
|
|
687
|
+
items, next_cursor = _ensure_list_response(result)
|
|
688
|
+
return [_payout_to_out(item) for item in items], next_cursor
|
|
689
|
+
|
|
690
|
+
async def get_payout(self, provider_payout_id: str) -> PayoutOut:
|
|
691
|
+
result = await _maybe_await(self._client.get_payout(provider_payout_id))
|
|
692
|
+
return _payout_to_out(result)
|
|
693
|
+
|
|
694
|
+
async def list_refunds(
|
|
695
|
+
self,
|
|
696
|
+
*,
|
|
697
|
+
provider_payment_intent_id: str | None,
|
|
698
|
+
limit: int,
|
|
699
|
+
cursor: str | None,
|
|
700
|
+
) -> tuple[list[RefundOut], str | None]:
|
|
701
|
+
result = await _maybe_await(
|
|
702
|
+
self._client.list_refunds(
|
|
703
|
+
provider_payment_intent_id=provider_payment_intent_id,
|
|
704
|
+
limit=limit,
|
|
705
|
+
cursor=cursor,
|
|
706
|
+
)
|
|
707
|
+
)
|
|
708
|
+
items, next_cursor = _ensure_list_response(result)
|
|
709
|
+
return [_refund_to_out(item) for item in items], next_cursor
|
|
710
|
+
|
|
711
|
+
async def get_refund(self, provider_refund_id: str) -> RefundOut:
|
|
712
|
+
result = await _maybe_await(self._client.get_refund(provider_refund_id))
|
|
713
|
+
return _refund_to_out(result)
|
|
714
|
+
|
|
715
|
+
async def create_usage_record(self, data: UsageRecordIn) -> UsageRecordOut:
|
|
716
|
+
payload = data.model_dump(exclude_none=True)
|
|
717
|
+
result = await _maybe_await(self._client.create_usage_record(payload))
|
|
718
|
+
return _usage_record_to_out(result)
|
|
719
|
+
|
|
720
|
+
async def list_usage_records(
|
|
721
|
+
self, f: UsageRecordListFilter
|
|
722
|
+
) -> tuple[list[UsageRecordOut], str | None]:
|
|
723
|
+
payload = f.model_dump(exclude_none=True)
|
|
724
|
+
result = await _maybe_await(self._client.list_usage_records(payload))
|
|
725
|
+
items, next_cursor = _ensure_list_response(result)
|
|
726
|
+
return [_usage_record_to_out(item) for item in items], next_cursor
|
|
727
|
+
|
|
728
|
+
async def get_usage_record(self, usage_record_id: str) -> UsageRecordOut:
|
|
729
|
+
result = await _maybe_await(self._client.get_usage_record(usage_record_id))
|
|
730
|
+
return _usage_record_to_out(result)
|
|
731
|
+
|
|
732
|
+
async def create_setup_intent(self, data: SetupIntentCreateIn) -> SetupIntentOut:
|
|
733
|
+
payload = data.model_dump(exclude_none=True)
|
|
734
|
+
result = await _maybe_await(self._client.create_setup_intent(payload))
|
|
735
|
+
return SetupIntentOut(
|
|
736
|
+
id=_coerce_id(result, "provider_setup_intent_id", "setup_intent_id", "id"),
|
|
737
|
+
provider="aiydan",
|
|
738
|
+
provider_setup_intent_id=_coerce_id(
|
|
739
|
+
result, "provider_setup_intent_id", "setup_intent_id", "id"
|
|
740
|
+
),
|
|
741
|
+
status=str(result.get("status", "")),
|
|
742
|
+
client_secret=result.get("client_secret"),
|
|
743
|
+
next_action=NextAction(type=(result.get("next_action") or {}).get("type")),
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
async def confirm_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
747
|
+
result = await _maybe_await(self._client.confirm_setup_intent(provider_setup_intent_id))
|
|
748
|
+
return SetupIntentOut(
|
|
749
|
+
id=_coerce_id(result, "provider_setup_intent_id", "setup_intent_id", "id"),
|
|
750
|
+
provider="aiydan",
|
|
751
|
+
provider_setup_intent_id=_coerce_id(
|
|
752
|
+
result, "provider_setup_intent_id", "setup_intent_id", "id"
|
|
753
|
+
),
|
|
754
|
+
status=str(result.get("status", "")),
|
|
755
|
+
client_secret=result.get("client_secret"),
|
|
756
|
+
next_action=NextAction(type=(result.get("next_action") or {}).get("type")),
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
async def get_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
760
|
+
result = await _maybe_await(self._client.get_setup_intent(provider_setup_intent_id))
|
|
761
|
+
return SetupIntentOut(
|
|
762
|
+
id=_coerce_id(result, "provider_setup_intent_id", "setup_intent_id", "id"),
|
|
763
|
+
provider="aiydan",
|
|
764
|
+
provider_setup_intent_id=_coerce_id(
|
|
765
|
+
result, "provider_setup_intent_id", "setup_intent_id", "id"
|
|
766
|
+
),
|
|
767
|
+
status=str(result.get("status", "")),
|
|
768
|
+
client_secret=result.get("client_secret"),
|
|
769
|
+
next_action=NextAction(type=(result.get("next_action") or {}).get("type")),
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
async def resume_intent_after_action(self, provider_intent_id: str) -> IntentOut:
|
|
773
|
+
if hasattr(self._client, "resume_intent_after_action"):
|
|
774
|
+
result = await _maybe_await(self._client.resume_intent_after_action(provider_intent_id))
|
|
775
|
+
else:
|
|
776
|
+
result = await _maybe_await(self._client.get_intent(provider_intent_id))
|
|
777
|
+
return _intent_to_out(result)
|
|
778
|
+
|
|
779
|
+
async def list_customers(
|
|
780
|
+
self, *, provider: str | None, user_id: str | None, limit: int, cursor: str | None
|
|
781
|
+
) -> tuple[list[CustomerOut], str | None]:
|
|
782
|
+
result = await _maybe_await(
|
|
783
|
+
self._client.list_customers(
|
|
784
|
+
provider=provider,
|
|
785
|
+
user_id=user_id,
|
|
786
|
+
limit=limit,
|
|
787
|
+
cursor=cursor,
|
|
788
|
+
)
|
|
789
|
+
)
|
|
790
|
+
items, next_cursor = _ensure_list_response(result)
|
|
791
|
+
return [_customer_to_out(item) for item in items], next_cursor
|
|
792
|
+
|
|
793
|
+
async def get_customer(self, provider_customer_id: str) -> Optional[CustomerOut]:
|
|
794
|
+
result = await _maybe_await(self._client.get_customer(provider_customer_id))
|
|
795
|
+
if result is None:
|
|
796
|
+
return None
|
|
797
|
+
return _customer_to_out(result)
|