svc-infra 0.1.589__py3-none-any.whl → 0.1.706__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of svc-infra might be problematic. Click here for more details.
- svc_infra/__init__.py +58 -2
- svc_infra/apf_payments/README.md +732 -0
- svc_infra/apf_payments/models.py +133 -42
- svc_infra/apf_payments/provider/__init__.py +4 -0
- svc_infra/apf_payments/provider/aiydan.py +871 -0
- svc_infra/apf_payments/provider/base.py +30 -9
- svc_infra/apf_payments/provider/stripe.py +156 -62
- svc_infra/apf_payments/schemas.py +19 -10
- svc_infra/apf_payments/service.py +211 -68
- svc_infra/apf_payments/settings.py +27 -3
- svc_infra/api/__init__.py +61 -0
- svc_infra/api/fastapi/__init__.py +15 -0
- svc_infra/api/fastapi/admin/__init__.py +3 -0
- svc_infra/api/fastapi/admin/add.py +245 -0
- svc_infra/api/fastapi/apf_payments/router.py +145 -46
- svc_infra/api/fastapi/apf_payments/setup.py +26 -8
- svc_infra/api/fastapi/auth/__init__.py +65 -0
- svc_infra/api/fastapi/auth/_cookies.py +6 -2
- svc_infra/api/fastapi/auth/add.py +27 -14
- svc_infra/api/fastapi/auth/gaurd.py +104 -13
- svc_infra/api/fastapi/auth/mfa/models.py +3 -1
- svc_infra/api/fastapi/auth/mfa/pre_auth.py +10 -6
- svc_infra/api/fastapi/auth/mfa/router.py +15 -8
- svc_infra/api/fastapi/auth/mfa/security.py +1 -2
- svc_infra/api/fastapi/auth/mfa/utils.py +2 -1
- svc_infra/api/fastapi/auth/mfa/verify.py +9 -2
- svc_infra/api/fastapi/auth/policy.py +0 -1
- svc_infra/api/fastapi/auth/providers.py +3 -1
- svc_infra/api/fastapi/auth/routers/apikey_router.py +6 -6
- svc_infra/api/fastapi/auth/routers/oauth_router.py +214 -75
- svc_infra/api/fastapi/auth/routers/session_router.py +67 -0
- svc_infra/api/fastapi/auth/security.py +31 -10
- svc_infra/api/fastapi/auth/sender.py +8 -1
- svc_infra/api/fastapi/auth/settings.py +2 -0
- svc_infra/api/fastapi/auth/state.py +3 -1
- svc_infra/api/fastapi/auth/ws_security.py +275 -0
- svc_infra/api/fastapi/billing/router.py +73 -0
- svc_infra/api/fastapi/billing/setup.py +19 -0
- svc_infra/api/fastapi/cache/add.py +9 -5
- svc_infra/api/fastapi/db/__init__.py +5 -1
- svc_infra/api/fastapi/db/http.py +3 -1
- svc_infra/api/fastapi/db/nosql/__init__.py +39 -1
- svc_infra/api/fastapi/db/nosql/mongo/add.py +47 -32
- svc_infra/api/fastapi/db/nosql/mongo/crud_router.py +30 -11
- svc_infra/api/fastapi/db/sql/__init__.py +5 -1
- svc_infra/api/fastapi/db/sql/add.py +71 -26
- svc_infra/api/fastapi/db/sql/crud_router.py +210 -22
- svc_infra/api/fastapi/db/sql/health.py +3 -1
- svc_infra/api/fastapi/db/sql/session.py +18 -0
- svc_infra/api/fastapi/db/sql/users.py +29 -5
- svc_infra/api/fastapi/dependencies/ratelimit.py +130 -0
- svc_infra/api/fastapi/docs/add.py +173 -0
- svc_infra/api/fastapi/docs/landing.py +4 -2
- svc_infra/api/fastapi/docs/scoped.py +62 -15
- svc_infra/api/fastapi/dual/__init__.py +12 -2
- svc_infra/api/fastapi/dual/dualize.py +1 -1
- svc_infra/api/fastapi/dual/protected.py +126 -4
- svc_infra/api/fastapi/dual/public.py +25 -0
- svc_infra/api/fastapi/dual/router.py +40 -13
- svc_infra/api/fastapi/dx.py +33 -2
- svc_infra/api/fastapi/ease.py +10 -2
- svc_infra/api/fastapi/http/concurrency.py +2 -1
- svc_infra/api/fastapi/http/conditional.py +3 -1
- svc_infra/api/fastapi/middleware/debug.py +4 -1
- svc_infra/api/fastapi/middleware/errors/catchall.py +6 -2
- svc_infra/api/fastapi/middleware/errors/exceptions.py +1 -1
- svc_infra/api/fastapi/middleware/errors/handlers.py +54 -8
- svc_infra/api/fastapi/middleware/graceful_shutdown.py +104 -0
- svc_infra/api/fastapi/middleware/idempotency.py +197 -70
- svc_infra/api/fastapi/middleware/idempotency_store.py +187 -0
- svc_infra/api/fastapi/middleware/optimistic_lock.py +42 -0
- svc_infra/api/fastapi/middleware/ratelimit.py +143 -31
- svc_infra/api/fastapi/middleware/ratelimit_store.py +111 -0
- svc_infra/api/fastapi/middleware/request_id.py +27 -11
- svc_infra/api/fastapi/middleware/request_size_limit.py +36 -0
- svc_infra/api/fastapi/middleware/timeout.py +177 -0
- svc_infra/api/fastapi/openapi/apply.py +5 -3
- svc_infra/api/fastapi/openapi/conventions.py +9 -2
- svc_infra/api/fastapi/openapi/mutators.py +165 -20
- svc_infra/api/fastapi/openapi/pipeline.py +1 -1
- svc_infra/api/fastapi/openapi/security.py +3 -1
- svc_infra/api/fastapi/ops/add.py +75 -0
- svc_infra/api/fastapi/pagination.py +47 -20
- svc_infra/api/fastapi/routers/__init__.py +43 -15
- svc_infra/api/fastapi/routers/ping.py +1 -0
- svc_infra/api/fastapi/setup.py +188 -56
- svc_infra/api/fastapi/tenancy/add.py +19 -0
- svc_infra/api/fastapi/tenancy/context.py +112 -0
- svc_infra/api/fastapi/versioned.py +101 -0
- svc_infra/app/README.md +5 -5
- svc_infra/app/__init__.py +3 -1
- svc_infra/app/env.py +69 -1
- svc_infra/app/logging/add.py +9 -2
- svc_infra/app/logging/formats.py +12 -5
- svc_infra/billing/__init__.py +23 -0
- svc_infra/billing/async_service.py +147 -0
- svc_infra/billing/jobs.py +241 -0
- svc_infra/billing/models.py +177 -0
- svc_infra/billing/quotas.py +103 -0
- svc_infra/billing/schemas.py +36 -0
- svc_infra/billing/service.py +123 -0
- svc_infra/bundled_docs/README.md +5 -0
- svc_infra/bundled_docs/__init__.py +1 -0
- svc_infra/bundled_docs/getting-started.md +6 -0
- svc_infra/cache/__init__.py +9 -0
- svc_infra/cache/add.py +170 -0
- svc_infra/cache/backend.py +7 -6
- svc_infra/cache/decorators.py +81 -15
- svc_infra/cache/demo.py +2 -2
- svc_infra/cache/keys.py +24 -4
- svc_infra/cache/recache.py +26 -14
- svc_infra/cache/resources.py +14 -5
- svc_infra/cache/tags.py +19 -44
- svc_infra/cache/utils.py +3 -1
- svc_infra/cli/__init__.py +52 -8
- svc_infra/cli/__main__.py +4 -0
- svc_infra/cli/cmds/__init__.py +39 -2
- svc_infra/cli/cmds/db/nosql/mongo/mongo_cmds.py +7 -4
- svc_infra/cli/cmds/db/nosql/mongo/mongo_scaffold_cmds.py +7 -5
- svc_infra/cli/cmds/db/ops_cmds.py +270 -0
- svc_infra/cli/cmds/db/sql/alembic_cmds.py +103 -18
- svc_infra/cli/cmds/db/sql/sql_export_cmds.py +88 -0
- svc_infra/cli/cmds/db/sql/sql_scaffold_cmds.py +3 -3
- svc_infra/cli/cmds/docs/docs_cmds.py +142 -0
- svc_infra/cli/cmds/dx/__init__.py +12 -0
- svc_infra/cli/cmds/dx/dx_cmds.py +116 -0
- svc_infra/cli/cmds/health/__init__.py +179 -0
- svc_infra/cli/cmds/health/health_cmds.py +8 -0
- svc_infra/cli/cmds/help.py +4 -0
- svc_infra/cli/cmds/jobs/__init__.py +1 -0
- svc_infra/cli/cmds/jobs/jobs_cmds.py +47 -0
- svc_infra/cli/cmds/obs/obs_cmds.py +36 -15
- svc_infra/cli/cmds/sdk/__init__.py +0 -0
- svc_infra/cli/cmds/sdk/sdk_cmds.py +112 -0
- svc_infra/cli/foundation/runner.py +6 -2
- svc_infra/data/add.py +61 -0
- svc_infra/data/backup.py +58 -0
- svc_infra/data/erasure.py +45 -0
- svc_infra/data/fixtures.py +42 -0
- svc_infra/data/retention.py +61 -0
- svc_infra/db/__init__.py +15 -0
- svc_infra/db/crud_schema.py +9 -9
- svc_infra/db/inbox.py +67 -0
- svc_infra/db/nosql/__init__.py +3 -0
- svc_infra/db/nosql/core.py +30 -9
- svc_infra/db/nosql/indexes.py +3 -1
- svc_infra/db/nosql/management.py +1 -1
- svc_infra/db/nosql/mongo/README.md +13 -13
- svc_infra/db/nosql/mongo/client.py +19 -2
- svc_infra/db/nosql/mongo/settings.py +6 -2
- svc_infra/db/nosql/repository.py +35 -15
- svc_infra/db/nosql/resource.py +20 -3
- svc_infra/db/nosql/scaffold.py +9 -3
- svc_infra/db/nosql/service.py +3 -1
- svc_infra/db/nosql/types.py +6 -2
- svc_infra/db/ops.py +384 -0
- svc_infra/db/outbox.py +108 -0
- svc_infra/db/sql/apikey.py +37 -9
- svc_infra/db/sql/authref.py +9 -3
- svc_infra/db/sql/constants.py +12 -8
- svc_infra/db/sql/core.py +2 -2
- svc_infra/db/sql/management.py +11 -8
- svc_infra/db/sql/repository.py +99 -26
- svc_infra/db/sql/resource.py +5 -0
- svc_infra/db/sql/scaffold.py +6 -2
- svc_infra/db/sql/service.py +15 -5
- svc_infra/db/sql/templates/models_schemas/auth/models.py.tmpl +7 -56
- svc_infra/db/sql/templates/models_schemas/auth/schemas.py.tmpl +1 -1
- svc_infra/db/sql/templates/setup/env_async.py.tmpl +34 -12
- svc_infra/db/sql/templates/setup/env_sync.py.tmpl +29 -7
- svc_infra/db/sql/tenant.py +88 -0
- svc_infra/db/sql/uniq_hooks.py +9 -3
- svc_infra/db/sql/utils.py +138 -51
- svc_infra/db/sql/versioning.py +14 -0
- svc_infra/deploy/__init__.py +538 -0
- svc_infra/documents/__init__.py +100 -0
- svc_infra/documents/add.py +264 -0
- svc_infra/documents/ease.py +233 -0
- svc_infra/documents/models.py +114 -0
- svc_infra/documents/storage.py +264 -0
- svc_infra/dx/add.py +65 -0
- svc_infra/dx/changelog.py +74 -0
- svc_infra/dx/checks.py +68 -0
- svc_infra/exceptions.py +141 -0
- svc_infra/health/__init__.py +864 -0
- svc_infra/http/__init__.py +13 -0
- svc_infra/http/client.py +105 -0
- svc_infra/jobs/builtins/outbox_processor.py +40 -0
- svc_infra/jobs/builtins/webhook_delivery.py +95 -0
- svc_infra/jobs/easy.py +33 -0
- svc_infra/jobs/loader.py +50 -0
- svc_infra/jobs/queue.py +116 -0
- svc_infra/jobs/redis_queue.py +256 -0
- svc_infra/jobs/runner.py +79 -0
- svc_infra/jobs/scheduler.py +53 -0
- svc_infra/jobs/worker.py +40 -0
- svc_infra/loaders/__init__.py +186 -0
- svc_infra/loaders/base.py +142 -0
- svc_infra/loaders/github.py +311 -0
- svc_infra/loaders/models.py +147 -0
- svc_infra/loaders/url.py +235 -0
- svc_infra/logging/__init__.py +374 -0
- svc_infra/mcp/svc_infra_mcp.py +91 -33
- svc_infra/obs/README.md +2 -0
- svc_infra/obs/add.py +65 -9
- svc_infra/obs/cloud_dash.py +2 -1
- svc_infra/obs/grafana/dashboards/http-overview.json +45 -0
- svc_infra/obs/metrics/__init__.py +52 -0
- svc_infra/obs/metrics/asgi.py +13 -7
- svc_infra/obs/metrics/http.py +9 -5
- svc_infra/obs/metrics/sqlalchemy.py +13 -9
- svc_infra/obs/metrics.py +53 -0
- svc_infra/obs/settings.py +6 -2
- svc_infra/security/add.py +217 -0
- svc_infra/security/audit.py +212 -0
- svc_infra/security/audit_service.py +74 -0
- svc_infra/security/headers.py +52 -0
- svc_infra/security/hibp.py +101 -0
- svc_infra/security/jwt_rotation.py +105 -0
- svc_infra/security/lockout.py +102 -0
- svc_infra/security/models.py +287 -0
- svc_infra/security/oauth_models.py +73 -0
- svc_infra/security/org_invites.py +130 -0
- svc_infra/security/passwords.py +79 -0
- svc_infra/security/permissions.py +171 -0
- svc_infra/security/session.py +98 -0
- svc_infra/security/signed_cookies.py +100 -0
- svc_infra/storage/__init__.py +93 -0
- svc_infra/storage/add.py +253 -0
- svc_infra/storage/backends/__init__.py +11 -0
- svc_infra/storage/backends/local.py +339 -0
- svc_infra/storage/backends/memory.py +216 -0
- svc_infra/storage/backends/s3.py +353 -0
- svc_infra/storage/base.py +239 -0
- svc_infra/storage/easy.py +185 -0
- svc_infra/storage/settings.py +195 -0
- svc_infra/testing/__init__.py +685 -0
- svc_infra/utils.py +7 -3
- svc_infra/webhooks/__init__.py +69 -0
- svc_infra/webhooks/add.py +339 -0
- svc_infra/webhooks/encryption.py +115 -0
- svc_infra/webhooks/fastapi.py +39 -0
- svc_infra/webhooks/router.py +55 -0
- svc_infra/webhooks/service.py +70 -0
- svc_infra/webhooks/signing.py +34 -0
- svc_infra/websocket/__init__.py +79 -0
- svc_infra/websocket/add.py +140 -0
- svc_infra/websocket/client.py +282 -0
- svc_infra/websocket/config.py +69 -0
- svc_infra/websocket/easy.py +76 -0
- svc_infra/websocket/exceptions.py +61 -0
- svc_infra/websocket/manager.py +344 -0
- svc_infra/websocket/models.py +49 -0
- svc_infra-0.1.706.dist-info/LICENSE +21 -0
- svc_infra-0.1.706.dist-info/METADATA +356 -0
- svc_infra-0.1.706.dist-info/RECORD +357 -0
- svc_infra-0.1.589.dist-info/METADATA +0 -79
- svc_infra-0.1.589.dist-info/RECORD +0 -234
- {svc_infra-0.1.589.dist-info → svc_infra-0.1.706.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.589.dist-info → svc_infra-0.1.706.dist-info}/entry_points.txt +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Literal, Optional
|
|
3
|
+
from typing import Annotated, Any, Literal, Optional
|
|
4
4
|
|
|
5
|
-
from pydantic import BaseModel, Field,
|
|
5
|
+
from pydantic import BaseModel, Field, StringConstraints
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
# Type aliases for payment fields using Annotated with proper type hints
|
|
8
|
+
Currency = Annotated[str, StringConstraints(pattern=r"^[A-Z]{3}$")]
|
|
9
|
+
AmountMinor = Annotated[int, Field(ge=0)] # minor units (cents)
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class CustomerUpsertIn(BaseModel):
|
|
@@ -24,10 +25,12 @@ class CustomerOut(BaseModel):
|
|
|
24
25
|
|
|
25
26
|
class IntentCreateIn(BaseModel):
|
|
26
27
|
amount: AmountMinor = Field(..., description="Minor units (e.g., cents)")
|
|
27
|
-
currency: Currency = Field(...,
|
|
28
|
+
currency: Currency = Field(..., json_schema_extra={"example": "USD"})
|
|
28
29
|
description: Optional[str] = None
|
|
29
30
|
capture_method: Literal["automatic", "manual"] = "automatic"
|
|
30
|
-
payment_method_types: list[str] = Field(
|
|
31
|
+
payment_method_types: list[str] = Field(
|
|
32
|
+
default_factory=list
|
|
33
|
+
) # let provider default
|
|
31
34
|
|
|
32
35
|
|
|
33
36
|
class NextAction(BaseModel):
|
|
@@ -133,14 +136,18 @@ class SubscriptionCreateIn(BaseModel):
|
|
|
133
136
|
price_provider_id: str
|
|
134
137
|
quantity: int = 1
|
|
135
138
|
trial_days: Optional[int] = None
|
|
136
|
-
proration_behavior: Literal["create_prorations", "none", "always_invoice"] =
|
|
139
|
+
proration_behavior: Literal["create_prorations", "none", "always_invoice"] = (
|
|
140
|
+
"create_prorations"
|
|
141
|
+
)
|
|
137
142
|
|
|
138
143
|
|
|
139
144
|
class SubscriptionUpdateIn(BaseModel):
|
|
140
145
|
price_provider_id: Optional[str] = None
|
|
141
146
|
quantity: Optional[int] = None
|
|
142
147
|
cancel_at_period_end: Optional[bool] = None
|
|
143
|
-
proration_behavior: Literal["create_prorations", "none", "always_invoice"] =
|
|
148
|
+
proration_behavior: Literal["create_prorations", "none", "always_invoice"] = (
|
|
149
|
+
"create_prorations"
|
|
150
|
+
)
|
|
144
151
|
|
|
145
152
|
|
|
146
153
|
class SubscriptionOut(BaseModel):
|
|
@@ -187,7 +194,7 @@ class UsageRecordIn(BaseModel):
|
|
|
187
194
|
# If provider doesn't use subscription_item, allow provider_price_id as fallback.
|
|
188
195
|
subscription_item: Optional[str] = None
|
|
189
196
|
provider_price_id: Optional[str] = None
|
|
190
|
-
quantity:
|
|
197
|
+
quantity: Annotated[int, Field(ge=0)]
|
|
191
198
|
timestamp: Optional[int] = None # Unix seconds; provider defaults to "now"
|
|
192
199
|
action: Optional[Literal["increment", "set"]] = "increment"
|
|
193
200
|
|
|
@@ -198,7 +205,9 @@ class InvoiceLineItemIn(BaseModel):
|
|
|
198
205
|
unit_amount: AmountMinor
|
|
199
206
|
currency: Currency
|
|
200
207
|
quantity: Optional[int] = 1
|
|
201
|
-
provider_price_id: Optional[str] =
|
|
208
|
+
provider_price_id: Optional[str] = (
|
|
209
|
+
None # if linked to a price, unit_amount may be ignored
|
|
210
|
+
)
|
|
202
211
|
|
|
203
212
|
|
|
204
213
|
class InvoicesListFilter(BaseModel):
|
|
@@ -19,6 +19,7 @@ from .models import (
|
|
|
19
19
|
PaySetupIntent,
|
|
20
20
|
PaySubscription,
|
|
21
21
|
)
|
|
22
|
+
from .provider.base import ProviderAdapter
|
|
22
23
|
from .provider.registry import get_provider_registry
|
|
23
24
|
from .schemas import (
|
|
24
25
|
BalanceSnapshotOut,
|
|
@@ -65,15 +66,30 @@ def _default_provider_name() -> str:
|
|
|
65
66
|
|
|
66
67
|
|
|
67
68
|
class PaymentsService:
|
|
69
|
+
"""Payments service facade wrapping provider adapters and persisting key rows.
|
|
68
70
|
|
|
69
|
-
|
|
71
|
+
NOTE: tenant_id is now required for all persistence operations. This is a breaking
|
|
72
|
+
change; callers must supply a valid tenant scope. (Future: could allow multi-tenant
|
|
73
|
+
mapping via adapter registry.)
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
session: AsyncSession,
|
|
79
|
+
*,
|
|
80
|
+
tenant_id: str,
|
|
81
|
+
provider_name: Optional[str] = None,
|
|
82
|
+
):
|
|
83
|
+
if not tenant_id:
|
|
84
|
+
raise ValueError("tenant_id is required for PaymentsService")
|
|
70
85
|
self.session = session
|
|
86
|
+
self.tenant_id = tenant_id
|
|
71
87
|
self._provider_name = (provider_name or _default_provider_name()).lower()
|
|
72
|
-
self._adapter = None # resolved on first use
|
|
88
|
+
self._adapter: ProviderAdapter | None = None # resolved on first use
|
|
73
89
|
|
|
74
90
|
# --- internal helpers -----------------------------------------------------
|
|
75
91
|
|
|
76
|
-
def _get_adapter(self):
|
|
92
|
+
def _get_adapter(self) -> ProviderAdapter:
|
|
77
93
|
if self._adapter is not None:
|
|
78
94
|
return self._adapter
|
|
79
95
|
reg = get_provider_registry()
|
|
@@ -118,6 +134,7 @@ class PaymentsService:
|
|
|
118
134
|
# If your PayCustomer model has additional columns (email/name), include them here.
|
|
119
135
|
self.session.add(
|
|
120
136
|
PayCustomer(
|
|
137
|
+
tenant_id=self.tenant_id,
|
|
121
138
|
provider=out.provider,
|
|
122
139
|
provider_customer_id=out.provider_customer_id,
|
|
123
140
|
user_id=data.user_id,
|
|
@@ -127,11 +144,14 @@ class PaymentsService:
|
|
|
127
144
|
|
|
128
145
|
# --- Intents --------------------------------------------------------------
|
|
129
146
|
|
|
130
|
-
async def create_intent(
|
|
147
|
+
async def create_intent(
|
|
148
|
+
self, user_id: Optional[str], data: IntentCreateIn
|
|
149
|
+
) -> IntentOut:
|
|
131
150
|
adapter = self._get_adapter()
|
|
132
151
|
out = await adapter.create_intent(data, user_id=user_id)
|
|
133
152
|
self.session.add(
|
|
134
153
|
PayIntent(
|
|
154
|
+
tenant_id=self.tenant_id,
|
|
135
155
|
provider=out.provider,
|
|
136
156
|
provider_intent_id=out.provider_intent_id,
|
|
137
157
|
user_id=user_id,
|
|
@@ -167,15 +187,44 @@ class PaymentsService:
|
|
|
167
187
|
async def refund(self, provider_intent_id: str, data: RefundIn) -> IntentOut:
|
|
168
188
|
adapter = self._get_adapter()
|
|
169
189
|
out = await adapter.refund(provider_intent_id, data)
|
|
190
|
+
# Create ledger entry if amount present and not already recorded
|
|
191
|
+
pi = await self.session.scalar(
|
|
192
|
+
select(PayIntent).where(PayIntent.provider_intent_id == provider_intent_id)
|
|
193
|
+
)
|
|
194
|
+
if pi:
|
|
195
|
+
amount = int(data.amount) if data.amount is not None else out.amount
|
|
196
|
+
# Guard against duplicates (same provider_ref + kind)
|
|
197
|
+
existing = await self.session.scalar(
|
|
198
|
+
select(LedgerEntry).where(
|
|
199
|
+
LedgerEntry.provider_ref == provider_intent_id,
|
|
200
|
+
LedgerEntry.kind == "refund",
|
|
201
|
+
)
|
|
202
|
+
)
|
|
203
|
+
if amount > 0 and not existing:
|
|
204
|
+
self.session.add(
|
|
205
|
+
LedgerEntry(
|
|
206
|
+
tenant_id=self.tenant_id,
|
|
207
|
+
provider=pi.provider,
|
|
208
|
+
provider_ref=provider_intent_id,
|
|
209
|
+
user_id=pi.user_id,
|
|
210
|
+
amount=+amount,
|
|
211
|
+
currency=out.currency,
|
|
212
|
+
kind="refund",
|
|
213
|
+
status="posted",
|
|
214
|
+
)
|
|
215
|
+
)
|
|
170
216
|
return out
|
|
171
217
|
|
|
172
218
|
# --- Webhooks -------------------------------------------------------------
|
|
173
219
|
|
|
174
|
-
async def handle_webhook(
|
|
220
|
+
async def handle_webhook(
|
|
221
|
+
self, provider: str, signature: str | None, payload: bytes
|
|
222
|
+
) -> dict:
|
|
175
223
|
adapter = self._get_adapter()
|
|
176
224
|
parsed = await adapter.verify_and_parse_webhook(signature, payload)
|
|
177
225
|
self.session.add(
|
|
178
226
|
PayEvent(
|
|
227
|
+
tenant_id=self.tenant_id,
|
|
179
228
|
provider=provider,
|
|
180
229
|
provider_event_id=parsed["id"],
|
|
181
230
|
type=parsed.get("type", ""),
|
|
@@ -199,6 +248,7 @@ class PaymentsService:
|
|
|
199
248
|
intent.status = "succeeded"
|
|
200
249
|
self.session.add(
|
|
201
250
|
LedgerEntry(
|
|
251
|
+
tenant_id=self.tenant_id,
|
|
202
252
|
provider=intent.provider,
|
|
203
253
|
provider_ref=provider_intent_id,
|
|
204
254
|
user_id=intent.user_id,
|
|
@@ -217,17 +267,27 @@ class PaymentsService:
|
|
|
217
267
|
select(PayIntent).where(PayIntent.provider_intent_id == pi_id)
|
|
218
268
|
)
|
|
219
269
|
if intent:
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
provider_ref
|
|
224
|
-
|
|
225
|
-
amount=+amount,
|
|
226
|
-
currency=currency,
|
|
227
|
-
kind="capture",
|
|
228
|
-
status="posted",
|
|
270
|
+
# Avoid duplicate capture entries
|
|
271
|
+
existing = await self.session.scalar(
|
|
272
|
+
select(LedgerEntry).where(
|
|
273
|
+
LedgerEntry.provider_ref == charge_obj.get("id"),
|
|
274
|
+
LedgerEntry.kind == "capture",
|
|
229
275
|
)
|
|
230
276
|
)
|
|
277
|
+
if not existing:
|
|
278
|
+
self.session.add(
|
|
279
|
+
LedgerEntry(
|
|
280
|
+
tenant_id=self.tenant_id,
|
|
281
|
+
provider=intent.provider,
|
|
282
|
+
provider_ref=charge_obj.get("id"),
|
|
283
|
+
user_id=intent.user_id,
|
|
284
|
+
amount=+amount,
|
|
285
|
+
currency=currency,
|
|
286
|
+
kind="capture",
|
|
287
|
+
status="posted",
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
intent.captured = True
|
|
231
291
|
|
|
232
292
|
async def _post_refund(self, charge_obj: dict):
|
|
233
293
|
amount = int(charge_obj.get("amount_refunded") or 0)
|
|
@@ -237,22 +297,33 @@ class PaymentsService:
|
|
|
237
297
|
select(PayIntent).where(PayIntent.provider_intent_id == pi_id)
|
|
238
298
|
)
|
|
239
299
|
if intent and amount > 0:
|
|
240
|
-
self.session.
|
|
241
|
-
LedgerEntry(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
user_id=intent.user_id,
|
|
245
|
-
amount=+amount,
|
|
246
|
-
currency=currency,
|
|
247
|
-
kind="refund",
|
|
248
|
-
status="posted",
|
|
300
|
+
existing = await self.session.scalar(
|
|
301
|
+
select(LedgerEntry).where(
|
|
302
|
+
LedgerEntry.provider_ref == charge_obj.get("id"),
|
|
303
|
+
LedgerEntry.kind == "refund",
|
|
249
304
|
)
|
|
250
305
|
)
|
|
306
|
+
if not existing:
|
|
307
|
+
self.session.add(
|
|
308
|
+
LedgerEntry(
|
|
309
|
+
tenant_id=self.tenant_id,
|
|
310
|
+
provider=intent.provider,
|
|
311
|
+
provider_ref=charge_obj.get("id"),
|
|
312
|
+
user_id=intent.user_id,
|
|
313
|
+
amount=+amount,
|
|
314
|
+
currency=currency,
|
|
315
|
+
kind="refund",
|
|
316
|
+
status="posted",
|
|
317
|
+
)
|
|
318
|
+
)
|
|
251
319
|
|
|
252
|
-
async def attach_payment_method(
|
|
320
|
+
async def attach_payment_method(
|
|
321
|
+
self, data: PaymentMethodAttachIn
|
|
322
|
+
) -> PaymentMethodOut:
|
|
253
323
|
out = await self._get_adapter().attach_payment_method(data)
|
|
254
324
|
# Upsert locally for quick listing
|
|
255
325
|
pm = PayPaymentMethod(
|
|
326
|
+
tenant_id=self.tenant_id,
|
|
256
327
|
provider=out.provider,
|
|
257
328
|
provider_customer_id=out.provider_customer_id,
|
|
258
329
|
provider_method_id=out.provider_method_id,
|
|
@@ -265,7 +336,9 @@ class PaymentsService:
|
|
|
265
336
|
self.session.add(pm)
|
|
266
337
|
return out
|
|
267
338
|
|
|
268
|
-
async def list_payment_methods(
|
|
339
|
+
async def list_payment_methods(
|
|
340
|
+
self, provider_customer_id: str
|
|
341
|
+
) -> list[PaymentMethodOut]:
|
|
269
342
|
return await self._get_adapter().list_payment_methods(provider_customer_id)
|
|
270
343
|
|
|
271
344
|
async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
@@ -283,6 +356,7 @@ class PaymentsService:
|
|
|
283
356
|
out = await self._get_adapter().create_product(data)
|
|
284
357
|
self.session.add(
|
|
285
358
|
PayProduct(
|
|
359
|
+
tenant_id=self.tenant_id,
|
|
286
360
|
provider=out.provider,
|
|
287
361
|
provider_product_id=out.provider_product_id,
|
|
288
362
|
name=out.name,
|
|
@@ -295,6 +369,7 @@ class PaymentsService:
|
|
|
295
369
|
out = await self._get_adapter().create_price(data)
|
|
296
370
|
self.session.add(
|
|
297
371
|
PayPrice(
|
|
372
|
+
tenant_id=self.tenant_id,
|
|
298
373
|
provider=out.provider,
|
|
299
374
|
provider_price_id=out.provider_price_id,
|
|
300
375
|
provider_product_id=out.provider_product_id,
|
|
@@ -312,6 +387,7 @@ class PaymentsService:
|
|
|
312
387
|
out = await self._get_adapter().create_subscription(data)
|
|
313
388
|
self.session.add(
|
|
314
389
|
PaySubscription(
|
|
390
|
+
tenant_id=self.tenant_id,
|
|
315
391
|
provider=out.provider,
|
|
316
392
|
provider_subscription_id=out.provider_subscription_id,
|
|
317
393
|
provider_price_id=out.provider_price_id,
|
|
@@ -325,14 +401,18 @@ class PaymentsService:
|
|
|
325
401
|
async def update_subscription(
|
|
326
402
|
self, provider_subscription_id: str, data: SubscriptionUpdateIn
|
|
327
403
|
) -> SubscriptionOut:
|
|
328
|
-
out = await self._get_adapter().update_subscription(
|
|
404
|
+
out = await self._get_adapter().update_subscription(
|
|
405
|
+
provider_subscription_id, data
|
|
406
|
+
)
|
|
329
407
|
# Optionally reflect status/quantity locally (query + update if exists)
|
|
330
408
|
return out
|
|
331
409
|
|
|
332
410
|
async def cancel_subscription(
|
|
333
411
|
self, provider_subscription_id: str, at_period_end: bool = True
|
|
334
412
|
) -> SubscriptionOut:
|
|
335
|
-
out = await self._get_adapter().cancel_subscription(
|
|
413
|
+
out = await self._get_adapter().cancel_subscription(
|
|
414
|
+
provider_subscription_id, at_period_end
|
|
415
|
+
)
|
|
336
416
|
return out
|
|
337
417
|
|
|
338
418
|
# --- Invoices ---
|
|
@@ -340,6 +420,7 @@ class PaymentsService:
|
|
|
340
420
|
out = await self._get_adapter().create_invoice(data)
|
|
341
421
|
self.session.add(
|
|
342
422
|
PayInvoice(
|
|
423
|
+
tenant_id=self.tenant_id,
|
|
343
424
|
provider=out.provider,
|
|
344
425
|
provider_invoice_id=out.provider_invoice_id,
|
|
345
426
|
provider_customer_id=out.provider_customer_id,
|
|
@@ -376,15 +457,15 @@ class PaymentsService:
|
|
|
376
457
|
q = select(
|
|
377
458
|
func.date_trunc("day", LedgerEntry.ts).label("day"),
|
|
378
459
|
LedgerEntry.currency,
|
|
379
|
-
func.sum(
|
|
380
|
-
"
|
|
381
|
-
),
|
|
382
|
-
func.sum(
|
|
383
|
-
"
|
|
384
|
-
),
|
|
385
|
-
func.sum(
|
|
386
|
-
"
|
|
387
|
-
),
|
|
460
|
+
func.sum(
|
|
461
|
+
func.case((LedgerEntry.kind == "payment", LedgerEntry.amount), else_=0)
|
|
462
|
+
).label("gross"),
|
|
463
|
+
func.sum(
|
|
464
|
+
func.case((LedgerEntry.kind == "refund", LedgerEntry.amount), else_=0)
|
|
465
|
+
).label("refunds"),
|
|
466
|
+
func.sum(
|
|
467
|
+
func.case((LedgerEntry.kind == "fee", LedgerEntry.amount), else_=0)
|
|
468
|
+
).label("fees"),
|
|
388
469
|
func.count().label("count"),
|
|
389
470
|
)
|
|
390
471
|
if date_from:
|
|
@@ -397,9 +478,9 @@ class PaymentsService:
|
|
|
397
478
|
q = q.where(LedgerEntry.ts <= datetime.fromisoformat(date_to))
|
|
398
479
|
except Exception:
|
|
399
480
|
pass
|
|
400
|
-
q = q.group_by(
|
|
401
|
-
func.date_trunc("day", LedgerEntry.ts).
|
|
402
|
-
)
|
|
481
|
+
q = q.group_by(
|
|
482
|
+
func.date_trunc("day", LedgerEntry.ts), LedgerEntry.currency
|
|
483
|
+
).order_by(func.date_trunc("day", LedgerEntry.ts).desc())
|
|
403
484
|
|
|
404
485
|
rows = (await self.session.execute(q)).all()
|
|
405
486
|
out: list[StatementRow] = []
|
|
@@ -421,9 +502,12 @@ class PaymentsService:
|
|
|
421
502
|
)
|
|
422
503
|
return out
|
|
423
504
|
|
|
424
|
-
async def capture_intent(
|
|
505
|
+
async def capture_intent(
|
|
506
|
+
self, provider_intent_id: str, data: CaptureIn
|
|
507
|
+
) -> IntentOut:
|
|
425
508
|
out = await self._get_adapter().capture_intent(
|
|
426
|
-
provider_intent_id,
|
|
509
|
+
provider_intent_id,
|
|
510
|
+
amount=int(data.amount) if data.amount is not None else None,
|
|
427
511
|
)
|
|
428
512
|
pi = await self.session.scalar(
|
|
429
513
|
select(PayIntent).where(PayIntent.provider_intent_id == provider_intent_id)
|
|
@@ -432,9 +516,32 @@ class PaymentsService:
|
|
|
432
516
|
pi.status = out.status
|
|
433
517
|
if out.status in ("succeeded", "requires_capture"): # Stripe specifics vary
|
|
434
518
|
pi.captured = True if out.status == "succeeded" else pi.captured
|
|
519
|
+
# Add capture ledger entry if succeeded and not already posted
|
|
520
|
+
if out.status == "succeeded":
|
|
521
|
+
existing = await self.session.scalar(
|
|
522
|
+
select(LedgerEntry).where(
|
|
523
|
+
LedgerEntry.provider_ref == provider_intent_id,
|
|
524
|
+
LedgerEntry.kind == "capture",
|
|
525
|
+
)
|
|
526
|
+
)
|
|
527
|
+
if not existing:
|
|
528
|
+
self.session.add(
|
|
529
|
+
LedgerEntry(
|
|
530
|
+
tenant_id=self.tenant_id,
|
|
531
|
+
provider=pi.provider,
|
|
532
|
+
provider_ref=provider_intent_id,
|
|
533
|
+
user_id=pi.user_id,
|
|
534
|
+
amount=+out.amount,
|
|
535
|
+
currency=out.currency,
|
|
536
|
+
kind="capture",
|
|
537
|
+
status="posted",
|
|
538
|
+
)
|
|
539
|
+
)
|
|
435
540
|
return out
|
|
436
541
|
|
|
437
|
-
async def list_intents(
|
|
542
|
+
async def list_intents(
|
|
543
|
+
self, f: IntentListFilter
|
|
544
|
+
) -> tuple[list[IntentOut], str | None]:
|
|
438
545
|
return await self._get_adapter().list_intents(
|
|
439
546
|
customer_provider_id=f.customer_provider_id,
|
|
440
547
|
status=f.status,
|
|
@@ -446,9 +553,13 @@ class PaymentsService:
|
|
|
446
553
|
async def add_invoice_line_item(
|
|
447
554
|
self, provider_invoice_id: str, data: InvoiceLineItemIn
|
|
448
555
|
) -> InvoiceOut:
|
|
449
|
-
return await self._get_adapter().add_invoice_line_item(
|
|
556
|
+
return await self._get_adapter().add_invoice_line_item(
|
|
557
|
+
provider_invoice_id, data
|
|
558
|
+
)
|
|
450
559
|
|
|
451
|
-
async def list_invoices(
|
|
560
|
+
async def list_invoices(
|
|
561
|
+
self, f: InvoicesListFilter
|
|
562
|
+
) -> tuple[list[InvoiceOut], str | None]:
|
|
452
563
|
return await self._get_adapter().list_invoices(
|
|
453
564
|
customer_provider_id=f.customer_provider_id,
|
|
454
565
|
status=f.status,
|
|
@@ -475,6 +586,7 @@ class PaymentsService:
|
|
|
475
586
|
out = await self._get_adapter().create_setup_intent(data)
|
|
476
587
|
self.session.add(
|
|
477
588
|
PaySetupIntent(
|
|
589
|
+
tenant_id=self.tenant_id,
|
|
478
590
|
provider=out.provider,
|
|
479
591
|
provider_setup_intent_id=out.provider_setup_intent_id,
|
|
480
592
|
user_id=None,
|
|
@@ -484,7 +596,9 @@ class PaymentsService:
|
|
|
484
596
|
)
|
|
485
597
|
return out
|
|
486
598
|
|
|
487
|
-
async def confirm_setup_intent(
|
|
599
|
+
async def confirm_setup_intent(
|
|
600
|
+
self, provider_setup_intent_id: str
|
|
601
|
+
) -> SetupIntentOut:
|
|
488
602
|
out = await self._get_adapter().confirm_setup_intent(provider_setup_intent_id)
|
|
489
603
|
row = await self.session.scalar(
|
|
490
604
|
select(PaySetupIntent).where(
|
|
@@ -510,6 +624,7 @@ class PaymentsService:
|
|
|
510
624
|
else:
|
|
511
625
|
self.session.add(
|
|
512
626
|
PaySetupIntent(
|
|
627
|
+
tenant_id=self.tenant_id,
|
|
513
628
|
provider=out.provider,
|
|
514
629
|
provider_setup_intent_id=out.provider_setup_intent_id,
|
|
515
630
|
user_id=None,
|
|
@@ -534,13 +649,17 @@ class PaymentsService:
|
|
|
534
649
|
async def list_disputes(
|
|
535
650
|
self, *, status: Optional[str], limit: int, cursor: Optional[str]
|
|
536
651
|
) -> tuple[list[DisputeOut], Optional[str]]:
|
|
537
|
-
return await self._get_adapter().list_disputes(
|
|
652
|
+
return await self._get_adapter().list_disputes(
|
|
653
|
+
status=status, limit=limit, cursor=cursor
|
|
654
|
+
)
|
|
538
655
|
|
|
539
656
|
async def get_dispute(self, provider_dispute_id: str) -> DisputeOut:
|
|
540
657
|
out = await self._get_adapter().get_dispute(provider_dispute_id)
|
|
541
658
|
# Upsert locally
|
|
542
659
|
row = await self.session.scalar(
|
|
543
|
-
select(PayDispute).where(
|
|
660
|
+
select(PayDispute).where(
|
|
661
|
+
PayDispute.provider_dispute_id == provider_dispute_id
|
|
662
|
+
)
|
|
544
663
|
)
|
|
545
664
|
if row:
|
|
546
665
|
row.status = out.status
|
|
@@ -549,6 +668,7 @@ class PaymentsService:
|
|
|
549
668
|
else:
|
|
550
669
|
self.session.add(
|
|
551
670
|
PayDispute(
|
|
671
|
+
tenant_id=self.tenant_id,
|
|
552
672
|
provider=out.provider,
|
|
553
673
|
provider_dispute_id=out.provider_dispute_id,
|
|
554
674
|
provider_charge_id=None, # set if adapter returns it
|
|
@@ -560,11 +680,17 @@ class PaymentsService:
|
|
|
560
680
|
)
|
|
561
681
|
return out
|
|
562
682
|
|
|
563
|
-
async def submit_dispute_evidence(
|
|
564
|
-
|
|
683
|
+
async def submit_dispute_evidence(
|
|
684
|
+
self, provider_dispute_id: str, evidence: dict
|
|
685
|
+
) -> DisputeOut:
|
|
686
|
+
out = await self._get_adapter().submit_dispute_evidence(
|
|
687
|
+
provider_dispute_id, evidence
|
|
688
|
+
)
|
|
565
689
|
# reflect status
|
|
566
690
|
row = await self.session.scalar(
|
|
567
|
-
select(PayDispute).where(
|
|
691
|
+
select(PayDispute).where(
|
|
692
|
+
PayDispute.provider_dispute_id == provider_dispute_id
|
|
693
|
+
)
|
|
568
694
|
)
|
|
569
695
|
if row:
|
|
570
696
|
row.status = out.status
|
|
@@ -594,6 +720,7 @@ class PaymentsService:
|
|
|
594
720
|
else:
|
|
595
721
|
self.session.add(
|
|
596
722
|
PayPayout(
|
|
723
|
+
tenant_id=self.tenant_id,
|
|
597
724
|
provider=out.provider,
|
|
598
725
|
provider_payout_id=out.provider_payout_id,
|
|
599
726
|
amount=out.amount,
|
|
@@ -633,11 +760,16 @@ class PaymentsService:
|
|
|
633
760
|
return len(rows)
|
|
634
761
|
|
|
635
762
|
# ---- Customers ----
|
|
636
|
-
async def list_customers(
|
|
763
|
+
async def list_customers(
|
|
764
|
+
self, f: CustomersListFilter
|
|
765
|
+
) -> tuple[list[CustomerOut], str | None]:
|
|
637
766
|
adapter = self._get_adapter()
|
|
638
767
|
try:
|
|
639
768
|
return await adapter.list_customers(
|
|
640
|
-
provider=f.provider,
|
|
769
|
+
provider=f.provider,
|
|
770
|
+
user_id=f.user_id,
|
|
771
|
+
limit=f.limit or 50,
|
|
772
|
+
cursor=f.cursor,
|
|
641
773
|
)
|
|
642
774
|
except NotImplementedError:
|
|
643
775
|
# Fallback to local DB listing
|
|
@@ -673,15 +805,17 @@ class PaymentsService:
|
|
|
673
805
|
raise RuntimeError("Customer not found")
|
|
674
806
|
# upsert locally
|
|
675
807
|
row = await self.session.scalar(
|
|
676
|
-
select(PayCustomer).where(
|
|
808
|
+
select(PayCustomer).where(
|
|
809
|
+
PayCustomer.provider_customer_id == provider_customer_id
|
|
810
|
+
)
|
|
677
811
|
)
|
|
678
812
|
if not row:
|
|
679
813
|
self.session.add(
|
|
680
814
|
PayCustomer(
|
|
815
|
+
tenant_id=self.tenant_id,
|
|
681
816
|
provider=out.provider,
|
|
682
817
|
provider_customer_id=out.provider_customer_id,
|
|
683
818
|
user_id=None,
|
|
684
|
-
tenant_id="",
|
|
685
819
|
)
|
|
686
820
|
)
|
|
687
821
|
return out
|
|
@@ -693,13 +827,19 @@ class PaymentsService:
|
|
|
693
827
|
async def list_products(
|
|
694
828
|
self, *, active: bool | None, limit: int, cursor: str | None
|
|
695
829
|
) -> tuple[list[ProductOut], str | None]:
|
|
696
|
-
return await self._get_adapter().list_products(
|
|
830
|
+
return await self._get_adapter().list_products(
|
|
831
|
+
active=active, limit=limit, cursor=cursor
|
|
832
|
+
)
|
|
697
833
|
|
|
698
|
-
async def update_product(
|
|
834
|
+
async def update_product(
|
|
835
|
+
self, provider_product_id: str, data: ProductUpdateIn
|
|
836
|
+
) -> ProductOut:
|
|
699
837
|
out = await self._get_adapter().update_product(provider_product_id, data)
|
|
700
838
|
# reflect DB
|
|
701
839
|
row = await self.session.scalar(
|
|
702
|
-
select(PayProduct).where(
|
|
840
|
+
select(PayProduct).where(
|
|
841
|
+
PayProduct.provider_product_id == provider_product_id
|
|
842
|
+
)
|
|
703
843
|
)
|
|
704
844
|
if row:
|
|
705
845
|
if data.name is not None:
|
|
@@ -720,10 +860,15 @@ class PaymentsService:
|
|
|
720
860
|
cursor: str | None,
|
|
721
861
|
) -> tuple[list[PriceOut], str | None]:
|
|
722
862
|
return await self._get_adapter().list_prices(
|
|
723
|
-
provider_product_id=provider_product_id,
|
|
863
|
+
provider_product_id=provider_product_id,
|
|
864
|
+
active=active,
|
|
865
|
+
limit=limit,
|
|
866
|
+
cursor=cursor,
|
|
724
867
|
)
|
|
725
868
|
|
|
726
|
-
async def update_price(
|
|
869
|
+
async def update_price(
|
|
870
|
+
self, provider_price_id: str, data: PriceUpdateIn
|
|
871
|
+
) -> PriceOut:
|
|
727
872
|
out = await self._get_adapter().update_price(provider_price_id, data)
|
|
728
873
|
row = await self.session.scalar(
|
|
729
874
|
select(PayPrice).where(PayPrice.provider_price_id == provider_price_id)
|
|
@@ -745,7 +890,10 @@ class PaymentsService:
|
|
|
745
890
|
cursor: str | None,
|
|
746
891
|
) -> tuple[list[SubscriptionOut], str | None]:
|
|
747
892
|
return await self._get_adapter().list_subscriptions(
|
|
748
|
-
customer_provider_id=customer_provider_id,
|
|
893
|
+
customer_provider_id=customer_provider_id,
|
|
894
|
+
status=status,
|
|
895
|
+
limit=limit,
|
|
896
|
+
cursor=cursor,
|
|
749
897
|
)
|
|
750
898
|
|
|
751
899
|
# ---- Payment Methods (get/update) ----
|
|
@@ -775,7 +923,9 @@ class PaymentsService:
|
|
|
775
923
|
self, *, provider_payment_intent_id: str | None, limit: int, cursor: str | None
|
|
776
924
|
) -> tuple[list[RefundOut], str | None]:
|
|
777
925
|
return await self._get_adapter().list_refunds(
|
|
778
|
-
provider_payment_intent_id=provider_payment_intent_id,
|
|
926
|
+
provider_payment_intent_id=provider_payment_intent_id,
|
|
927
|
+
limit=limit,
|
|
928
|
+
cursor=cursor,
|
|
779
929
|
)
|
|
780
930
|
|
|
781
931
|
async def get_refund(self, provider_refund_id: str) -> RefundOut:
|
|
@@ -797,10 +947,3 @@ class PaymentsService:
|
|
|
797
947
|
|
|
798
948
|
async def get_usage_record(self, usage_record_id: str) -> UsageRecordOut:
|
|
799
949
|
return await self._get_adapter().get_usage_record(usage_record_id)
|
|
800
|
-
|
|
801
|
-
async def delete_invoice_line_item(
|
|
802
|
-
self, provider_invoice_id: str, provider_line_item_id: str
|
|
803
|
-
) -> InvoiceOut:
|
|
804
|
-
return await self._get_adapter().delete_invoice_line_item(
|
|
805
|
-
provider_invoice_id, provider_line_item_id
|
|
806
|
-
)
|
|
@@ -7,7 +7,18 @@ from pydantic import BaseModel, SecretStr
|
|
|
7
7
|
|
|
8
8
|
STRIPE_KEY = os.getenv("STRIPE_SECRET") or os.getenv("STRIPE_API_KEY")
|
|
9
9
|
STRIPE_WH = os.getenv("STRIPE_WH_SECRET")
|
|
10
|
-
PROVIDER = (
|
|
10
|
+
PROVIDER = (
|
|
11
|
+
os.getenv("APF_PAYMENTS_PROVIDER")
|
|
12
|
+
or os.getenv("PAYMENTS_PROVIDER", "stripe")
|
|
13
|
+
or "stripe"
|
|
14
|
+
).lower()
|
|
15
|
+
|
|
16
|
+
AIYDAN_KEY = os.getenv("AIYDAN_API_KEY")
|
|
17
|
+
AIYDAN_CLIENT_KEY = os.getenv("AIYDAN_CLIENT_KEY")
|
|
18
|
+
AIYDAN_MERCHANT = os.getenv("AIYDAN_MERCHANT_ACCOUNT")
|
|
19
|
+
AIYDAN_HMAC = os.getenv("AIYDAN_HMAC_KEY")
|
|
20
|
+
AIYDAN_BASE_URL = os.getenv("AIYDAN_BASE_URL")
|
|
21
|
+
AIYDAN_WH = os.getenv("AIYDAN_WH_SECRET")
|
|
11
22
|
|
|
12
23
|
|
|
13
24
|
class StripeConfig(BaseModel):
|
|
@@ -15,11 +26,13 @@ class StripeConfig(BaseModel):
|
|
|
15
26
|
webhook_secret: Optional[SecretStr] = None
|
|
16
27
|
|
|
17
28
|
|
|
18
|
-
class
|
|
29
|
+
class AiydanConfig(BaseModel):
|
|
19
30
|
api_key: SecretStr
|
|
20
31
|
client_key: Optional[SecretStr] = None
|
|
21
32
|
merchant_account: Optional[str] = None
|
|
22
33
|
hmac_key: Optional[SecretStr] = None
|
|
34
|
+
base_url: Optional[str] = None
|
|
35
|
+
webhook_secret: Optional[SecretStr] = None
|
|
23
36
|
|
|
24
37
|
|
|
25
38
|
class PaymentsSettings(BaseModel):
|
|
@@ -34,7 +47,18 @@ class PaymentsSettings(BaseModel):
|
|
|
34
47
|
if STRIPE_KEY
|
|
35
48
|
else None
|
|
36
49
|
)
|
|
37
|
-
|
|
50
|
+
aiydan: Optional[AiydanConfig] = (
|
|
51
|
+
AiydanConfig(
|
|
52
|
+
api_key=SecretStr(AIYDAN_KEY),
|
|
53
|
+
client_key=SecretStr(AIYDAN_CLIENT_KEY) if AIYDAN_CLIENT_KEY else None,
|
|
54
|
+
merchant_account=AIYDAN_MERCHANT,
|
|
55
|
+
hmac_key=SecretStr(AIYDAN_HMAC) if AIYDAN_HMAC else None,
|
|
56
|
+
base_url=AIYDAN_BASE_URL,
|
|
57
|
+
webhook_secret=SecretStr(AIYDAN_WH) if AIYDAN_WH else None,
|
|
58
|
+
)
|
|
59
|
+
if AIYDAN_KEY
|
|
60
|
+
else None
|
|
61
|
+
)
|
|
38
62
|
|
|
39
63
|
|
|
40
64
|
_SETTINGS: Optional[PaymentsSettings] = None
|