svc-infra 0.1.706__py3-none-any.whl → 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of svc-infra might be problematic. Click here for more details.
- svc_infra/apf_payments/models.py +47 -108
- svc_infra/apf_payments/provider/__init__.py +2 -2
- svc_infra/apf_payments/provider/aiydan.py +42 -100
- svc_infra/apf_payments/provider/base.py +10 -26
- svc_infra/apf_payments/provider/registry.py +3 -5
- svc_infra/apf_payments/provider/stripe.py +63 -135
- svc_infra/apf_payments/schemas.py +82 -90
- svc_infra/apf_payments/service.py +40 -86
- svc_infra/apf_payments/settings.py +10 -13
- svc_infra/api/__init__.py +13 -13
- svc_infra/api/fastapi/__init__.py +19 -0
- svc_infra/api/fastapi/admin/add.py +13 -18
- svc_infra/api/fastapi/apf_payments/router.py +47 -84
- svc_infra/api/fastapi/apf_payments/setup.py +7 -13
- svc_infra/api/fastapi/auth/__init__.py +1 -1
- svc_infra/api/fastapi/auth/_cookies.py +3 -9
- svc_infra/api/fastapi/auth/add.py +4 -8
- svc_infra/api/fastapi/auth/gaurd.py +9 -26
- svc_infra/api/fastapi/auth/mfa/models.py +4 -7
- svc_infra/api/fastapi/auth/mfa/pre_auth.py +3 -3
- svc_infra/api/fastapi/auth/mfa/router.py +9 -15
- svc_infra/api/fastapi/auth/mfa/security.py +3 -5
- svc_infra/api/fastapi/auth/mfa/utils.py +3 -2
- svc_infra/api/fastapi/auth/mfa/verify.py +2 -9
- svc_infra/api/fastapi/auth/providers.py +4 -6
- svc_infra/api/fastapi/auth/routers/apikey_router.py +16 -18
- svc_infra/api/fastapi/auth/routers/oauth_router.py +37 -85
- svc_infra/api/fastapi/auth/routers/session_router.py +3 -6
- svc_infra/api/fastapi/auth/security.py +17 -28
- svc_infra/api/fastapi/auth/sender.py +1 -3
- svc_infra/api/fastapi/auth/settings.py +18 -19
- svc_infra/api/fastapi/auth/state.py +6 -7
- svc_infra/api/fastapi/auth/ws_security.py +2 -2
- svc_infra/api/fastapi/billing/router.py +6 -8
- svc_infra/api/fastapi/db/http.py +10 -11
- svc_infra/api/fastapi/db/nosql/mongo/add.py +5 -15
- svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +14 -15
- svc_infra/api/fastapi/db/sql/add.py +6 -14
- svc_infra/api/fastapi/db/sql/crud_router.py +27 -40
- svc_infra/api/fastapi/db/sql/health.py +1 -3
- svc_infra/api/fastapi/db/sql/session.py +4 -5
- svc_infra/api/fastapi/db/sql/users.py +8 -11
- svc_infra/api/fastapi/dependencies/ratelimit.py +4 -6
- svc_infra/api/fastapi/docs/add.py +13 -23
- svc_infra/api/fastapi/docs/landing.py +6 -8
- svc_infra/api/fastapi/docs/scoped.py +34 -42
- svc_infra/api/fastapi/dual/dualize.py +1 -1
- svc_infra/api/fastapi/dual/protected.py +12 -21
- svc_infra/api/fastapi/dual/router.py +14 -31
- svc_infra/api/fastapi/ease.py +57 -13
- svc_infra/api/fastapi/http/conditional.py +3 -5
- svc_infra/api/fastapi/middleware/errors/catchall.py +2 -6
- svc_infra/api/fastapi/middleware/errors/exceptions.py +1 -4
- svc_infra/api/fastapi/middleware/errors/handlers.py +12 -18
- svc_infra/api/fastapi/middleware/graceful_shutdown.py +4 -13
- svc_infra/api/fastapi/middleware/idempotency.py +11 -16
- svc_infra/api/fastapi/middleware/idempotency_store.py +14 -14
- svc_infra/api/fastapi/middleware/optimistic_lock.py +5 -8
- svc_infra/api/fastapi/middleware/ratelimit.py +8 -8
- svc_infra/api/fastapi/middleware/ratelimit_store.py +7 -8
- svc_infra/api/fastapi/middleware/request_id.py +1 -3
- svc_infra/api/fastapi/middleware/timeout.py +9 -10
- svc_infra/api/fastapi/object_router.py +1060 -0
- svc_infra/api/fastapi/openapi/apply.py +5 -6
- svc_infra/api/fastapi/openapi/conventions.py +4 -4
- svc_infra/api/fastapi/openapi/mutators.py +13 -31
- svc_infra/api/fastapi/openapi/pipeline.py +2 -2
- svc_infra/api/fastapi/openapi/responses.py +4 -6
- svc_infra/api/fastapi/openapi/security.py +1 -3
- svc_infra/api/fastapi/ops/add.py +7 -9
- svc_infra/api/fastapi/pagination.py +25 -37
- svc_infra/api/fastapi/routers/__init__.py +16 -38
- svc_infra/api/fastapi/setup.py +13 -31
- svc_infra/api/fastapi/tenancy/add.py +3 -2
- svc_infra/api/fastapi/tenancy/context.py +8 -7
- svc_infra/api/fastapi/versioned.py +3 -2
- svc_infra/app/env.py +5 -7
- svc_infra/app/logging/add.py +2 -1
- svc_infra/app/logging/filter.py +1 -1
- svc_infra/app/logging/formats.py +3 -2
- svc_infra/app/root.py +3 -3
- svc_infra/billing/__init__.py +19 -2
- svc_infra/billing/async_service.py +27 -7
- svc_infra/billing/jobs.py +23 -33
- svc_infra/billing/models.py +21 -52
- svc_infra/billing/quotas.py +5 -7
- svc_infra/billing/schemas.py +4 -6
- svc_infra/cache/__init__.py +12 -5
- svc_infra/cache/add.py +6 -9
- svc_infra/cache/backend.py +6 -5
- svc_infra/cache/decorators.py +17 -28
- svc_infra/cache/keys.py +2 -2
- svc_infra/cache/recache.py +22 -35
- svc_infra/cache/resources.py +8 -16
- svc_infra/cache/ttl.py +2 -3
- svc_infra/cache/utils.py +5 -6
- svc_infra/cli/__init__.py +4 -12
- svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +11 -10
- svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +6 -9
- svc_infra/cli/cmds/db/ops_cmds.py +3 -6
- svc_infra/cli/cmds/db/sql/alembic_cmds.py +24 -41
- svc_infra/cli/cmds/db/sql/sql_export_cmds.py +9 -17
- svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +10 -10
- svc_infra/cli/cmds/docs/docs_cmds.py +7 -10
- svc_infra/cli/cmds/dx/dx_cmds.py +5 -11
- svc_infra/cli/cmds/jobs/jobs_cmds.py +2 -7
- svc_infra/cli/cmds/obs/obs_cmds.py +4 -7
- svc_infra/cli/cmds/sdk/sdk_cmds.py +5 -15
- svc_infra/cli/foundation/runner.py +6 -11
- svc_infra/cli/foundation/typer_bootstrap.py +1 -2
- svc_infra/data/__init__.py +83 -0
- svc_infra/data/add.py +5 -5
- svc_infra/data/backup.py +8 -10
- svc_infra/data/erasure.py +3 -2
- svc_infra/data/fixtures.py +3 -3
- svc_infra/data/retention.py +8 -13
- svc_infra/db/crud_schema.py +9 -8
- svc_infra/db/nosql/__init__.py +0 -1
- svc_infra/db/nosql/constants.py +1 -1
- svc_infra/db/nosql/core.py +7 -14
- svc_infra/db/nosql/indexes.py +11 -10
- svc_infra/db/nosql/management.py +3 -3
- svc_infra/db/nosql/mongo/client.py +3 -3
- svc_infra/db/nosql/mongo/settings.py +2 -6
- svc_infra/db/nosql/repository.py +27 -28
- svc_infra/db/nosql/resource.py +15 -20
- svc_infra/db/nosql/scaffold.py +13 -17
- svc_infra/db/nosql/service.py +3 -4
- svc_infra/db/nosql/service_with_hooks.py +4 -3
- svc_infra/db/nosql/types.py +2 -6
- svc_infra/db/nosql/utils.py +4 -4
- svc_infra/db/ops.py +14 -18
- svc_infra/db/outbox.py +15 -18
- svc_infra/db/sql/apikey.py +12 -21
- svc_infra/db/sql/authref.py +3 -7
- svc_infra/db/sql/constants.py +9 -9
- svc_infra/db/sql/core.py +11 -11
- svc_infra/db/sql/management.py +2 -6
- svc_infra/db/sql/repository.py +17 -24
- svc_infra/db/sql/resource.py +14 -13
- svc_infra/db/sql/scaffold.py +13 -17
- svc_infra/db/sql/service.py +7 -16
- svc_infra/db/sql/service_with_hooks.py +4 -3
- svc_infra/db/sql/tenant.py +6 -14
- svc_infra/db/sql/uniq.py +8 -7
- svc_infra/db/sql/uniq_hooks.py +14 -19
- svc_infra/db/sql/utils.py +24 -53
- svc_infra/db/utils.py +3 -3
- svc_infra/deploy/__init__.py +8 -15
- svc_infra/documents/add.py +7 -8
- svc_infra/documents/ease.py +8 -8
- svc_infra/documents/models.py +3 -3
- svc_infra/documents/storage.py +11 -13
- svc_infra/dx/__init__.py +58 -0
- svc_infra/dx/add.py +1 -3
- svc_infra/dx/changelog.py +2 -2
- svc_infra/dx/checks.py +1 -1
- svc_infra/health/__init__.py +15 -16
- svc_infra/http/client.py +10 -14
- svc_infra/jobs/__init__.py +79 -0
- svc_infra/jobs/builtins/outbox_processor.py +3 -5
- svc_infra/jobs/builtins/webhook_delivery.py +1 -3
- svc_infra/jobs/loader.py +4 -5
- svc_infra/jobs/queue.py +14 -24
- svc_infra/jobs/redis_queue.py +20 -34
- svc_infra/jobs/runner.py +7 -11
- svc_infra/jobs/scheduler.py +5 -5
- svc_infra/jobs/worker.py +1 -1
- svc_infra/loaders/base.py +5 -4
- svc_infra/loaders/github.py +1 -3
- svc_infra/loaders/url.py +3 -9
- svc_infra/logging/__init__.py +7 -6
- svc_infra/mcp/__init__.py +82 -0
- svc_infra/mcp/svc_infra_mcp.py +2 -2
- svc_infra/obs/add.py +4 -3
- svc_infra/obs/cloud_dash.py +1 -1
- svc_infra/obs/metrics/__init__.py +3 -3
- svc_infra/obs/metrics/asgi.py +9 -14
- svc_infra/obs/metrics/base.py +13 -13
- svc_infra/obs/metrics/http.py +5 -9
- svc_infra/obs/metrics/sqlalchemy.py +9 -12
- svc_infra/obs/metrics.py +3 -3
- svc_infra/obs/settings.py +2 -6
- svc_infra/resilience/__init__.py +44 -0
- svc_infra/resilience/circuit_breaker.py +328 -0
- svc_infra/resilience/retry.py +289 -0
- svc_infra/security/__init__.py +167 -0
- svc_infra/security/add.py +5 -9
- svc_infra/security/audit.py +14 -17
- svc_infra/security/audit_service.py +9 -9
- svc_infra/security/hibp.py +3 -6
- svc_infra/security/jwt_rotation.py +7 -10
- svc_infra/security/lockout.py +12 -11
- svc_infra/security/models.py +37 -46
- svc_infra/security/oauth_models.py +8 -8
- svc_infra/security/org_invites.py +11 -13
- svc_infra/security/passwords.py +4 -6
- svc_infra/security/permissions.py +8 -7
- svc_infra/security/session.py +6 -7
- svc_infra/security/signed_cookies.py +9 -9
- svc_infra/storage/add.py +5 -8
- svc_infra/storage/backends/local.py +13 -21
- svc_infra/storage/backends/memory.py +4 -7
- svc_infra/storage/backends/s3.py +17 -36
- svc_infra/storage/base.py +2 -2
- svc_infra/storage/easy.py +4 -8
- svc_infra/storage/settings.py +16 -18
- svc_infra/testing/__init__.py +36 -39
- svc_infra/utils.py +169 -8
- svc_infra/webhooks/__init__.py +1 -1
- svc_infra/webhooks/add.py +17 -29
- svc_infra/webhooks/encryption.py +2 -2
- svc_infra/webhooks/fastapi.py +2 -4
- svc_infra/webhooks/router.py +3 -3
- svc_infra/webhooks/service.py +5 -6
- svc_infra/webhooks/signing.py +5 -5
- svc_infra/websocket/add.py +2 -3
- svc_infra/websocket/client.py +3 -2
- svc_infra/websocket/config.py +6 -18
- svc_infra/websocket/manager.py +9 -10
- {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/METADATA +11 -5
- svc_infra-1.1.0.dist-info/RECORD +364 -0
- svc_infra/billing/service.py +0 -123
- svc_infra-0.1.706.dist-info/RECORD +0 -357
- {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/LICENSE +0 -0
- {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.706.dist-info → svc_infra-1.1.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Annotated, Any, Literal
|
|
3
|
+
from typing import Annotated, Any, Literal
|
|
4
4
|
|
|
5
5
|
from pydantic import BaseModel, Field, StringConstraints
|
|
6
6
|
|
|
@@ -10,32 +10,30 @@ AmountMinor = Annotated[int, Field(ge=0)] # minor units (cents)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class CustomerUpsertIn(BaseModel):
|
|
13
|
-
user_id:
|
|
14
|
-
email:
|
|
15
|
-
name:
|
|
13
|
+
user_id: str | None = None
|
|
14
|
+
email: str | None = None
|
|
15
|
+
name: str | None = None
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class CustomerOut(BaseModel):
|
|
19
19
|
id: str
|
|
20
20
|
provider: str
|
|
21
21
|
provider_customer_id: str
|
|
22
|
-
email:
|
|
23
|
-
name:
|
|
22
|
+
email: str | None = None
|
|
23
|
+
name: str | None = None
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class IntentCreateIn(BaseModel):
|
|
27
27
|
amount: AmountMinor = Field(..., description="Minor units (e.g., cents)")
|
|
28
28
|
currency: Currency = Field(..., json_schema_extra={"example": "USD"})
|
|
29
|
-
description:
|
|
29
|
+
description: str | None = None
|
|
30
30
|
capture_method: Literal["automatic", "manual"] = "automatic"
|
|
31
|
-
payment_method_types: list[str] = Field(
|
|
32
|
-
default_factory=list
|
|
33
|
-
) # let provider default
|
|
31
|
+
payment_method_types: list[str] = Field(default_factory=list) # let provider default
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
class NextAction(BaseModel):
|
|
37
|
-
type:
|
|
38
|
-
data:
|
|
35
|
+
type: str | None = None
|
|
36
|
+
data: dict[str, Any] | None = None
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
class IntentOut(BaseModel):
|
|
@@ -45,13 +43,13 @@ class IntentOut(BaseModel):
|
|
|
45
43
|
status: str
|
|
46
44
|
amount: AmountMinor
|
|
47
45
|
currency: Currency
|
|
48
|
-
client_secret:
|
|
49
|
-
next_action:
|
|
46
|
+
client_secret: str | None = None
|
|
47
|
+
next_action: NextAction | None = None
|
|
50
48
|
|
|
51
49
|
|
|
52
50
|
class RefundIn(BaseModel):
|
|
53
|
-
amount:
|
|
54
|
-
reason:
|
|
51
|
+
amount: AmountMinor | None = None
|
|
52
|
+
reason: str | None = None
|
|
55
53
|
|
|
56
54
|
|
|
57
55
|
class TransactionRow(BaseModel):
|
|
@@ -63,9 +61,9 @@ class TransactionRow(BaseModel):
|
|
|
63
61
|
status: str
|
|
64
62
|
provider: str
|
|
65
63
|
provider_ref: str
|
|
66
|
-
user_id:
|
|
67
|
-
net:
|
|
68
|
-
fee:
|
|
64
|
+
user_id: str | None = None
|
|
65
|
+
net: int | None = None
|
|
66
|
+
fee: int | None = None
|
|
69
67
|
|
|
70
68
|
|
|
71
69
|
class StatementRow(BaseModel):
|
|
@@ -90,10 +88,10 @@ class PaymentMethodOut(BaseModel):
|
|
|
90
88
|
provider: str
|
|
91
89
|
provider_customer_id: str
|
|
92
90
|
provider_method_id: str
|
|
93
|
-
brand:
|
|
94
|
-
last4:
|
|
95
|
-
exp_month:
|
|
96
|
-
exp_year:
|
|
91
|
+
brand: str | None = None
|
|
92
|
+
last4: str | None = None
|
|
93
|
+
exp_month: int | None = None
|
|
94
|
+
exp_year: int | None = None
|
|
97
95
|
is_default: bool = False
|
|
98
96
|
|
|
99
97
|
|
|
@@ -114,8 +112,8 @@ class PriceCreateIn(BaseModel):
|
|
|
114
112
|
provider_product_id: str
|
|
115
113
|
currency: Currency
|
|
116
114
|
unit_amount: AmountMinor
|
|
117
|
-
interval:
|
|
118
|
-
trial_days:
|
|
115
|
+
interval: Literal["day", "week", "month", "year"] | None = None
|
|
116
|
+
trial_days: int | None = None
|
|
119
117
|
active: bool = True
|
|
120
118
|
|
|
121
119
|
|
|
@@ -126,8 +124,8 @@ class PriceOut(BaseModel):
|
|
|
126
124
|
provider_product_id: str
|
|
127
125
|
currency: Currency
|
|
128
126
|
unit_amount: AmountMinor
|
|
129
|
-
interval:
|
|
130
|
-
trial_days:
|
|
127
|
+
interval: str | None = None
|
|
128
|
+
trial_days: int | None = None
|
|
131
129
|
active: bool = True
|
|
132
130
|
|
|
133
131
|
|
|
@@ -135,19 +133,15 @@ class SubscriptionCreateIn(BaseModel):
|
|
|
135
133
|
customer_provider_id: str
|
|
136
134
|
price_provider_id: str
|
|
137
135
|
quantity: int = 1
|
|
138
|
-
trial_days:
|
|
139
|
-
proration_behavior: Literal["create_prorations", "none", "always_invoice"] =
|
|
140
|
-
"create_prorations"
|
|
141
|
-
)
|
|
136
|
+
trial_days: int | None = None
|
|
137
|
+
proration_behavior: Literal["create_prorations", "none", "always_invoice"] = "create_prorations"
|
|
142
138
|
|
|
143
139
|
|
|
144
140
|
class SubscriptionUpdateIn(BaseModel):
|
|
145
|
-
price_provider_id:
|
|
146
|
-
quantity:
|
|
147
|
-
cancel_at_period_end:
|
|
148
|
-
proration_behavior: Literal["create_prorations", "none", "always_invoice"] =
|
|
149
|
-
"create_prorations"
|
|
150
|
-
)
|
|
141
|
+
price_provider_id: str | None = None
|
|
142
|
+
quantity: int | None = None
|
|
143
|
+
cancel_at_period_end: bool | None = None
|
|
144
|
+
proration_behavior: Literal["create_prorations", "none", "always_invoice"] = "create_prorations"
|
|
151
145
|
|
|
152
146
|
|
|
153
147
|
class SubscriptionOut(BaseModel):
|
|
@@ -158,7 +152,7 @@ class SubscriptionOut(BaseModel):
|
|
|
158
152
|
status: str
|
|
159
153
|
quantity: int
|
|
160
154
|
cancel_at_period_end: bool
|
|
161
|
-
current_period_end:
|
|
155
|
+
current_period_end: str | None = None
|
|
162
156
|
|
|
163
157
|
|
|
164
158
|
class InvoiceCreateIn(BaseModel):
|
|
@@ -174,47 +168,45 @@ class InvoiceOut(BaseModel):
|
|
|
174
168
|
status: str
|
|
175
169
|
amount_due: AmountMinor
|
|
176
170
|
currency: Currency
|
|
177
|
-
hosted_invoice_url:
|
|
178
|
-
pdf_url:
|
|
171
|
+
hosted_invoice_url: str | None = None
|
|
172
|
+
pdf_url: str | None = None
|
|
179
173
|
|
|
180
174
|
|
|
181
175
|
class CaptureIn(BaseModel):
|
|
182
|
-
amount:
|
|
176
|
+
amount: AmountMinor | None = None # partial capture supported
|
|
183
177
|
|
|
184
178
|
|
|
185
179
|
class IntentListFilter(BaseModel):
|
|
186
|
-
customer_provider_id:
|
|
187
|
-
status:
|
|
188
|
-
limit:
|
|
189
|
-
cursor:
|
|
180
|
+
customer_provider_id: str | None = None
|
|
181
|
+
status: str | None = None
|
|
182
|
+
limit: int | None = Field(default=50, ge=1, le=200)
|
|
183
|
+
cursor: str | None = None # opaque provider cursor when supported
|
|
190
184
|
|
|
191
185
|
|
|
192
186
|
class UsageRecordIn(BaseModel):
|
|
193
187
|
# Stripe: subscription_item is the target for metered billing.
|
|
194
188
|
# If provider doesn't use subscription_item, allow provider_price_id as fallback.
|
|
195
|
-
subscription_item:
|
|
196
|
-
provider_price_id:
|
|
189
|
+
subscription_item: str | None = None
|
|
190
|
+
provider_price_id: str | None = None
|
|
197
191
|
quantity: Annotated[int, Field(ge=0)]
|
|
198
|
-
timestamp:
|
|
199
|
-
action:
|
|
192
|
+
timestamp: int | None = None # Unix seconds; provider defaults to "now"
|
|
193
|
+
action: Literal["increment", "set"] | None = "increment"
|
|
200
194
|
|
|
201
195
|
|
|
202
196
|
class InvoiceLineItemIn(BaseModel):
|
|
203
197
|
customer_provider_id: str
|
|
204
|
-
description:
|
|
198
|
+
description: str | None = None
|
|
205
199
|
unit_amount: AmountMinor
|
|
206
200
|
currency: Currency
|
|
207
|
-
quantity:
|
|
208
|
-
provider_price_id:
|
|
209
|
-
None # if linked to a price, unit_amount may be ignored
|
|
210
|
-
)
|
|
201
|
+
quantity: int | None = 1
|
|
202
|
+
provider_price_id: str | None = None # if linked to a price, unit_amount may be ignored
|
|
211
203
|
|
|
212
204
|
|
|
213
205
|
class InvoicesListFilter(BaseModel):
|
|
214
|
-
customer_provider_id:
|
|
215
|
-
status:
|
|
216
|
-
limit:
|
|
217
|
-
cursor:
|
|
206
|
+
customer_provider_id: str | None = None
|
|
207
|
+
status: str | None = None
|
|
208
|
+
limit: int | None = Field(default=50, ge=1, le=200)
|
|
209
|
+
cursor: str | None = None
|
|
218
210
|
|
|
219
211
|
|
|
220
212
|
class SetupIntentOut(BaseModel):
|
|
@@ -222,8 +214,8 @@ class SetupIntentOut(BaseModel):
|
|
|
222
214
|
provider: str
|
|
223
215
|
provider_setup_intent_id: str
|
|
224
216
|
status: str
|
|
225
|
-
client_secret:
|
|
226
|
-
next_action:
|
|
217
|
+
client_secret: str | None = None
|
|
218
|
+
next_action: NextAction | None = None
|
|
227
219
|
|
|
228
220
|
|
|
229
221
|
class DisputeOut(BaseModel):
|
|
@@ -232,10 +224,10 @@ class DisputeOut(BaseModel):
|
|
|
232
224
|
provider_dispute_id: str
|
|
233
225
|
amount: AmountMinor
|
|
234
226
|
currency: Currency
|
|
235
|
-
reason:
|
|
227
|
+
reason: str | None = None
|
|
236
228
|
status: str
|
|
237
|
-
evidence_due_by:
|
|
238
|
-
created_at:
|
|
229
|
+
evidence_due_by: str | None = None
|
|
230
|
+
created_at: str | None = None
|
|
239
231
|
|
|
240
232
|
|
|
241
233
|
class PayoutOut(BaseModel):
|
|
@@ -245,8 +237,8 @@ class PayoutOut(BaseModel):
|
|
|
245
237
|
amount: AmountMinor
|
|
246
238
|
currency: Currency
|
|
247
239
|
status: str
|
|
248
|
-
arrival_date:
|
|
249
|
-
type:
|
|
240
|
+
arrival_date: str | None = None
|
|
241
|
+
type: str | None = None
|
|
250
242
|
|
|
251
243
|
|
|
252
244
|
class BalanceAmount(BaseModel):
|
|
@@ -264,7 +256,7 @@ class SetupIntentCreateIn(BaseModel):
|
|
|
264
256
|
|
|
265
257
|
|
|
266
258
|
class WebhookReplayIn(BaseModel):
|
|
267
|
-
event_ids:
|
|
259
|
+
event_ids: list[str] | None = None
|
|
268
260
|
|
|
269
261
|
|
|
270
262
|
class WebhookReplayOut(BaseModel):
|
|
@@ -278,36 +270,36 @@ class WebhookAckOut(BaseModel):
|
|
|
278
270
|
class UsageRecordOut(BaseModel):
|
|
279
271
|
id: str
|
|
280
272
|
quantity: int
|
|
281
|
-
timestamp:
|
|
282
|
-
subscription_item:
|
|
283
|
-
provider_price_id:
|
|
284
|
-
action:
|
|
273
|
+
timestamp: int | None = None
|
|
274
|
+
subscription_item: str | None = None
|
|
275
|
+
provider_price_id: str | None = None
|
|
276
|
+
action: Literal["increment", "set"] | None = None
|
|
285
277
|
|
|
286
278
|
|
|
287
279
|
# -------- Customers list filter ----------
|
|
288
280
|
class CustomersListFilter(BaseModel):
|
|
289
|
-
provider:
|
|
290
|
-
user_id:
|
|
291
|
-
limit:
|
|
292
|
-
cursor:
|
|
281
|
+
provider: str | None = None
|
|
282
|
+
user_id: str | None = None
|
|
283
|
+
limit: int | None = Field(default=50, ge=1, le=200)
|
|
284
|
+
cursor: str | None = None # we’ll paginate on provider_customer_id asc
|
|
293
285
|
|
|
294
286
|
|
|
295
287
|
# -------- Products / Prices updates ----------
|
|
296
288
|
class ProductUpdateIn(BaseModel):
|
|
297
|
-
name:
|
|
298
|
-
active:
|
|
289
|
+
name: str | None = None
|
|
290
|
+
active: bool | None = None
|
|
299
291
|
|
|
300
292
|
|
|
301
293
|
class PriceUpdateIn(BaseModel):
|
|
302
|
-
active:
|
|
294
|
+
active: bool | None = None
|
|
303
295
|
|
|
304
296
|
|
|
305
297
|
# -------- Payment Method update ----------
|
|
306
298
|
class PaymentMethodUpdateIn(BaseModel):
|
|
307
299
|
# keep minimal + commonly supported card fields
|
|
308
|
-
name:
|
|
309
|
-
exp_month:
|
|
310
|
-
exp_year:
|
|
300
|
+
name: str | None = None
|
|
301
|
+
exp_month: int | None = None
|
|
302
|
+
exp_year: int | None = None
|
|
311
303
|
# extend here later with address fields (line1, city, etc.)
|
|
312
304
|
|
|
313
305
|
|
|
@@ -316,27 +308,27 @@ class RefundOut(BaseModel):
|
|
|
316
308
|
id: str
|
|
317
309
|
provider: str
|
|
318
310
|
provider_refund_id: str
|
|
319
|
-
provider_payment_intent_id:
|
|
311
|
+
provider_payment_intent_id: str | None = None
|
|
320
312
|
amount: AmountMinor
|
|
321
313
|
currency: Currency
|
|
322
314
|
status: str
|
|
323
|
-
reason:
|
|
324
|
-
created_at:
|
|
315
|
+
reason: str | None = None
|
|
316
|
+
created_at: str | None = None
|
|
325
317
|
|
|
326
318
|
|
|
327
319
|
# -------- Invoice line items (list) ----------
|
|
328
320
|
class InvoiceLineItemOut(BaseModel):
|
|
329
321
|
id: str
|
|
330
|
-
description:
|
|
322
|
+
description: str | None = None
|
|
331
323
|
amount: AmountMinor
|
|
332
324
|
currency: Currency
|
|
333
|
-
quantity:
|
|
334
|
-
provider_price_id:
|
|
325
|
+
quantity: int | None = 1
|
|
326
|
+
provider_price_id: str | None = None
|
|
335
327
|
|
|
336
328
|
|
|
337
329
|
# -------- Usage records list/get ----------
|
|
338
330
|
class UsageRecordListFilter(BaseModel):
|
|
339
|
-
subscription_item:
|
|
340
|
-
provider_price_id:
|
|
341
|
-
limit:
|
|
342
|
-
cursor:
|
|
331
|
+
subscription_item: str | None = None
|
|
332
|
+
provider_price_id: str | None = None
|
|
333
|
+
limit: int | None = Field(default=50, ge=1, le=200)
|
|
334
|
+
cursor: str | None = None
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
3
|
from sqlalchemy import select
|
|
6
4
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
7
5
|
|
|
@@ -78,7 +76,7 @@ class PaymentsService:
|
|
|
78
76
|
session: AsyncSession,
|
|
79
77
|
*,
|
|
80
78
|
tenant_id: str,
|
|
81
|
-
provider_name:
|
|
79
|
+
provider_name: str | None = None,
|
|
82
80
|
):
|
|
83
81
|
if not tenant_id:
|
|
84
82
|
raise ValueError("tenant_id is required for PaymentsService")
|
|
@@ -144,9 +142,7 @@ class PaymentsService:
|
|
|
144
142
|
|
|
145
143
|
# --- Intents --------------------------------------------------------------
|
|
146
144
|
|
|
147
|
-
async def create_intent(
|
|
148
|
-
self, user_id: Optional[str], data: IntentCreateIn
|
|
149
|
-
) -> IntentOut:
|
|
145
|
+
async def create_intent(self, user_id: str | None, data: IntentCreateIn) -> IntentOut:
|
|
150
146
|
adapter = self._get_adapter()
|
|
151
147
|
out = await adapter.create_intent(data, user_id=user_id)
|
|
152
148
|
self.session.add(
|
|
@@ -217,9 +213,7 @@ class PaymentsService:
|
|
|
217
213
|
|
|
218
214
|
# --- Webhooks -------------------------------------------------------------
|
|
219
215
|
|
|
220
|
-
async def handle_webhook(
|
|
221
|
-
self, provider: str, signature: str | None, payload: bytes
|
|
222
|
-
) -> dict:
|
|
216
|
+
async def handle_webhook(self, provider: str, signature: str | None, payload: bytes) -> dict:
|
|
223
217
|
adapter = self._get_adapter()
|
|
224
218
|
parsed = await adapter.verify_and_parse_webhook(signature, payload)
|
|
225
219
|
self.session.add(
|
|
@@ -317,9 +311,7 @@ class PaymentsService:
|
|
|
317
311
|
)
|
|
318
312
|
)
|
|
319
313
|
|
|
320
|
-
async def attach_payment_method(
|
|
321
|
-
self, data: PaymentMethodAttachIn
|
|
322
|
-
) -> PaymentMethodOut:
|
|
314
|
+
async def attach_payment_method(self, data: PaymentMethodAttachIn) -> PaymentMethodOut:
|
|
323
315
|
out = await self._get_adapter().attach_payment_method(data)
|
|
324
316
|
# Upsert locally for quick listing
|
|
325
317
|
pm = PayPaymentMethod(
|
|
@@ -336,9 +328,7 @@ class PaymentsService:
|
|
|
336
328
|
self.session.add(pm)
|
|
337
329
|
return out
|
|
338
330
|
|
|
339
|
-
async def list_payment_methods(
|
|
340
|
-
self, provider_customer_id: str
|
|
341
|
-
) -> list[PaymentMethodOut]:
|
|
331
|
+
async def list_payment_methods(self, provider_customer_id: str) -> list[PaymentMethodOut]:
|
|
342
332
|
return await self._get_adapter().list_payment_methods(provider_customer_id)
|
|
343
333
|
|
|
344
334
|
async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
@@ -401,18 +391,14 @@ class PaymentsService:
|
|
|
401
391
|
async def update_subscription(
|
|
402
392
|
self, provider_subscription_id: str, data: SubscriptionUpdateIn
|
|
403
393
|
) -> SubscriptionOut:
|
|
404
|
-
out = await self._get_adapter().update_subscription(
|
|
405
|
-
provider_subscription_id, data
|
|
406
|
-
)
|
|
394
|
+
out = await self._get_adapter().update_subscription(provider_subscription_id, data)
|
|
407
395
|
# Optionally reflect status/quantity locally (query + update if exists)
|
|
408
396
|
return out
|
|
409
397
|
|
|
410
398
|
async def cancel_subscription(
|
|
411
399
|
self, provider_subscription_id: str, at_period_end: bool = True
|
|
412
400
|
) -> SubscriptionOut:
|
|
413
|
-
out = await self._get_adapter().cancel_subscription(
|
|
414
|
-
provider_subscription_id, at_period_end
|
|
415
|
-
)
|
|
401
|
+
out = await self._get_adapter().cancel_subscription(provider_subscription_id, at_period_end)
|
|
416
402
|
return out
|
|
417
403
|
|
|
418
404
|
# --- Invoices ---
|
|
@@ -457,15 +443,15 @@ class PaymentsService:
|
|
|
457
443
|
q = select(
|
|
458
444
|
func.date_trunc("day", LedgerEntry.ts).label("day"),
|
|
459
445
|
LedgerEntry.currency,
|
|
460
|
-
func.sum(
|
|
461
|
-
|
|
462
|
-
)
|
|
463
|
-
func.sum(
|
|
464
|
-
|
|
465
|
-
)
|
|
466
|
-
func.sum(
|
|
467
|
-
|
|
468
|
-
)
|
|
446
|
+
func.sum(func.case((LedgerEntry.kind == "payment", LedgerEntry.amount), else_=0)).label(
|
|
447
|
+
"gross"
|
|
448
|
+
),
|
|
449
|
+
func.sum(func.case((LedgerEntry.kind == "refund", LedgerEntry.amount), else_=0)).label(
|
|
450
|
+
"refunds"
|
|
451
|
+
),
|
|
452
|
+
func.sum(func.case((LedgerEntry.kind == "fee", LedgerEntry.amount), else_=0)).label(
|
|
453
|
+
"fees"
|
|
454
|
+
),
|
|
469
455
|
func.count().label("count"),
|
|
470
456
|
)
|
|
471
457
|
if date_from:
|
|
@@ -478,9 +464,9 @@ class PaymentsService:
|
|
|
478
464
|
q = q.where(LedgerEntry.ts <= datetime.fromisoformat(date_to))
|
|
479
465
|
except Exception:
|
|
480
466
|
pass
|
|
481
|
-
q = q.group_by(
|
|
482
|
-
func.date_trunc("day", LedgerEntry.ts)
|
|
483
|
-
)
|
|
467
|
+
q = q.group_by(func.date_trunc("day", LedgerEntry.ts), LedgerEntry.currency).order_by(
|
|
468
|
+
func.date_trunc("day", LedgerEntry.ts).desc()
|
|
469
|
+
)
|
|
484
470
|
|
|
485
471
|
rows = (await self.session.execute(q)).all()
|
|
486
472
|
out: list[StatementRow] = []
|
|
@@ -502,9 +488,7 @@ class PaymentsService:
|
|
|
502
488
|
)
|
|
503
489
|
return out
|
|
504
490
|
|
|
505
|
-
async def capture_intent(
|
|
506
|
-
self, provider_intent_id: str, data: CaptureIn
|
|
507
|
-
) -> IntentOut:
|
|
491
|
+
async def capture_intent(self, provider_intent_id: str, data: CaptureIn) -> IntentOut:
|
|
508
492
|
out = await self._get_adapter().capture_intent(
|
|
509
493
|
provider_intent_id,
|
|
510
494
|
amount=int(data.amount) if data.amount is not None else None,
|
|
@@ -539,9 +523,7 @@ class PaymentsService:
|
|
|
539
523
|
)
|
|
540
524
|
return out
|
|
541
525
|
|
|
542
|
-
async def list_intents(
|
|
543
|
-
self, f: IntentListFilter
|
|
544
|
-
) -> tuple[list[IntentOut], str | None]:
|
|
526
|
+
async def list_intents(self, f: IntentListFilter) -> tuple[list[IntentOut], str | None]:
|
|
545
527
|
return await self._get_adapter().list_intents(
|
|
546
528
|
customer_provider_id=f.customer_provider_id,
|
|
547
529
|
status=f.status,
|
|
@@ -553,13 +535,9 @@ class PaymentsService:
|
|
|
553
535
|
async def add_invoice_line_item(
|
|
554
536
|
self, provider_invoice_id: str, data: InvoiceLineItemIn
|
|
555
537
|
) -> InvoiceOut:
|
|
556
|
-
return await self._get_adapter().add_invoice_line_item(
|
|
557
|
-
provider_invoice_id, data
|
|
558
|
-
)
|
|
538
|
+
return await self._get_adapter().add_invoice_line_item(provider_invoice_id, data)
|
|
559
539
|
|
|
560
|
-
async def list_invoices(
|
|
561
|
-
self, f: InvoicesListFilter
|
|
562
|
-
) -> tuple[list[InvoiceOut], str | None]:
|
|
540
|
+
async def list_invoices(self, f: InvoicesListFilter) -> tuple[list[InvoiceOut], str | None]:
|
|
563
541
|
return await self._get_adapter().list_invoices(
|
|
564
542
|
customer_provider_id=f.customer_provider_id,
|
|
565
543
|
status=f.status,
|
|
@@ -596,9 +574,7 @@ class PaymentsService:
|
|
|
596
574
|
)
|
|
597
575
|
return out
|
|
598
576
|
|
|
599
|
-
async def confirm_setup_intent(
|
|
600
|
-
self, provider_setup_intent_id: str
|
|
601
|
-
) -> SetupIntentOut:
|
|
577
|
+
async def confirm_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
602
578
|
out = await self._get_adapter().confirm_setup_intent(provider_setup_intent_id)
|
|
603
579
|
row = await self.session.scalar(
|
|
604
580
|
select(PaySetupIntent).where(
|
|
@@ -647,19 +623,15 @@ class PaymentsService:
|
|
|
647
623
|
|
|
648
624
|
# --- Disputes -------------------------------------------------------------
|
|
649
625
|
async def list_disputes(
|
|
650
|
-
self, *, status:
|
|
651
|
-
) -> tuple[list[DisputeOut],
|
|
652
|
-
return await self._get_adapter().list_disputes(
|
|
653
|
-
status=status, limit=limit, cursor=cursor
|
|
654
|
-
)
|
|
626
|
+
self, *, status: str | None, limit: int, cursor: str | None
|
|
627
|
+
) -> tuple[list[DisputeOut], str | None]:
|
|
628
|
+
return await self._get_adapter().list_disputes(status=status, limit=limit, cursor=cursor)
|
|
655
629
|
|
|
656
630
|
async def get_dispute(self, provider_dispute_id: str) -> DisputeOut:
|
|
657
631
|
out = await self._get_adapter().get_dispute(provider_dispute_id)
|
|
658
632
|
# Upsert locally
|
|
659
633
|
row = await self.session.scalar(
|
|
660
|
-
select(PayDispute).where(
|
|
661
|
-
PayDispute.provider_dispute_id == provider_dispute_id
|
|
662
|
-
)
|
|
634
|
+
select(PayDispute).where(PayDispute.provider_dispute_id == provider_dispute_id)
|
|
663
635
|
)
|
|
664
636
|
if row:
|
|
665
637
|
row.status = out.status
|
|
@@ -680,17 +652,11 @@ class PaymentsService:
|
|
|
680
652
|
)
|
|
681
653
|
return out
|
|
682
654
|
|
|
683
|
-
async def submit_dispute_evidence(
|
|
684
|
-
self
|
|
685
|
-
) -> DisputeOut:
|
|
686
|
-
out = await self._get_adapter().submit_dispute_evidence(
|
|
687
|
-
provider_dispute_id, evidence
|
|
688
|
-
)
|
|
655
|
+
async def submit_dispute_evidence(self, provider_dispute_id: str, evidence: dict) -> DisputeOut:
|
|
656
|
+
out = await self._get_adapter().submit_dispute_evidence(provider_dispute_id, evidence)
|
|
689
657
|
# reflect status
|
|
690
658
|
row = await self.session.scalar(
|
|
691
|
-
select(PayDispute).where(
|
|
692
|
-
PayDispute.provider_dispute_id == provider_dispute_id
|
|
693
|
-
)
|
|
659
|
+
select(PayDispute).where(PayDispute.provider_dispute_id == provider_dispute_id)
|
|
694
660
|
)
|
|
695
661
|
if row:
|
|
696
662
|
row.status = out.status
|
|
@@ -702,8 +668,8 @@ class PaymentsService:
|
|
|
702
668
|
|
|
703
669
|
# --- Payouts --------------------------------------------------------------
|
|
704
670
|
async def list_payouts(
|
|
705
|
-
self, *, limit: int, cursor:
|
|
706
|
-
) -> tuple[list[PayoutOut],
|
|
671
|
+
self, *, limit: int, cursor: str | None
|
|
672
|
+
) -> tuple[list[PayoutOut], str | None]:
|
|
707
673
|
return await self._get_adapter().list_payouts(limit=limit, cursor=cursor)
|
|
708
674
|
|
|
709
675
|
async def get_payout(self, provider_payout_id: str) -> PayoutOut:
|
|
@@ -733,7 +699,7 @@ class PaymentsService:
|
|
|
733
699
|
|
|
734
700
|
# --- Webhook replay -------------------------------------------------------
|
|
735
701
|
async def replay_webhooks(
|
|
736
|
-
self, since:
|
|
702
|
+
self, since: str | None, until: str | None, event_ids: list[str]
|
|
737
703
|
) -> int:
|
|
738
704
|
from datetime import datetime
|
|
739
705
|
|
|
@@ -760,9 +726,7 @@ class PaymentsService:
|
|
|
760
726
|
return len(rows)
|
|
761
727
|
|
|
762
728
|
# ---- Customers ----
|
|
763
|
-
async def list_customers(
|
|
764
|
-
self, f: CustomersListFilter
|
|
765
|
-
) -> tuple[list[CustomerOut], str | None]:
|
|
729
|
+
async def list_customers(self, f: CustomersListFilter) -> tuple[list[CustomerOut], str | None]:
|
|
766
730
|
adapter = self._get_adapter()
|
|
767
731
|
try:
|
|
768
732
|
return await adapter.list_customers(
|
|
@@ -805,9 +769,7 @@ class PaymentsService:
|
|
|
805
769
|
raise RuntimeError("Customer not found")
|
|
806
770
|
# upsert locally
|
|
807
771
|
row = await self.session.scalar(
|
|
808
|
-
select(PayCustomer).where(
|
|
809
|
-
PayCustomer.provider_customer_id == provider_customer_id
|
|
810
|
-
)
|
|
772
|
+
select(PayCustomer).where(PayCustomer.provider_customer_id == provider_customer_id)
|
|
811
773
|
)
|
|
812
774
|
if not row:
|
|
813
775
|
self.session.add(
|
|
@@ -827,19 +789,13 @@ class PaymentsService:
|
|
|
827
789
|
async def list_products(
|
|
828
790
|
self, *, active: bool | None, limit: int, cursor: str | None
|
|
829
791
|
) -> tuple[list[ProductOut], str | None]:
|
|
830
|
-
return await self._get_adapter().list_products(
|
|
831
|
-
active=active, limit=limit, cursor=cursor
|
|
832
|
-
)
|
|
792
|
+
return await self._get_adapter().list_products(active=active, limit=limit, cursor=cursor)
|
|
833
793
|
|
|
834
|
-
async def update_product(
|
|
835
|
-
self, provider_product_id: str, data: ProductUpdateIn
|
|
836
|
-
) -> ProductOut:
|
|
794
|
+
async def update_product(self, provider_product_id: str, data: ProductUpdateIn) -> ProductOut:
|
|
837
795
|
out = await self._get_adapter().update_product(provider_product_id, data)
|
|
838
796
|
# reflect DB
|
|
839
797
|
row = await self.session.scalar(
|
|
840
|
-
select(PayProduct).where(
|
|
841
|
-
PayProduct.provider_product_id == provider_product_id
|
|
842
|
-
)
|
|
798
|
+
select(PayProduct).where(PayProduct.provider_product_id == provider_product_id)
|
|
843
799
|
)
|
|
844
800
|
if row:
|
|
845
801
|
if data.name is not None:
|
|
@@ -866,9 +822,7 @@ class PaymentsService:
|
|
|
866
822
|
cursor=cursor,
|
|
867
823
|
)
|
|
868
824
|
|
|
869
|
-
async def update_price(
|
|
870
|
-
self, provider_price_id: str, data: PriceUpdateIn
|
|
871
|
-
) -> PriceOut:
|
|
825
|
+
async def update_price(self, provider_price_id: str, data: PriceUpdateIn) -> PriceOut:
|
|
872
826
|
out = await self._get_adapter().update_price(provider_price_id, data)
|
|
873
827
|
row = await self.session.scalar(
|
|
874
828
|
select(PayPrice).where(PayPrice.provider_price_id == provider_price_id)
|