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,732 @@
|
|
|
1
|
+
# APF Payments Integration Guide
|
|
2
|
+
|
|
3
|
+
A unified payments abstraction for FastAPI services supporting multiple payment providers (Stripe, Aiydan, etc.) with consistent APIs for intents, subscriptions, invoices, disputes, and more.
|
|
4
|
+
|
|
5
|
+
**Key Features:**
|
|
6
|
+
- 🚀 **Zero-boilerplate setup** – One function call creates a production-ready service
|
|
7
|
+
- 🔌 **Multi-provider support** – Stripe, Aiydan, or custom adapters
|
|
8
|
+
- 🛡️ **Built-in security** – Idempotency, rate limiting, webhook verification, auth guards
|
|
9
|
+
- 📊 **Observability included** – Prometheus metrics, OpenTelemetry tracing, structured logging
|
|
10
|
+
- 🔄 **Auto-configuration** – Reads environment variables, sensible defaults
|
|
11
|
+
- 📝 **Complete API** – 40+ endpoints for payments, subscriptions, invoices, disputes, refunds
|
|
12
|
+
- 🧪 **Test-friendly** – Mock adapters, comprehensive test coverage
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### 1. Install & Configure
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Install with your provider extras
|
|
22
|
+
pip install svc-infra stripe # for Stripe
|
|
23
|
+
# or
|
|
24
|
+
pip install svc-infra aiydan # for Aiydan (when available)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Set environment variables for your chosen provider:
|
|
28
|
+
|
|
29
|
+
**Stripe:**
|
|
30
|
+
```bash
|
|
31
|
+
export STRIPE_SECRET="sk_test_..."
|
|
32
|
+
export STRIPE_WH_SECRET="whsec_..." # optional, for webhook verification
|
|
33
|
+
export PAYMENTS_PROVIDER="stripe" # default
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Aiydan:**
|
|
37
|
+
```bash
|
|
38
|
+
export AIYDAN_API_KEY="aiydan_key_..."
|
|
39
|
+
export AIYDAN_CLIENT_KEY="..." # optional
|
|
40
|
+
export AIYDAN_MERCHANT_ACCOUNT="..." # optional
|
|
41
|
+
export AIYDAN_HMAC_KEY="..." # optional
|
|
42
|
+
export AIYDAN_BASE_URL="https://..." # optional
|
|
43
|
+
export AIYDAN_WH_SECRET="..." # optional
|
|
44
|
+
export PAYMENTS_PROVIDER="aiydan"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Create Your FastAPI App with Payments
|
|
48
|
+
|
|
49
|
+
**Option A: The Easiest Way (Recommended)**
|
|
50
|
+
|
|
51
|
+
Use `easy_service_app` for a complete FastAPI service with logging, observability, and payments in one call:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from svc_infra.api.fastapi.ease import easy_service_app
|
|
55
|
+
|
|
56
|
+
app = easy_service_app(
|
|
57
|
+
name="My Payment Service",
|
|
58
|
+
release="1.0.0",
|
|
59
|
+
versions=[
|
|
60
|
+
("v1", "myapp.routers.v1", None), # (tag, routers_package, public_base_url)
|
|
61
|
+
],
|
|
62
|
+
root_routers="myapp.routers.root", # includes your root-level routes
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# That's it! Logging, metrics, and all svc-infra features are auto-configured from env vars.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Then add payments:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from svc_infra.api.fastapi.apf_payments.setup import add_payments
|
|
72
|
+
|
|
73
|
+
add_payments(app, prefix="/payments")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Option B: Manual Setup (More Control)**
|
|
77
|
+
|
|
78
|
+
If you need fine-grained control:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from fastapi import FastAPI
|
|
82
|
+
from svc_infra.api.fastapi.apf_payments.setup import add_payments
|
|
83
|
+
|
|
84
|
+
app = FastAPI()
|
|
85
|
+
|
|
86
|
+
# Auto-registers default providers (Stripe, Aiydan) based on env config
|
|
87
|
+
add_payments(app, prefix="/payments")
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Option C: Full-Featured Service Setup**
|
|
91
|
+
|
|
92
|
+
Use `setup_service_api` for multi-version APIs with custom routers:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from svc_infra.api.fastapi.setup import setup_service_api
|
|
96
|
+
from svc_infra.api.fastapi.openapi.models import ServiceInfo, APIVersionSpec
|
|
97
|
+
from svc_infra.api.fastapi.apf_payments.setup import add_payments
|
|
98
|
+
|
|
99
|
+
service = ServiceInfo(name="My Payment Service", release="1.0.0")
|
|
100
|
+
versions = [
|
|
101
|
+
APIVersionSpec(tag="v1", routers_package="myapp.routers.v1"),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
app = setup_service_api(
|
|
105
|
+
service=service,
|
|
106
|
+
versions=versions,
|
|
107
|
+
root_routers="myapp.routers.root",
|
|
108
|
+
public_cors_origins=["http://localhost:3000"],
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
add_payments(app, prefix="/payments")
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Tenant Context**
|
|
115
|
+
|
|
116
|
+
All payments endpoints require a tenant identifier. The FastAPI router now
|
|
117
|
+
derives it automatically from the authenticated principal:
|
|
118
|
+
|
|
119
|
+
- API key principals → ``principal.api_key.tenant_id``
|
|
120
|
+
- User principals → ``principal.user.tenant_id``
|
|
121
|
+
- Fallbacks: ``X-Tenant-Id`` request header or ``request.state.tenant_id``
|
|
122
|
+
|
|
123
|
+
If you need custom mapping logic (for example, translating API keys to an
|
|
124
|
+
external tenant registry), register an override during startup:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from svc_infra.api.fastapi.apf_payments.router import set_payments_tenant_resolver
|
|
128
|
+
|
|
129
|
+
async def resolve_tenant(request, identity, header):
|
|
130
|
+
# return a string tenant id, or None to fall back to the defaults
|
|
131
|
+
return "tenant-from-custom-logic"
|
|
132
|
+
|
|
133
|
+
set_payments_tenant_resolver(resolve_tenant)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
If no tenant can be derived (and the override also returns ``None``), the
|
|
137
|
+
router responds with ``400 tenant_context_missing`` so callers can supply the
|
|
138
|
+
missing context explicitly.
|
|
139
|
+
|
|
140
|
+
**Environment-Based Configuration**
|
|
141
|
+
|
|
142
|
+
The `easy_service_app` reads these env vars automatically:
|
|
143
|
+
- `ENABLE_LOGGING` (default: true) – Auto-configures JSON logs in prod, plain logs in dev
|
|
144
|
+
- `ENABLE_OBS` (default: true) – Enables Prometheus metrics at `/metrics`
|
|
145
|
+
- `LOG_LEVEL` – DEBUG, INFO, WARNING, etc.
|
|
146
|
+
- `LOG_FORMAT` – json or plain
|
|
147
|
+
- `CORS_ALLOW_ORIGINS` – Comma-separated CORS origins
|
|
148
|
+
|
|
149
|
+
No boilerplate needed! 🎉
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
That's it! Your app now has:
|
|
154
|
+
- `POST /payments/customers` – Create/upsert customers
|
|
155
|
+
- `POST /payments/intents` – Create payment intents
|
|
156
|
+
- `POST /payments/methods/attach` – Attach payment methods
|
|
157
|
+
- `GET /payments/methods` – List customer payment methods
|
|
158
|
+
- `POST /payments/subscriptions` – Create subscriptions
|
|
159
|
+
- `POST /payments/invoices` – Create invoices
|
|
160
|
+
- `POST /payments/webhooks/{provider}` – Handle provider webhooks
|
|
161
|
+
- And many more endpoints for refunds, disputes, payouts, etc.
|
|
162
|
+
|
|
163
|
+
### 3. Complete Example (app.py)
|
|
164
|
+
|
|
165
|
+
Here's a complete, production-ready service in ~20 lines:
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
# app.py
|
|
169
|
+
from svc_infra.api.fastapi.ease import easy_service_app
|
|
170
|
+
from svc_infra.api.fastapi.apf_payments.setup import add_payments
|
|
171
|
+
|
|
172
|
+
# Create service with auto-configured logging, metrics, CORS, middleware
|
|
173
|
+
app = easy_service_app(
|
|
174
|
+
name="Payment API",
|
|
175
|
+
release="1.0.0",
|
|
176
|
+
versions=[
|
|
177
|
+
("v1", "myapp.routers.v1", None),
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Add payments functionality
|
|
182
|
+
add_payments(app, prefix="/payments")
|
|
183
|
+
|
|
184
|
+
# Optional: Add custom routes
|
|
185
|
+
@app.get("/health")
|
|
186
|
+
async def health():
|
|
187
|
+
return {"status": "healthy"}
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
import uvicorn
|
|
191
|
+
uvicorn.run(app, host="0.0.0.0", port=8000)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Run it:**
|
|
195
|
+
```bash
|
|
196
|
+
export STRIPE_SECRET="sk_test_..."
|
|
197
|
+
export STRIPE_WH_SECRET="whsec_..."
|
|
198
|
+
python app.py
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**Access:**
|
|
202
|
+
- Swagger docs: `http://localhost:8000/` (landing page with all doc links)
|
|
203
|
+
- Version v1: `http://localhost:8000/v1/docs`
|
|
204
|
+
- Payments: `http://localhost:8000/payments/`
|
|
205
|
+
- Metrics: `http://localhost:8000/metrics`
|
|
206
|
+
|
|
207
|
+
### Why Use `easy_service_app`?
|
|
208
|
+
|
|
209
|
+
Without `easy_service_app`, you'd need to manually:
|
|
210
|
+
- Configure logging (JSON in prod, plain in dev)
|
|
211
|
+
- Set up CORS middleware
|
|
212
|
+
- Add request ID middleware
|
|
213
|
+
- Configure error handlers (catch-all exceptions)
|
|
214
|
+
- Add idempotency middleware
|
|
215
|
+
- Add rate limiting middleware
|
|
216
|
+
- Set up OpenAPI documentation
|
|
217
|
+
- Configure Prometheus metrics
|
|
218
|
+
- Add OpenTelemetry instrumentation
|
|
219
|
+
- Mount versioned routers
|
|
220
|
+
- Create a landing page for docs
|
|
221
|
+
- Handle environment-based config
|
|
222
|
+
|
|
223
|
+
**That's 50+ lines of boilerplate!** With `easy_service_app`, it's **3 lines**:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
app = easy_service_app(name="My API", release="1.0.0", versions=[("v1", "myapp.routers.v1", None)])
|
|
227
|
+
add_payments(app, prefix="/payments")
|
|
228
|
+
# Done! Production-ready service with all features.
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
## Core Concepts
|
|
234
|
+
|
|
235
|
+
### Providers
|
|
236
|
+
Payment providers are registered adapters that implement the `ProviderAdapter` protocol. Built-in:
|
|
237
|
+
- **Stripe** (`stripe`): Full-featured, production-ready.
|
|
238
|
+
- **Aiydan** (`aiydan`): Custom provider with similar capabilities.
|
|
239
|
+
|
|
240
|
+
The active provider is selected via `PAYMENTS_PROVIDER` env var.
|
|
241
|
+
|
|
242
|
+
### Router Groups by Auth Posture
|
|
243
|
+
- **Public** (`/payments/webhooks/{provider}`): No auth, for webhooks.
|
|
244
|
+
- **User** (`/payments/customers`, `/payments/intents`, `/payments/methods/attach`): Requires user JWT.
|
|
245
|
+
- **Protected** (`/payments/methods`, `/payments/intents/{id}/confirm`, etc.): Requires auth (user or service).
|
|
246
|
+
- **Service** (`/payments/products`, `/payments/prices`): Service-to-service auth (API key).
|
|
247
|
+
|
|
248
|
+
### Key Entities
|
|
249
|
+
- **Customers**: Link your app users to provider customer records.
|
|
250
|
+
- **Payment Intents**: Represent a payment attempt (amount, currency, status).
|
|
251
|
+
- **Payment Methods**: Saved cards/bank accounts attached to customers.
|
|
252
|
+
- **Subscriptions**: Recurring billing tied to prices and products.
|
|
253
|
+
- **Invoices**: Generated bills (automatic or manual).
|
|
254
|
+
- **Refunds / Disputes / Payouts**: Post-payment flows.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Common Workflows
|
|
259
|
+
|
|
260
|
+
### Create a Customer and Attach a Payment Method
|
|
261
|
+
|
|
262
|
+
```python
|
|
263
|
+
import httpx
|
|
264
|
+
|
|
265
|
+
# 1. Create/upsert customer
|
|
266
|
+
resp = await client.post(
|
|
267
|
+
"/payments/customers",
|
|
268
|
+
json={"email": "user@example.com", "name": "Alice"},
|
|
269
|
+
headers={"Idempotency-Key": "customer-alice-1"}
|
|
270
|
+
)
|
|
271
|
+
customer = resp.json()
|
|
272
|
+
customer_id = customer["provider_customer_id"] # e.g., "cus_123"
|
|
273
|
+
|
|
274
|
+
# 2. Attach payment method (token from frontend)
|
|
275
|
+
resp = await client.post(
|
|
276
|
+
"/payments/methods/attach",
|
|
277
|
+
json={
|
|
278
|
+
"customer_provider_id": customer_id,
|
|
279
|
+
"payment_method_token": "pm_test_...", # from Stripe.js or similar
|
|
280
|
+
"make_default": True
|
|
281
|
+
},
|
|
282
|
+
headers={"Idempotency-Key": "attach-pm-1"}
|
|
283
|
+
)
|
|
284
|
+
method = resp.json()
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Create a Payment Intent
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
# Create an intent for $50.00 USD
|
|
291
|
+
resp = await client.post(
|
|
292
|
+
"/payments/intents",
|
|
293
|
+
json={
|
|
294
|
+
"amount": 5000, # minor units (cents)
|
|
295
|
+
"currency": "USD",
|
|
296
|
+
"description": "Order #12345",
|
|
297
|
+
"capture_method": "automatic"
|
|
298
|
+
},
|
|
299
|
+
headers={"Idempotency-Key": "intent-order-12345"}
|
|
300
|
+
)
|
|
301
|
+
intent = resp.json()
|
|
302
|
+
client_secret = intent["client_secret"] # pass to frontend for confirmation
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### List Customer Payment Methods
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
resp = await client.get(
|
|
309
|
+
"/payments/methods",
|
|
310
|
+
params={"customer_provider_id": "cus_123"}
|
|
311
|
+
)
|
|
312
|
+
methods = resp.json()["items"]
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Create a Subscription
|
|
316
|
+
|
|
317
|
+
```python
|
|
318
|
+
# 1. Create product & price (service-level)
|
|
319
|
+
product_resp = await client.post(
|
|
320
|
+
"/payments/products",
|
|
321
|
+
json={"name": "Pro Plan", "active": True},
|
|
322
|
+
headers={"Idempotency-Key": "product-pro", "Authorization": "Bearer <service-token>"}
|
|
323
|
+
)
|
|
324
|
+
product_id = product_resp.json()["provider_product_id"]
|
|
325
|
+
|
|
326
|
+
price_resp = await client.post(
|
|
327
|
+
"/payments/prices",
|
|
328
|
+
json={
|
|
329
|
+
"provider_product_id": product_id,
|
|
330
|
+
"currency": "USD",
|
|
331
|
+
"unit_amount": 2000,
|
|
332
|
+
"interval": "month",
|
|
333
|
+
"active": True
|
|
334
|
+
},
|
|
335
|
+
headers={"Idempotency-Key": "price-pro-monthly", "Authorization": "Bearer <service-token>"}
|
|
336
|
+
)
|
|
337
|
+
price_id = price_resp.json()["provider_price_id"]
|
|
338
|
+
|
|
339
|
+
# 2. Subscribe customer
|
|
340
|
+
sub_resp = await client.post(
|
|
341
|
+
"/payments/subscriptions",
|
|
342
|
+
json={
|
|
343
|
+
"customer_provider_id": "cus_123",
|
|
344
|
+
"price_provider_id": price_id,
|
|
345
|
+
"quantity": 1
|
|
346
|
+
},
|
|
347
|
+
headers={"Idempotency-Key": "sub-alice-pro"}
|
|
348
|
+
)
|
|
349
|
+
subscription = sub_resp.json()
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Handle Webhooks
|
|
353
|
+
|
|
354
|
+
```python
|
|
355
|
+
# Provider (e.g., Stripe) POSTs to /payments/webhooks/stripe
|
|
356
|
+
# The adapter verifies signature and parses event
|
|
357
|
+
# Events trigger internal actions (e.g., mark payment succeeded, post to ledger)
|
|
358
|
+
|
|
359
|
+
# In your app:
|
|
360
|
+
from svc_infra.apf_payments.service import PaymentsService
|
|
361
|
+
|
|
362
|
+
async def on_payment_succeeded(event_data: dict):
|
|
363
|
+
# Custom business logic after payment
|
|
364
|
+
pass
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Advanced Usage
|
|
370
|
+
|
|
371
|
+
### Fine-Tuned App Configuration
|
|
372
|
+
|
|
373
|
+
Override env-based defaults with `EasyAppOptions`:
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
from svc_infra.api.fastapi.ease import easy_service_app, EasyAppOptions, LoggingOptions, ObservabilityOptions
|
|
377
|
+
|
|
378
|
+
app = easy_service_app(
|
|
379
|
+
name="Payment API",
|
|
380
|
+
release="1.0.0",
|
|
381
|
+
versions=[("v1", "myapp.routers.v1", None)],
|
|
382
|
+
options=EasyAppOptions(
|
|
383
|
+
logging=LoggingOptions(
|
|
384
|
+
enable=True,
|
|
385
|
+
level="DEBUG",
|
|
386
|
+
fmt="json"
|
|
387
|
+
),
|
|
388
|
+
observability=ObservabilityOptions(
|
|
389
|
+
enable=True,
|
|
390
|
+
db_engines=[engine], # pass SQLAlchemy engines for connection pool metrics
|
|
391
|
+
metrics_path="/metrics",
|
|
392
|
+
skip_metric_paths=["/health", "/metrics"]
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
)
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
Or use flags for quick toggles:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
app = easy_service_app(
|
|
402
|
+
name="Payment API",
|
|
403
|
+
release="1.0.0",
|
|
404
|
+
versions=[("v1", "myapp.routers.v1", None)],
|
|
405
|
+
enable_logging=True,
|
|
406
|
+
enable_observability=False # disable metrics
|
|
407
|
+
)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**Configuration Precedence** (strongest → weakest):
|
|
411
|
+
1. Function arguments (`enable_logging`, `enable_observability`)
|
|
412
|
+
2. `options=` parameter
|
|
413
|
+
3. Environment variables (`ENABLE_LOGGING`, `ENABLE_OBS`, etc.)
|
|
414
|
+
|
|
415
|
+
### Custom Provider Adapter
|
|
416
|
+
|
|
417
|
+
If you need a provider not included by default:
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
from svc_infra.apf_payments.provider.base import ProviderAdapter
|
|
421
|
+
from svc_infra.api.fastapi.apf_payments.setup import add_payments
|
|
422
|
+
|
|
423
|
+
class MyCustomAdapter(ProviderAdapter):
|
|
424
|
+
name = "mycustom"
|
|
425
|
+
|
|
426
|
+
async def ensure_customer(self, data):
|
|
427
|
+
# Your implementation
|
|
428
|
+
...
|
|
429
|
+
|
|
430
|
+
async def create_intent(self, data, *, user_id):
|
|
431
|
+
# Your implementation
|
|
432
|
+
...
|
|
433
|
+
|
|
434
|
+
# Implement all required methods...
|
|
435
|
+
|
|
436
|
+
# Register it
|
|
437
|
+
add_payments(
|
|
438
|
+
app,
|
|
439
|
+
register_default_providers=False, # skip Stripe/Aiydan auto-registration
|
|
440
|
+
adapters=[MyCustomAdapter()]
|
|
441
|
+
)
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Multiple Providers in One App
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
from svc_infra.apf_payments.provider.stripe import StripeAdapter
|
|
448
|
+
from svc_infra.apf_payments.provider.aiydan import AiydanAdapter
|
|
449
|
+
from svc_infra.apf_payments.provider.registry import get_provider_registry
|
|
450
|
+
|
|
451
|
+
# Register both
|
|
452
|
+
reg = get_provider_registry()
|
|
453
|
+
reg.register(StripeAdapter())
|
|
454
|
+
reg.register(AiydanAdapter())
|
|
455
|
+
|
|
456
|
+
# Select at runtime via settings or per-request logic
|
|
457
|
+
# (Default provider is controlled by PAYMENTS_PROVIDER env)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Database Models
|
|
461
|
+
|
|
462
|
+
The payments module creates these tables (via Alembic migrations):
|
|
463
|
+
- `pay_customers`: Maps app users to provider customer IDs
|
|
464
|
+
- `pay_intents`: Stores payment intent records
|
|
465
|
+
- `pay_payment_methods`: Cached payment methods
|
|
466
|
+
- `pay_products`, `pay_prices`: Product/price catalog
|
|
467
|
+
- `pay_subscriptions`: Active subscriptions
|
|
468
|
+
- `pay_invoices`: Invoice records
|
|
469
|
+
- `pay_events`: Webhook event log
|
|
470
|
+
- `ledger_entries`: Financial ledger (debits/credits for payments, refunds, fees, payouts)
|
|
471
|
+
|
|
472
|
+
To generate migrations:
|
|
473
|
+
```bash
|
|
474
|
+
# From your project root
|
|
475
|
+
svc-infra db revision -m "Add payments tables"
|
|
476
|
+
svc-infra db upgrade head
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
### Observability
|
|
480
|
+
|
|
481
|
+
Payments endpoints are instrumented with OpenTelemetry spans. Webhook processing is logged.
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
from svc_infra.obs import add_observability
|
|
485
|
+
|
|
486
|
+
shutdown = add_observability(app, db_engines=[engine])
|
|
487
|
+
# Metrics available at /metrics
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Idempotency
|
|
491
|
+
|
|
492
|
+
All mutating endpoints (`POST`, `PUT`, `DELETE`) require an `Idempotency-Key` header to prevent duplicate operations:
|
|
493
|
+
|
|
494
|
+
```python
|
|
495
|
+
headers = {"Idempotency-Key": "unique-key-per-request"}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
Implemented via `IdempotencyMiddleware` (uses in-memory store by default; plug in Redis for production).
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## Configuration Reference
|
|
503
|
+
|
|
504
|
+
### Environment Variables
|
|
505
|
+
|
|
506
|
+
**FastAPI Service Setup** (used by `easy_service_app`):
|
|
507
|
+
|
|
508
|
+
| Variable | Description | Default |
|
|
509
|
+
|----------|-------------|---------|
|
|
510
|
+
| `ENABLE_LOGGING` | Enable automatic logging setup | `true` |
|
|
511
|
+
| `ENABLE_OBS` | Enable observability (metrics/tracing) | `true` |
|
|
512
|
+
| `LOG_LEVEL` | Logging level (DEBUG, INFO, WARNING, ERROR) | INFO (prod/test), DEBUG (dev/local) |
|
|
513
|
+
| `LOG_FORMAT` | Log format (`json` or `plain`) | json (prod/test), plain (dev/local) |
|
|
514
|
+
| `LOG_DROP_PATHS` | Comma-separated paths to exclude from logs | `/metrics` (prod/test) |
|
|
515
|
+
| `CORS_ALLOW_ORIGINS` | Comma-separated CORS origins | `http://localhost:3000` |
|
|
516
|
+
| `METRICS_PATH` | Path for Prometheus metrics endpoint | `/metrics` |
|
|
517
|
+
| `OBS_SKIP_PATHS` | Comma/space-separated paths to skip in metrics | `/metrics,/health` |
|
|
518
|
+
| `APP_ENV` | Environment name (prod, test, dev, local) | Auto-detected from `RAILWAY_ENVIRONMENT_NAME` or defaults to `local` |
|
|
519
|
+
|
|
520
|
+
**Payment Provider Configuration**:
|
|
521
|
+
|
|
522
|
+
| Variable | Description | Default |
|
|
523
|
+
|----------|-------------|---------|
|
|
524
|
+
| `PAYMENTS_PROVIDER` | Active provider name (`stripe`, `aiydan`, etc.) | `stripe` |
|
|
525
|
+
| `STRIPE_SECRET` or `STRIPE_API_KEY` | Stripe secret key | - |
|
|
526
|
+
| `STRIPE_WH_SECRET` | Stripe webhook signing secret | - |
|
|
527
|
+
| `AIYDAN_API_KEY` | Aiydan API key | - |
|
|
528
|
+
| `AIYDAN_CLIENT_KEY` | Aiydan client key (optional) | - |
|
|
529
|
+
| `AIYDAN_MERCHANT_ACCOUNT` | Aiydan merchant account (optional) | - |
|
|
530
|
+
| `AIYDAN_HMAC_KEY` | Aiydan HMAC key for signatures (optional) | - |
|
|
531
|
+
| `AIYDAN_BASE_URL` | Aiydan API base URL (optional) | - |
|
|
532
|
+
| `AIYDAN_WH_SECRET` | Aiydan webhook secret (optional) | - |
|
|
533
|
+
|
|
534
|
+
### Settings Object
|
|
535
|
+
|
|
536
|
+
```python
|
|
537
|
+
from svc_infra.apf_payments.settings import get_payments_settings
|
|
538
|
+
|
|
539
|
+
settings = get_payments_settings()
|
|
540
|
+
print(settings.default_provider) # "stripe"
|
|
541
|
+
print(settings.stripe.secret_key.get_secret_value()) # "sk_test_..."
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
---
|
|
545
|
+
|
|
546
|
+
## API Endpoints Summary
|
|
547
|
+
|
|
548
|
+
### Customers
|
|
549
|
+
- `POST /payments/customers` – Create/upsert customer (user auth)
|
|
550
|
+
|
|
551
|
+
### Payment Intents
|
|
552
|
+
- `POST /payments/intents` – Create intent (user auth)
|
|
553
|
+
- `POST /payments/intents/{id}/confirm` – Confirm intent (protected)
|
|
554
|
+
- `POST /payments/intents/{id}/cancel` – Cancel intent (protected)
|
|
555
|
+
- `POST /payments/intents/{id}/refund` – Refund intent (protected)
|
|
556
|
+
- `POST /payments/intents/{id}/capture` – Capture manual intent (protected)
|
|
557
|
+
- `GET /payments/intents/{id}` – Retrieve intent (protected)
|
|
558
|
+
- `GET /payments/intents` – List intents (protected)
|
|
559
|
+
|
|
560
|
+
### Payment Methods
|
|
561
|
+
- `POST /payments/methods/attach` – Attach method to customer (user auth)
|
|
562
|
+
- `GET /payments/methods` – List customer methods (protected, requires `customer_provider_id` query param)
|
|
563
|
+
- `POST /payments/methods/{id}/detach` – Detach method (protected)
|
|
564
|
+
- `POST /payments/methods/{id}/default` – Set as default (protected, requires `customer_provider_id` query param)
|
|
565
|
+
- `GET /payments/methods/{id}` – Retrieve method (protected)
|
|
566
|
+
- `PUT /payments/methods/{id}` – Update method (protected)
|
|
567
|
+
|
|
568
|
+
### Products & Prices (Service Auth)
|
|
569
|
+
- `POST /payments/products` – Create product
|
|
570
|
+
- `GET /payments/products` – List products
|
|
571
|
+
- `GET /payments/products/{id}` – Retrieve product
|
|
572
|
+
- `PUT /payments/products/{id}` – Update product
|
|
573
|
+
- `POST /payments/prices` – Create price
|
|
574
|
+
- `GET /payments/prices` – List prices
|
|
575
|
+
- `GET /payments/prices/{id}` – Retrieve price
|
|
576
|
+
- `PUT /payments/prices/{id}` – Update price
|
|
577
|
+
|
|
578
|
+
### Subscriptions
|
|
579
|
+
- `POST /payments/subscriptions` – Create subscription (protected)
|
|
580
|
+
- `POST /payments/subscriptions/{id}` – Update subscription (protected)
|
|
581
|
+
- `POST /payments/subscriptions/{id}/cancel` – Cancel subscription (protected)
|
|
582
|
+
- `GET /payments/subscriptions/{id}` – Retrieve subscription (protected)
|
|
583
|
+
- `GET /payments/subscriptions` – List subscriptions (protected)
|
|
584
|
+
|
|
585
|
+
### Invoices
|
|
586
|
+
- `POST /payments/invoices` – Create invoice (protected)
|
|
587
|
+
- `POST /payments/invoices/{id}/finalize` – Finalize invoice (protected)
|
|
588
|
+
- `POST /payments/invoices/{id}/void` – Void invoice (protected)
|
|
589
|
+
- `POST /payments/invoices/{id}/pay` – Pay invoice (protected)
|
|
590
|
+
- `POST /payments/invoices/{id}/line-items` – Add line item (protected)
|
|
591
|
+
- `GET /payments/invoices/{id}` – Retrieve invoice (protected)
|
|
592
|
+
- `GET /payments/invoices` – List invoices (protected)
|
|
593
|
+
- `GET /payments/invoices/{id}/line-items` – List line items (protected)
|
|
594
|
+
- `GET /payments/invoices/preview` – Preview upcoming invoice (protected)
|
|
595
|
+
|
|
596
|
+
### Disputes
|
|
597
|
+
- `GET /payments/disputes` – List disputes (protected)
|
|
598
|
+
- `GET /payments/disputes/{id}` – Retrieve dispute (protected)
|
|
599
|
+
- `POST /payments/disputes/{id}/evidence` – Submit evidence (protected)
|
|
600
|
+
|
|
601
|
+
### Balance & Payouts
|
|
602
|
+
- `GET /payments/balance` – Get balance snapshot (protected)
|
|
603
|
+
- `GET /payments/payouts` – List payouts (protected)
|
|
604
|
+
- `GET /payments/payouts/{id}` – Retrieve payout (protected)
|
|
605
|
+
|
|
606
|
+
### Refunds
|
|
607
|
+
- `GET /payments/refunds` – List refunds (protected)
|
|
608
|
+
- `GET /payments/refunds/{id}` – Retrieve refund (protected)
|
|
609
|
+
|
|
610
|
+
### Transactions & Statements
|
|
611
|
+
- `GET /payments/transactions` – List all ledger transactions (protected)
|
|
612
|
+
- `GET /payments/statements/daily` – Daily rollup statements (protected)
|
|
613
|
+
|
|
614
|
+
### Usage Records (Metered Billing)
|
|
615
|
+
- `POST /payments/usage-records` – Create usage record (protected)
|
|
616
|
+
- `GET /payments/usage-records` – List usage records (protected)
|
|
617
|
+
- `GET /payments/usage-records/{id}` – Retrieve usage record (protected)
|
|
618
|
+
|
|
619
|
+
### Setup Intents (Off-Session)
|
|
620
|
+
- `POST /payments/setup-intents` – Create setup intent (user auth)
|
|
621
|
+
- `POST /payments/setup-intents/{id}/confirm` – Confirm setup intent (protected)
|
|
622
|
+
- `GET /payments/setup-intents/{id}` – Retrieve setup intent (protected)
|
|
623
|
+
|
|
624
|
+
### Webhooks
|
|
625
|
+
- `POST /payments/webhooks/{provider}` – Receive provider webhooks (public, no auth)
|
|
626
|
+
|
|
627
|
+
### Webhook Replay (Testing)
|
|
628
|
+
- `POST /payments/webhooks/{provider}/replay` – Replay webhook event (protected)
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## Testing
|
|
633
|
+
|
|
634
|
+
### Unit Tests
|
|
635
|
+
|
|
636
|
+
```bash
|
|
637
|
+
pytest tests/payments/
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
### Integration Tests
|
|
641
|
+
|
|
642
|
+
Mock the provider adapter:
|
|
643
|
+
|
|
644
|
+
```python
|
|
645
|
+
from svc_infra.apf_payments.provider.base import ProviderAdapter
|
|
646
|
+
|
|
647
|
+
class FakeAdapter(ProviderAdapter):
|
|
648
|
+
name = "fake"
|
|
649
|
+
|
|
650
|
+
async def ensure_customer(self, data):
|
|
651
|
+
return CustomerOut(
|
|
652
|
+
id="cus_fake",
|
|
653
|
+
provider="fake",
|
|
654
|
+
provider_customer_id="cus_fake",
|
|
655
|
+
email=data.email
|
|
656
|
+
)
|
|
657
|
+
# ... implement other methods
|
|
658
|
+
|
|
659
|
+
add_payments(app, register_default_providers=False, adapters=[FakeAdapter()])
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### Local Webhook Testing
|
|
663
|
+
|
|
664
|
+
Use Stripe CLI or similar:
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
stripe listen --forward-to http://localhost:8000/payments/webhooks/stripe
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
## Troubleshooting
|
|
673
|
+
|
|
674
|
+
### "No payments adapter registered for 'xyz'"
|
|
675
|
+
- Ensure you've set the correct `PAYMENTS_PROVIDER` env var.
|
|
676
|
+
- Verify the provider SDK is installed (`pip install stripe` or `pip install aiydan`).
|
|
677
|
+
- Check that the provider credentials are configured.
|
|
678
|
+
|
|
679
|
+
### "Idempotency-Key required"
|
|
680
|
+
- All mutating operations need an `Idempotency-Key` header.
|
|
681
|
+
- Use a unique key per logical operation (e.g., `f"order-{order_id}-payment"`).
|
|
682
|
+
|
|
683
|
+
### Webhook signature verification fails
|
|
684
|
+
- Ensure `STRIPE_WH_SECRET` (or equivalent) is set correctly.
|
|
685
|
+
- Check that the webhook endpoint URL in your provider dashboard matches your deployment.
|
|
686
|
+
|
|
687
|
+
### Database migrations not applied
|
|
688
|
+
```bash
|
|
689
|
+
svc-infra db upgrade head
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
## Security Best Practices
|
|
695
|
+
|
|
696
|
+
1. **Never log or expose secret keys** – Use `SecretStr` in settings.
|
|
697
|
+
2. **Always verify webhook signatures** – Prevents spoofed events.
|
|
698
|
+
3. **Use HTTPS in production** – Payment data is sensitive.
|
|
699
|
+
4. **Implement rate limiting** – Protect webhook endpoints from abuse.
|
|
700
|
+
5. **Store minimal PII** – Only cache what's needed; rely on provider for full records.
|
|
701
|
+
6. **Rotate API keys periodically** – Follow provider recommendations.
|
|
702
|
+
|
|
703
|
+
---
|
|
704
|
+
|
|
705
|
+
## Performance Tips
|
|
706
|
+
|
|
707
|
+
- **Cache payment methods locally** – Reduce API calls to provider.
|
|
708
|
+
- **Use cursor-based pagination** – More efficient for large result sets.
|
|
709
|
+
- **Process webhooks asynchronously** – Respond quickly (< 5s) to avoid retries.
|
|
710
|
+
- **Monitor provider API usage** – Stay within rate limits.
|
|
711
|
+
|
|
712
|
+
---
|
|
713
|
+
|
|
714
|
+
## Roadmap
|
|
715
|
+
|
|
716
|
+
- [ ] Support for additional providers (PayPal, Square, etc.)
|
|
717
|
+
- [ ] Enhanced ledger reconciliation tools
|
|
718
|
+
- [ ] Multi-currency support improvements
|
|
719
|
+
- [ ] Automatic retry logic for failed provider calls
|
|
720
|
+
- [ ] Admin dashboard for payment operations
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## Support
|
|
725
|
+
|
|
726
|
+
- GitHub Issues: [svc-infra/issues](https://github.com/your-org/svc-infra/issues)
|
|
727
|
+
- Documentation: [Full docs](https://github.com/your-org/svc-infra#readme)
|
|
728
|
+
- Provider docs: [Stripe](https://stripe.com/docs/api), [Aiydan](#) (when available)
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
**Happy payments building!** 🎉
|