svc-infra 0.1.585__py3-none-any.whl → 0.1.587__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/provider/stripe.py +670 -100
- svc_infra/api/fastapi/apf_payments/router.py +9 -9
- {svc_infra-0.1.585.dist-info → svc_infra-0.1.587.dist-info}/METADATA +1 -1
- {svc_infra-0.1.585.dist-info → svc_infra-0.1.587.dist-info}/RECORD +6 -6
- {svc_infra-0.1.585.dist-info → svc_infra-0.1.587.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.585.dist-info → svc_infra-0.1.587.dist-info}/entry_points.txt +0 -0
|
@@ -6,15 +6,37 @@ from typing import Any, Optional
|
|
|
6
6
|
import anyio
|
|
7
7
|
|
|
8
8
|
from ..schemas import (
|
|
9
|
+
BalanceSnapshotOut,
|
|
9
10
|
CustomerOut,
|
|
10
11
|
CustomerUpsertIn,
|
|
12
|
+
DisputeOut,
|
|
11
13
|
IntentCreateIn,
|
|
12
14
|
IntentOut,
|
|
15
|
+
InvoiceCreateIn,
|
|
13
16
|
InvoiceLineItemIn,
|
|
17
|
+
InvoiceLineItemOut,
|
|
14
18
|
InvoiceOut,
|
|
15
19
|
NextAction,
|
|
20
|
+
PaymentMethodAttachIn,
|
|
21
|
+
PaymentMethodOut,
|
|
22
|
+
PaymentMethodUpdateIn,
|
|
23
|
+
PayoutOut,
|
|
24
|
+
PriceCreateIn,
|
|
25
|
+
PriceOut,
|
|
26
|
+
PriceUpdateIn,
|
|
27
|
+
ProductCreateIn,
|
|
28
|
+
ProductOut,
|
|
29
|
+
ProductUpdateIn,
|
|
16
30
|
RefundIn,
|
|
31
|
+
RefundOut,
|
|
32
|
+
SetupIntentCreateIn,
|
|
33
|
+
SetupIntentOut,
|
|
34
|
+
SubscriptionCreateIn,
|
|
35
|
+
SubscriptionOut,
|
|
36
|
+
SubscriptionUpdateIn,
|
|
17
37
|
UsageRecordIn,
|
|
38
|
+
UsageRecordListFilter,
|
|
39
|
+
UsageRecordOut,
|
|
18
40
|
)
|
|
19
41
|
from ..settings import get_payments_settings
|
|
20
42
|
from .base import ProviderAdapter
|
|
@@ -56,6 +78,110 @@ def _inv_to_out(inv) -> InvoiceOut:
|
|
|
56
78
|
)
|
|
57
79
|
|
|
58
80
|
|
|
81
|
+
def _pm_to_out(pm, *, is_default: bool = False) -> PaymentMethodOut:
|
|
82
|
+
card = getattr(pm, "card", None) or {}
|
|
83
|
+
return PaymentMethodOut(
|
|
84
|
+
id=pm.id,
|
|
85
|
+
provider="stripe",
|
|
86
|
+
provider_customer_id=getattr(pm, "customer", None) or "",
|
|
87
|
+
provider_method_id=pm.id,
|
|
88
|
+
brand=card.get("brand"),
|
|
89
|
+
last4=card.get("last4"),
|
|
90
|
+
exp_month=card.get("exp_month"),
|
|
91
|
+
exp_year=card.get("exp_year"),
|
|
92
|
+
is_default=bool(is_default),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _product_to_out(p) -> ProductOut:
|
|
97
|
+
return ProductOut(
|
|
98
|
+
id=p.id,
|
|
99
|
+
provider="stripe",
|
|
100
|
+
provider_product_id=p.id,
|
|
101
|
+
name=p.name,
|
|
102
|
+
active=bool(p.active),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _price_to_out(pr) -> PriceOut:
|
|
107
|
+
rec = getattr(pr, "recurring", None) or {}
|
|
108
|
+
return PriceOut(
|
|
109
|
+
id=pr.id,
|
|
110
|
+
provider="stripe",
|
|
111
|
+
provider_price_id=pr.id,
|
|
112
|
+
provider_product_id=(
|
|
113
|
+
pr.product if isinstance(pr.product, str) else getattr(pr.product, "id", "")
|
|
114
|
+
),
|
|
115
|
+
currency=str(pr.currency).upper(),
|
|
116
|
+
unit_amount=int(pr.unit_amount),
|
|
117
|
+
interval=rec.get("interval"),
|
|
118
|
+
trial_days=getattr(pr, "trial_period_days", None),
|
|
119
|
+
active=bool(pr.active),
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _sub_to_out(s) -> SubscriptionOut:
|
|
124
|
+
# pick first item’s price/quantity for simple one-item subs
|
|
125
|
+
item = s.items.data[0] if getattr(s.items, "data", []) else None
|
|
126
|
+
price_id = item.price.id if item and getattr(item, "price", None) else ""
|
|
127
|
+
qty = item.quantity if item else 0
|
|
128
|
+
return SubscriptionOut(
|
|
129
|
+
id=s.id,
|
|
130
|
+
provider="stripe",
|
|
131
|
+
provider_subscription_id=s.id,
|
|
132
|
+
provider_price_id=price_id,
|
|
133
|
+
status=s.status,
|
|
134
|
+
quantity=int(qty or 0),
|
|
135
|
+
cancel_at_period_end=bool(s.cancel_at_period_end),
|
|
136
|
+
current_period_end=(
|
|
137
|
+
str(s.current_period_end) if getattr(s, "current_period_end", None) else None
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _refund_to_out(r) -> RefundOut:
|
|
143
|
+
return RefundOut(
|
|
144
|
+
id=r.id,
|
|
145
|
+
provider="stripe",
|
|
146
|
+
provider_refund_id=r.id,
|
|
147
|
+
provider_payment_intent_id=getattr(r, "payment_intent", None),
|
|
148
|
+
amount=int(r.amount),
|
|
149
|
+
currency=str(r.currency).upper(),
|
|
150
|
+
status=r.status,
|
|
151
|
+
reason=getattr(r, "reason", None),
|
|
152
|
+
created_at=str(r.created) if getattr(r, "created", None) else None,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _dispute_to_out(d) -> DisputeOut:
|
|
157
|
+
return DisputeOut(
|
|
158
|
+
id=d.id,
|
|
159
|
+
provider="stripe",
|
|
160
|
+
provider_dispute_id=d.id,
|
|
161
|
+
amount=int(d.amount),
|
|
162
|
+
currency=str(d.currency).upper(),
|
|
163
|
+
reason=getattr(d, "reason", None),
|
|
164
|
+
status=d.status,
|
|
165
|
+
evidence_due_by=(
|
|
166
|
+
str(d.evidence_details.get("due_by")) if getattr(d, "evidence_details", None) else None
|
|
167
|
+
),
|
|
168
|
+
created_at=str(d.created) if getattr(d, "created", None) else None,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def _payout_to_out(p) -> PayoutOut:
|
|
173
|
+
return PayoutOut(
|
|
174
|
+
id=p.id,
|
|
175
|
+
provider="stripe",
|
|
176
|
+
provider_payout_id=p.id,
|
|
177
|
+
amount=int(p.amount),
|
|
178
|
+
currency=str(p.currency).upper(),
|
|
179
|
+
status=p.status,
|
|
180
|
+
arrival_date=str(p.arrival_date) if getattr(p, "arrival_date", None) else None,
|
|
181
|
+
type=getattr(p, "type", None),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
59
185
|
class StripeAdapter(ProviderAdapter):
|
|
60
186
|
name = "stripe"
|
|
61
187
|
|
|
@@ -70,8 +196,8 @@ class StripeAdapter(ProviderAdapter):
|
|
|
70
196
|
st.stripe.webhook_secret.get_secret_value() if st.stripe.webhook_secret else None
|
|
71
197
|
)
|
|
72
198
|
|
|
199
|
+
# -------- Customers --------
|
|
73
200
|
async def ensure_customer(self, data: CustomerUpsertIn) -> CustomerOut:
|
|
74
|
-
# try by email (idempotent enough for demo; production can map via your DB)
|
|
75
201
|
if data.email:
|
|
76
202
|
existing = await _acall(stripe.Customer.list, email=data.email, limit=1)
|
|
77
203
|
c = (
|
|
@@ -108,113 +234,265 @@ class StripeAdapter(ProviderAdapter):
|
|
|
108
234
|
name=c.get("name"),
|
|
109
235
|
)
|
|
110
236
|
|
|
111
|
-
async def
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
237
|
+
async def list_customers(
|
|
238
|
+
self, *, provider: str | None, user_id: str | None, limit: int, cursor: str | None
|
|
239
|
+
) -> tuple[list[CustomerOut], str | None]:
|
|
240
|
+
params = {"limit": int(limit)}
|
|
241
|
+
if cursor:
|
|
242
|
+
params["starting_after"] = cursor
|
|
243
|
+
# Stripe has no direct filter for our custom user_id; many teams store mapping in DB.
|
|
244
|
+
# If 'user_id' was stored in metadata, we could search via /v1/customers?limit=... then filter client-side.
|
|
245
|
+
res = await _acall(stripe.Customer.list, **params)
|
|
246
|
+
items = [
|
|
247
|
+
CustomerOut(
|
|
248
|
+
id=c.id,
|
|
249
|
+
provider="stripe",
|
|
250
|
+
provider_customer_id=c.id,
|
|
251
|
+
email=c.get("email"),
|
|
252
|
+
name=c.get("name"),
|
|
253
|
+
)
|
|
254
|
+
for c in res.data
|
|
255
|
+
]
|
|
256
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
257
|
+
return items, next_cursor
|
|
258
|
+
|
|
259
|
+
# -------- Payment Methods --------
|
|
260
|
+
async def attach_payment_method(self, data: PaymentMethodAttachIn) -> PaymentMethodOut:
|
|
261
|
+
pm = await _acall(
|
|
262
|
+
stripe.PaymentMethod.attach,
|
|
263
|
+
data.payment_method_token,
|
|
264
|
+
customer=data.customer_provider_id,
|
|
118
265
|
)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
266
|
+
is_default = False
|
|
267
|
+
if data.make_default:
|
|
268
|
+
cust = await _acall(
|
|
269
|
+
stripe.Customer.modify,
|
|
270
|
+
data.customer_provider_id,
|
|
271
|
+
invoice_settings={"default_payment_method": pm.id},
|
|
272
|
+
)
|
|
273
|
+
is_default = (
|
|
274
|
+
getattr(getattr(cust, "invoice_settings", None), "default_payment_method", None)
|
|
275
|
+
== pm.id
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
cust = await _acall(stripe.Customer.retrieve, data.customer_provider_id)
|
|
279
|
+
is_default = (
|
|
280
|
+
getattr(getattr(cust, "invoice_settings", None), "default_payment_method", None)
|
|
281
|
+
== pm.id
|
|
282
|
+
)
|
|
283
|
+
return _pm_to_out(pm, is_default=is_default)
|
|
284
|
+
|
|
285
|
+
async def list_payment_methods(self, provider_customer_id: str) -> list[PaymentMethodOut]:
|
|
286
|
+
cust = await _acall(stripe.Customer.retrieve, provider_customer_id)
|
|
287
|
+
default_pm = getattr(
|
|
288
|
+
getattr(cust, "invoice_settings", None), "default_payment_method", None
|
|
124
289
|
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
290
|
+
res = await _acall(stripe.PaymentMethod.list, customer=provider_customer_id, type="card")
|
|
291
|
+
return [_pm_to_out(pm, is_default=(pm.id == default_pm)) for pm in res.data]
|
|
292
|
+
|
|
293
|
+
async def detach_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
294
|
+
pm = await _acall(stripe.PaymentMethod.detach, provider_method_id)
|
|
295
|
+
# we no longer know default status reliably—fetch customer if set
|
|
296
|
+
cust_id = getattr(pm, "customer", None)
|
|
297
|
+
default_pm = None
|
|
298
|
+
if cust_id:
|
|
299
|
+
cust = await _acall(stripe.Customer.retrieve, cust_id)
|
|
300
|
+
default_pm = getattr(
|
|
301
|
+
getattr(cust, "invoice_settings", None), "default_payment_method", None
|
|
302
|
+
)
|
|
303
|
+
return _pm_to_out(pm, is_default=(pm.id == default_pm))
|
|
304
|
+
|
|
305
|
+
async def set_default_payment_method(
|
|
306
|
+
self, provider_customer_id: str, provider_method_id: str
|
|
307
|
+
) -> PaymentMethodOut:
|
|
308
|
+
cust = await _acall(
|
|
309
|
+
stripe.Customer.modify,
|
|
310
|
+
provider_customer_id,
|
|
311
|
+
invoice_settings={"default_payment_method": provider_method_id},
|
|
312
|
+
)
|
|
313
|
+
pm = await _acall(stripe.PaymentMethod.retrieve, provider_method_id)
|
|
314
|
+
is_default = (
|
|
315
|
+
getattr(getattr(cust, "invoice_settings", None), "default_payment_method", None)
|
|
316
|
+
== pm.id
|
|
134
317
|
)
|
|
318
|
+
return _pm_to_out(pm, is_default=is_default)
|
|
135
319
|
|
|
136
|
-
async def
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
320
|
+
async def get_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
321
|
+
pm = await _acall(stripe.PaymentMethod.retrieve, provider_method_id)
|
|
322
|
+
cust_id = getattr(pm, "customer", None)
|
|
323
|
+
default_pm = None
|
|
324
|
+
if cust_id:
|
|
325
|
+
cust = await _acall(stripe.Customer.retrieve, cust_id)
|
|
326
|
+
default_pm = getattr(
|
|
327
|
+
getattr(cust, "invoice_settings", None), "default_payment_method", None
|
|
328
|
+
)
|
|
329
|
+
return _pm_to_out(pm, is_default=(pm.id == default_pm))
|
|
330
|
+
|
|
331
|
+
async def update_payment_method(
|
|
332
|
+
self, provider_method_id: str, data: PaymentMethodUpdateIn
|
|
333
|
+
) -> PaymentMethodOut:
|
|
334
|
+
update: dict[str, Any] = {}
|
|
335
|
+
if data.name is not None:
|
|
336
|
+
update["billing_details"] = {"name": data.name}
|
|
337
|
+
if data.exp_month is not None or data.exp_year is not None:
|
|
338
|
+
update["card"] = {}
|
|
339
|
+
if data.exp_month is not None:
|
|
340
|
+
update["card"]["exp_month"] = data.exp_month
|
|
341
|
+
if data.exp_year is not None:
|
|
342
|
+
update["card"]["exp_year"] = data.exp_year
|
|
343
|
+
pm = (
|
|
344
|
+
await _acall(stripe.PaymentMethod.modify, provider_method_id, **update)
|
|
345
|
+
if update
|
|
346
|
+
else await _acall(stripe.PaymentMethod.retrieve, provider_method_id)
|
|
147
347
|
)
|
|
348
|
+
cust_id = getattr(pm, "customer", None)
|
|
349
|
+
default_pm = None
|
|
350
|
+
if cust_id:
|
|
351
|
+
cust = await _acall(stripe.Customer.retrieve, cust_id)
|
|
352
|
+
default_pm = getattr(
|
|
353
|
+
getattr(cust, "invoice_settings", None), "default_payment_method", None
|
|
354
|
+
)
|
|
355
|
+
return _pm_to_out(pm, is_default=(pm.id == default_pm))
|
|
148
356
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
357
|
+
# -------- Products / Prices --------
|
|
358
|
+
async def create_product(self, data: ProductCreateIn) -> ProductOut:
|
|
359
|
+
p = await _acall(stripe.Product.create, name=data.name, active=bool(data.active))
|
|
360
|
+
return _product_to_out(p)
|
|
361
|
+
|
|
362
|
+
async def get_product(self, provider_product_id: str) -> ProductOut:
|
|
363
|
+
p = await _acall(stripe.Product.retrieve, provider_product_id)
|
|
364
|
+
return _product_to_out(p)
|
|
365
|
+
|
|
366
|
+
async def list_products(
|
|
367
|
+
self, *, active: bool | None, limit: int, cursor: str | None
|
|
368
|
+
) -> tuple[list[ProductOut], str | None]:
|
|
369
|
+
params = {"limit": int(limit)}
|
|
370
|
+
if active is not None:
|
|
371
|
+
params["active"] = bool(active)
|
|
372
|
+
if cursor:
|
|
373
|
+
params["starting_after"] = cursor
|
|
374
|
+
res = await _acall(stripe.Product.list, **params)
|
|
375
|
+
items = [_product_to_out(p) for p in res.data]
|
|
376
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
377
|
+
return items, next_cursor
|
|
378
|
+
|
|
379
|
+
async def update_product(self, provider_product_id: str, data: ProductUpdateIn) -> ProductOut:
|
|
380
|
+
update: dict[str, Any] = {}
|
|
381
|
+
if data.name is not None:
|
|
382
|
+
update["name"] = data.name
|
|
383
|
+
if data.active is not None:
|
|
384
|
+
update["active"] = bool(data.active)
|
|
385
|
+
p = (
|
|
386
|
+
await _acall(stripe.Product.modify, provider_product_id, **update)
|
|
387
|
+
if update
|
|
388
|
+
else await _acall(stripe.Product.retrieve, provider_product_id)
|
|
158
389
|
)
|
|
390
|
+
return _product_to_out(p)
|
|
159
391
|
|
|
160
|
-
async def
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
392
|
+
async def create_price(self, data: PriceCreateIn) -> PriceOut:
|
|
393
|
+
kwargs: dict[str, Any] = dict(
|
|
394
|
+
product=data.provider_product_id,
|
|
395
|
+
currency=data.currency.lower(),
|
|
396
|
+
unit_amount=int(data.unit_amount),
|
|
397
|
+
active=bool(data.active),
|
|
164
398
|
)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
399
|
+
if data.interval:
|
|
400
|
+
kwargs["recurring"] = {"interval": data.interval}
|
|
401
|
+
if data.trial_days is not None:
|
|
402
|
+
kwargs["trial_period_days"] = int(data.trial_days)
|
|
403
|
+
pr = await _acall(stripe.Price.create, **kwargs)
|
|
404
|
+
return _price_to_out(pr)
|
|
405
|
+
|
|
406
|
+
async def get_price(self, provider_price_id: str) -> PriceOut:
|
|
407
|
+
pr = await _acall(stripe.Price.retrieve, provider_price_id)
|
|
408
|
+
return _price_to_out(pr)
|
|
409
|
+
|
|
410
|
+
async def list_prices(
|
|
411
|
+
self,
|
|
412
|
+
*,
|
|
413
|
+
provider_product_id: str | None,
|
|
414
|
+
active: bool | None,
|
|
415
|
+
limit: int,
|
|
416
|
+
cursor: str | None,
|
|
417
|
+
) -> tuple[list[PriceOut], str | None]:
|
|
418
|
+
params = {"limit": int(limit)}
|
|
419
|
+
if provider_product_id:
|
|
420
|
+
params["product"] = provider_product_id
|
|
421
|
+
if active is not None:
|
|
422
|
+
params["active"] = bool(active)
|
|
423
|
+
if cursor:
|
|
424
|
+
params["starting_after"] = cursor
|
|
425
|
+
res = await _acall(stripe.Price.list, **params)
|
|
426
|
+
items = [_price_to_out(p) for p in res.data]
|
|
427
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
428
|
+
return items, next_cursor
|
|
429
|
+
|
|
430
|
+
async def update_price(self, provider_price_id: str, data: PriceUpdateIn) -> PriceOut:
|
|
431
|
+
# Stripe allows toggling `active` and updating metadata, but not amount/currency/product.
|
|
432
|
+
update: dict[str, Any] = {}
|
|
433
|
+
if data.active is not None:
|
|
434
|
+
update["active"] = bool(data.active)
|
|
435
|
+
pr = (
|
|
436
|
+
await _acall(stripe.Price.modify, provider_price_id, **update)
|
|
437
|
+
if update
|
|
438
|
+
else await _acall(stripe.Price.retrieve, provider_price_id)
|
|
172
439
|
)
|
|
173
|
-
|
|
174
|
-
return await self.hydrate_intent(provider_intent_id)
|
|
440
|
+
return _price_to_out(pr)
|
|
175
441
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
stripe.Webhook.construct_event,
|
|
183
|
-
payload=payload,
|
|
184
|
-
sig_header=signature,
|
|
185
|
-
secret=self._wh_secret,
|
|
442
|
+
# -------- Subscriptions --------
|
|
443
|
+
async def create_subscription(self, data: SubscriptionCreateIn) -> SubscriptionOut:
|
|
444
|
+
kwargs: dict[str, Any] = dict(
|
|
445
|
+
customer=data.customer_provider_id,
|
|
446
|
+
items=[{"price": data.price_provider_id, "quantity": int(data.quantity)}],
|
|
447
|
+
proration_behavior=data.proration_behavior,
|
|
186
448
|
)
|
|
187
|
-
|
|
449
|
+
if data.trial_days is not None:
|
|
450
|
+
kwargs["trial_period_days"] = int(data.trial_days)
|
|
451
|
+
s = await _acall(stripe.Subscription.create, **kwargs)
|
|
452
|
+
return _sub_to_out(s)
|
|
188
453
|
|
|
189
|
-
async def
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
454
|
+
async def update_subscription(
|
|
455
|
+
self, provider_subscription_id: str, data: SubscriptionUpdateIn
|
|
456
|
+
) -> SubscriptionOut:
|
|
457
|
+
s = await _acall(stripe.Subscription.retrieve, provider_subscription_id, expand=["items"])
|
|
458
|
+
items = s.items.data
|
|
459
|
+
update_kwargs: dict[str, Any] = {"proration_behavior": data.proration_behavior}
|
|
460
|
+
# update first item (simple plan model)
|
|
461
|
+
if items:
|
|
462
|
+
first_item = items[0]
|
|
463
|
+
item_update = {"id": first_item.id}
|
|
464
|
+
if data.price_provider_id:
|
|
465
|
+
item_update["price"] = data.price_provider_id
|
|
466
|
+
if data.quantity is not None:
|
|
467
|
+
item_update["quantity"] = int(data.quantity)
|
|
468
|
+
update_kwargs["items"] = [item_update]
|
|
469
|
+
if data.cancel_at_period_end is not None:
|
|
470
|
+
update_kwargs["cancel_at_period_end"] = bool(data.cancel_at_period_end)
|
|
471
|
+
s2 = await _acall(stripe.Subscription.modify, provider_subscription_id, **update_kwargs)
|
|
472
|
+
return _sub_to_out(s2)
|
|
473
|
+
|
|
474
|
+
async def cancel_subscription(
|
|
475
|
+
self, provider_subscription_id: str, at_period_end: bool = True
|
|
476
|
+
) -> SubscriptionOut:
|
|
477
|
+
s = await _acall(
|
|
478
|
+
stripe.Subscription.cancel if not at_period_end else stripe.Subscription.modify,
|
|
479
|
+
provider_subscription_id,
|
|
480
|
+
**({} if not at_period_end else {"cancel_at_period_end": True}),
|
|
200
481
|
)
|
|
482
|
+
return _sub_to_out(s)
|
|
201
483
|
|
|
202
|
-
async def
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if amount is not None:
|
|
206
|
-
kwargs["amount_to_capture"] = int(amount)
|
|
207
|
-
pi = await _acall(stripe.PaymentIntent.capture, provider_intent_id, **kwargs)
|
|
208
|
-
return _pi_to_out(pi)
|
|
484
|
+
async def get_subscription(self, provider_subscription_id: str) -> SubscriptionOut:
|
|
485
|
+
s = await _acall(stripe.Subscription.retrieve, provider_subscription_id, expand=["items"])
|
|
486
|
+
return _sub_to_out(s)
|
|
209
487
|
|
|
210
|
-
async def
|
|
488
|
+
async def list_subscriptions(
|
|
211
489
|
self,
|
|
212
490
|
*,
|
|
213
491
|
customer_provider_id: str | None,
|
|
214
492
|
status: str | None,
|
|
215
493
|
limit: int,
|
|
216
494
|
cursor: str | None,
|
|
217
|
-
) -> tuple[list[
|
|
495
|
+
) -> tuple[list[SubscriptionOut], str | None]:
|
|
218
496
|
params = {"limit": int(limit)}
|
|
219
497
|
if customer_provider_id:
|
|
220
498
|
params["customer"] = customer_provider_id
|
|
@@ -222,16 +500,40 @@ class StripeAdapter(ProviderAdapter):
|
|
|
222
500
|
params["status"] = status
|
|
223
501
|
if cursor:
|
|
224
502
|
params["starting_after"] = cursor
|
|
225
|
-
res = await _acall(stripe.
|
|
226
|
-
items = [
|
|
503
|
+
res = await _acall(stripe.Subscription.list, **params)
|
|
504
|
+
items = [_sub_to_out(s) for s in res.data]
|
|
227
505
|
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
228
506
|
return items, next_cursor
|
|
229
507
|
|
|
230
|
-
#
|
|
231
|
-
async def
|
|
232
|
-
|
|
508
|
+
# -------- Invoices --------
|
|
509
|
+
async def create_invoice(self, data: InvoiceCreateIn) -> InvoiceOut:
|
|
510
|
+
inv = await _acall(
|
|
511
|
+
stripe.Invoice.create,
|
|
233
512
|
customer=data.customer_provider_id,
|
|
234
|
-
|
|
513
|
+
auto_advance=bool(data.auto_advance),
|
|
514
|
+
)
|
|
515
|
+
return _inv_to_out(inv)
|
|
516
|
+
|
|
517
|
+
async def finalize_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
518
|
+
inv = await _acall(stripe.Invoice.finalize_invoice, provider_invoice_id)
|
|
519
|
+
return _inv_to_out(inv)
|
|
520
|
+
|
|
521
|
+
async def void_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
522
|
+
inv = await _acall(stripe.Invoice.void_invoice, provider_invoice_id)
|
|
523
|
+
return _inv_to_out(inv)
|
|
524
|
+
|
|
525
|
+
async def pay_invoice(self, provider_invoice_id: str) -> InvoiceOut:
|
|
526
|
+
inv = await _acall(stripe.Invoice.pay, provider_invoice_id)
|
|
527
|
+
return _inv_to_out(inv)
|
|
528
|
+
|
|
529
|
+
async def add_invoice_line_item(
|
|
530
|
+
self, provider_invoice_id: str, data: InvoiceLineItemIn
|
|
531
|
+
) -> InvoiceOut:
|
|
532
|
+
# attach an item to a DRAFT invoice
|
|
533
|
+
kwargs: dict[str, Any] = dict(
|
|
534
|
+
invoice=provider_invoice_id,
|
|
535
|
+
customer=data.customer_provider_id,
|
|
536
|
+
quantity=int(data.quantity or 1),
|
|
235
537
|
currency=data.currency.lower(),
|
|
236
538
|
description=data.description or None,
|
|
237
539
|
)
|
|
@@ -239,10 +541,11 @@ class StripeAdapter(ProviderAdapter):
|
|
|
239
541
|
kwargs["price"] = data.provider_price_id
|
|
240
542
|
else:
|
|
241
543
|
kwargs["unit_amount"] = int(data.unit_amount)
|
|
242
|
-
|
|
544
|
+
await _acall(
|
|
243
545
|
stripe.InvoiceItem.create, **{k: v for k, v in kwargs.items() if v is not None}
|
|
244
546
|
)
|
|
245
|
-
|
|
547
|
+
inv = await _acall(stripe.Invoice.retrieve, provider_invoice_id)
|
|
548
|
+
return _inv_to_out(inv)
|
|
246
549
|
|
|
247
550
|
async def list_invoices(
|
|
248
551
|
self,
|
|
@@ -277,21 +580,229 @@ class StripeAdapter(ProviderAdapter):
|
|
|
277
580
|
inv = await _acall(stripe.Invoice.upcoming, **params)
|
|
278
581
|
return _inv_to_out(inv)
|
|
279
582
|
|
|
280
|
-
|
|
281
|
-
|
|
583
|
+
async def list_invoice_line_items(
|
|
584
|
+
self, provider_invoice_id: str, *, limit: int, cursor: str | None
|
|
585
|
+
) -> tuple[list[InvoiceLineItemOut], str | None]:
|
|
586
|
+
params = {"limit": int(limit)}
|
|
587
|
+
if cursor:
|
|
588
|
+
params["starting_after"] = cursor
|
|
589
|
+
res = await _acall(stripe.Invoice.list_lines, provider_invoice_id, **params)
|
|
590
|
+
items: list[InvoiceLineItemOut] = []
|
|
591
|
+
for li in res.data:
|
|
592
|
+
amount = int(getattr(li, "amount", 0))
|
|
593
|
+
currency = str(getattr(li, "currency", "USD")).upper()
|
|
594
|
+
price_id = getattr(getattr(li, "price", None), "id", None)
|
|
595
|
+
items.append(
|
|
596
|
+
InvoiceLineItemOut(
|
|
597
|
+
id=li.id,
|
|
598
|
+
description=getattr(li, "description", None),
|
|
599
|
+
amount=amount,
|
|
600
|
+
currency=currency,
|
|
601
|
+
quantity=getattr(li, "quantity", 1),
|
|
602
|
+
provider_price_id=price_id,
|
|
603
|
+
)
|
|
604
|
+
)
|
|
605
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
606
|
+
return items, next_cursor
|
|
607
|
+
|
|
608
|
+
# -------- Intents --------
|
|
609
|
+
async def create_intent(self, data: IntentCreateIn, *, user_id: str | None) -> IntentOut:
|
|
610
|
+
kwargs: dict[str, Any] = dict(
|
|
611
|
+
amount=int(data.amount),
|
|
612
|
+
currency=data.currency.lower(),
|
|
613
|
+
description=data.description or None,
|
|
614
|
+
capture_method="manual" if data.capture_method == "manual" else "automatic",
|
|
615
|
+
automatic_payment_methods={"enabled": True} if not data.payment_method_types else None,
|
|
616
|
+
)
|
|
617
|
+
if data.payment_method_types:
|
|
618
|
+
kwargs["payment_method_types"] = data.payment_method_types
|
|
619
|
+
pi = await _acall(
|
|
620
|
+
stripe.PaymentIntent.create, **{k: v for k, v in kwargs.items() if v is not None}
|
|
621
|
+
)
|
|
622
|
+
return _pi_to_out(pi)
|
|
623
|
+
|
|
624
|
+
async def confirm_intent(self, provider_intent_id: str) -> IntentOut:
|
|
625
|
+
pi = await _acall(stripe.PaymentIntent.confirm, provider_intent_id)
|
|
626
|
+
return _pi_to_out(pi)
|
|
627
|
+
|
|
628
|
+
async def cancel_intent(self, provider_intent_id: str) -> IntentOut:
|
|
629
|
+
pi = await _acall(stripe.PaymentIntent.cancel, provider_intent_id)
|
|
630
|
+
return _pi_to_out(pi)
|
|
631
|
+
|
|
632
|
+
async def refund(self, provider_intent_id: str, data: RefundIn) -> IntentOut:
|
|
633
|
+
pi = await _acall(
|
|
634
|
+
stripe.PaymentIntent.retrieve, provider_intent_id, expand=["latest_charge"]
|
|
635
|
+
)
|
|
636
|
+
charge_id = pi.latest_charge.id if getattr(pi, "latest_charge", None) else None
|
|
637
|
+
if not charge_id:
|
|
638
|
+
raise ValueError("No charge available to refund")
|
|
639
|
+
await _acall(
|
|
640
|
+
stripe.Refund.create,
|
|
641
|
+
charge=charge_id,
|
|
642
|
+
amount=int(data.amount) if data.amount else None,
|
|
643
|
+
reason=data.reason or None,
|
|
644
|
+
)
|
|
645
|
+
return await self.hydrate_intent(provider_intent_id)
|
|
646
|
+
|
|
647
|
+
async def hydrate_intent(self, provider_intent_id: str) -> IntentOut:
|
|
648
|
+
pi = await _acall(stripe.PaymentIntent.retrieve, provider_intent_id)
|
|
649
|
+
return _pi_to_out(pi)
|
|
650
|
+
|
|
651
|
+
async def capture_intent(self, provider_intent_id: str, *, amount: int | None) -> IntentOut:
|
|
652
|
+
kwargs = {}
|
|
653
|
+
if amount is not None:
|
|
654
|
+
kwargs["amount_to_capture"] = int(amount)
|
|
655
|
+
pi = await _acall(stripe.PaymentIntent.capture, provider_intent_id, **kwargs)
|
|
656
|
+
return _pi_to_out(pi)
|
|
657
|
+
|
|
658
|
+
async def list_intents(
|
|
659
|
+
self,
|
|
660
|
+
*,
|
|
661
|
+
customer_provider_id: str | None,
|
|
662
|
+
status: str | None,
|
|
663
|
+
limit: int,
|
|
664
|
+
cursor: str | None,
|
|
665
|
+
) -> tuple[list[IntentOut], str | None]:
|
|
666
|
+
params = {"limit": int(limit)}
|
|
667
|
+
if customer_provider_id:
|
|
668
|
+
params["customer"] = customer_provider_id
|
|
669
|
+
if status:
|
|
670
|
+
params["status"] = status
|
|
671
|
+
if cursor:
|
|
672
|
+
params["starting_after"] = cursor
|
|
673
|
+
res = await _acall(stripe.PaymentIntent.list, **params)
|
|
674
|
+
items = [_pi_to_out(pi) for pi in res.data]
|
|
675
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
676
|
+
return items, next_cursor
|
|
677
|
+
|
|
678
|
+
# ---- Setup Intents (off-session readiness) ----
|
|
679
|
+
async def create_setup_intent(self, data: SetupIntentCreateIn) -> SetupIntentOut:
|
|
680
|
+
si = await _acall(
|
|
681
|
+
stripe.SetupIntent.create, payment_method_types=data.payment_method_types or ["card"]
|
|
682
|
+
)
|
|
683
|
+
return SetupIntentOut(
|
|
684
|
+
id=si.id,
|
|
685
|
+
provider="stripe",
|
|
686
|
+
provider_setup_intent_id=si.id,
|
|
687
|
+
status=si.status,
|
|
688
|
+
client_secret=getattr(si, "client_secret", None),
|
|
689
|
+
next_action=NextAction(type=getattr(getattr(si, "next_action", None), "type", None)),
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
async def confirm_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
693
|
+
si = await _acall(stripe.SetupIntent.confirm, provider_setup_intent_id)
|
|
694
|
+
return SetupIntentOut(
|
|
695
|
+
id=si.id,
|
|
696
|
+
provider="stripe",
|
|
697
|
+
provider_setup_intent_id=si.id,
|
|
698
|
+
status=si.status,
|
|
699
|
+
client_secret=getattr(si, "client_secret", None),
|
|
700
|
+
next_action=NextAction(type=getattr(getattr(si, "next_action", None), "type", None)),
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
async def get_setup_intent(self, provider_setup_intent_id: str) -> SetupIntentOut:
|
|
704
|
+
si = await _acall(stripe.SetupIntent.retrieve, provider_setup_intent_id)
|
|
705
|
+
return SetupIntentOut(
|
|
706
|
+
id=si.id,
|
|
707
|
+
provider="stripe",
|
|
708
|
+
provider_setup_intent_id=si.id,
|
|
709
|
+
status=si.status,
|
|
710
|
+
client_secret=getattr(si, "client_secret", None),
|
|
711
|
+
next_action=NextAction(type=getattr(getattr(si, "next_action", None), "type", None)),
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
# ---- 3DS/SCA resume ----
|
|
715
|
+
async def resume_intent_after_action(self, provider_intent_id: str) -> IntentOut:
|
|
716
|
+
# For Stripe, retrieving after the customer completes next_action is sufficient
|
|
717
|
+
return await self.hydrate_intent(provider_intent_id)
|
|
718
|
+
|
|
719
|
+
# -------- Disputes --------
|
|
720
|
+
async def list_disputes(
|
|
721
|
+
self, *, status: str | None, limit: int, cursor: str | None
|
|
722
|
+
) -> tuple[list[DisputeOut], str | None]:
|
|
723
|
+
params = {"limit": int(limit)}
|
|
724
|
+
if status:
|
|
725
|
+
params["status"] = status
|
|
726
|
+
if cursor:
|
|
727
|
+
params["starting_after"] = cursor
|
|
728
|
+
res = await _acall(stripe.Dispute.list, **params)
|
|
729
|
+
items = [_dispute_to_out(d) for d in res.data]
|
|
730
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
731
|
+
return items, next_cursor
|
|
732
|
+
|
|
733
|
+
async def get_dispute(self, provider_dispute_id: str) -> DisputeOut:
|
|
734
|
+
d = await _acall(stripe.Dispute.retrieve, provider_dispute_id)
|
|
735
|
+
return _dispute_to_out(d)
|
|
736
|
+
|
|
737
|
+
async def submit_dispute_evidence(self, provider_dispute_id: str, evidence: dict) -> DisputeOut:
|
|
738
|
+
d = await _acall(stripe.Dispute.modify, provider_dispute_id, evidence=evidence)
|
|
739
|
+
# Some disputes require explicit submit call:
|
|
740
|
+
try:
|
|
741
|
+
d = await _acall(stripe.Dispute.submit, provider_dispute_id)
|
|
742
|
+
except Exception:
|
|
743
|
+
pass
|
|
744
|
+
return _dispute_to_out(d)
|
|
745
|
+
|
|
746
|
+
# -------- Balance & Payouts --------
|
|
747
|
+
async def get_balance_snapshot(self) -> BalanceSnapshotOut:
|
|
748
|
+
bal = await _acall(stripe.Balance.retrieve)
|
|
749
|
+
|
|
750
|
+
def _bucket(entries):
|
|
751
|
+
out = []
|
|
752
|
+
for b in entries or []:
|
|
753
|
+
out.append({"currency": str(b.currency).upper(), "amount": int(b.amount)})
|
|
754
|
+
return out
|
|
755
|
+
|
|
756
|
+
return BalanceSnapshotOut(
|
|
757
|
+
available=_bucket(getattr(bal, "available", [])),
|
|
758
|
+
pending=_bucket(getattr(bal, "pending", [])),
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
async def list_payouts(
|
|
762
|
+
self, *, limit: int, cursor: str | None
|
|
763
|
+
) -> tuple[list[PayoutOut], str | None]:
|
|
764
|
+
params = {"limit": int(limit)}
|
|
765
|
+
if cursor:
|
|
766
|
+
params["starting_after"] = cursor
|
|
767
|
+
res = await _acall(stripe.Payout.list, **params)
|
|
768
|
+
items = [_payout_to_out(p) for p in res.data]
|
|
769
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
770
|
+
return items, next_cursor
|
|
771
|
+
|
|
772
|
+
async def get_payout(self, provider_payout_id: str) -> PayoutOut:
|
|
773
|
+
p = await _acall(stripe.Payout.retrieve, provider_payout_id)
|
|
774
|
+
return _payout_to_out(p)
|
|
775
|
+
|
|
776
|
+
# -------- Refunds (list/get) --------
|
|
777
|
+
async def list_refunds(
|
|
778
|
+
self, *, provider_payment_intent_id: str | None, limit: int, cursor: str | None
|
|
779
|
+
) -> tuple[list[RefundOut], str | None]:
|
|
780
|
+
params = {"limit": int(limit)}
|
|
781
|
+
if provider_payment_intent_id:
|
|
782
|
+
params["payment_intent"] = provider_payment_intent_id
|
|
783
|
+
if cursor:
|
|
784
|
+
params["starting_after"] = cursor
|
|
785
|
+
res = await _acall(stripe.Refund.list, **params)
|
|
786
|
+
items = [_refund_to_out(r) for r in res.data]
|
|
787
|
+
next_cursor = res.data[-1].id if getattr(res, "has_more", False) and res.data else None
|
|
788
|
+
return items, next_cursor
|
|
789
|
+
|
|
790
|
+
async def get_refund(self, provider_refund_id: str) -> RefundOut:
|
|
791
|
+
r = await _acall(stripe.Refund.retrieve, provider_refund_id)
|
|
792
|
+
return _refund_to_out(r)
|
|
793
|
+
|
|
794
|
+
# -------- Usage (create/list/get) --------
|
|
795
|
+
async def create_usage_record(self, data: UsageRecordIn) -> UsageRecordOut:
|
|
282
796
|
if not data.subscription_item and not data.provider_price_id:
|
|
283
797
|
raise ValueError("subscription_item or provider_price_id is required")
|
|
284
|
-
# If a price is given, you’d normally look up the active subscription_item for that price.
|
|
285
798
|
sub_item = data.subscription_item
|
|
286
799
|
if not sub_item and data.provider_price_id:
|
|
287
|
-
# best-effort: find an active subscription item for the price
|
|
288
800
|
items = await _acall(
|
|
289
801
|
stripe.SubscriptionItem.list, price=data.provider_price_id, limit=1
|
|
290
802
|
)
|
|
291
803
|
sub_item = items.data[0].id if items.data else None
|
|
292
804
|
if not sub_item:
|
|
293
805
|
raise ValueError("No subscription item found for usage record")
|
|
294
|
-
|
|
295
806
|
body = {
|
|
296
807
|
"subscription_item": sub_item,
|
|
297
808
|
"quantity": int(data.quantity),
|
|
@@ -300,4 +811,63 @@ class StripeAdapter(ProviderAdapter):
|
|
|
300
811
|
if data.timestamp:
|
|
301
812
|
body["timestamp"] = int(data.timestamp)
|
|
302
813
|
rec = await _acall(stripe.UsageRecord.create, **body)
|
|
303
|
-
return
|
|
814
|
+
return UsageRecordOut(
|
|
815
|
+
id=rec.id,
|
|
816
|
+
quantity=int(rec.quantity),
|
|
817
|
+
timestamp=getattr(rec, "timestamp", None),
|
|
818
|
+
subscription_item=sub_item,
|
|
819
|
+
provider_price_id=data.provider_price_id,
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
async def list_usage_records(
|
|
823
|
+
self, f: UsageRecordListFilter
|
|
824
|
+
) -> tuple[list[UsageRecordOut], str | None]:
|
|
825
|
+
# Stripe exposes *summaries* per period. We surface them as list results.
|
|
826
|
+
sub_item = f.subscription_item
|
|
827
|
+
if not sub_item and f.provider_price_id:
|
|
828
|
+
items = await _acall(stripe.SubscriptionItem.list, price=f.provider_price_id, limit=1)
|
|
829
|
+
sub_item = items.data[0].id if items.data else None
|
|
830
|
+
if not sub_item:
|
|
831
|
+
return [], None
|
|
832
|
+
params = {"limit": int(f.limit or 50)}
|
|
833
|
+
if f.cursor:
|
|
834
|
+
params["starting_after"] = f.cursor
|
|
835
|
+
res = await _acall(stripe.SubscriptionItem.list_usage_record_summaries, sub_item, **params)
|
|
836
|
+
items: list[UsageRecordOut] = []
|
|
837
|
+
for s in res.data:
|
|
838
|
+
# No record id in summaries—synthesize a stable id from period start.
|
|
839
|
+
synthesized_id = f"{sub_item}:{getattr(s, 'period', {}).get('start')}"
|
|
840
|
+
items.append(
|
|
841
|
+
UsageRecordOut(
|
|
842
|
+
id=synthesized_id,
|
|
843
|
+
quantity=int(getattr(s, "total_usage", 0)),
|
|
844
|
+
timestamp=getattr(s, "period", {}).get("end"),
|
|
845
|
+
subscription_item=sub_item,
|
|
846
|
+
provider_price_id=f.provider_price_id,
|
|
847
|
+
)
|
|
848
|
+
)
|
|
849
|
+
next_cursor = (
|
|
850
|
+
res.data[-1].id
|
|
851
|
+
if getattr(res, "has_more", False) and res.data and hasattr(res.data[-1], "id")
|
|
852
|
+
else None
|
|
853
|
+
)
|
|
854
|
+
return items, next_cursor
|
|
855
|
+
|
|
856
|
+
async def get_usage_record(self, usage_record_id: str) -> UsageRecordOut:
|
|
857
|
+
# Stripe has no direct "retrieve usage record by id" API.
|
|
858
|
+
# You can reconstruct via list summaries or store records locally when creating.
|
|
859
|
+
raise NotImplementedError("Stripe does not support retrieving a single usage record by id")
|
|
860
|
+
|
|
861
|
+
# -------- Webhooks --------
|
|
862
|
+
async def verify_and_parse_webhook(
|
|
863
|
+
self, signature: str | None, payload: bytes
|
|
864
|
+
) -> dict[str, Any]:
|
|
865
|
+
if not self._wh_secret:
|
|
866
|
+
raise ValueError("Stripe webhook secret not configured")
|
|
867
|
+
event = await _acall(
|
|
868
|
+
stripe.Webhook.construct_event,
|
|
869
|
+
payload=payload,
|
|
870
|
+
sig_header=signature,
|
|
871
|
+
secret=self._wh_secret,
|
|
872
|
+
)
|
|
873
|
+
return {"id": event.id, "type": event.type, "data": event.data.object}
|
|
@@ -433,7 +433,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
433
433
|
return await svc.get_intent(provider_intent_id)
|
|
434
434
|
|
|
435
435
|
# STATEMENTS (rollup)
|
|
436
|
-
@
|
|
436
|
+
@prot.get(
|
|
437
437
|
"/statements/daily",
|
|
438
438
|
response_model=list[StatementRow],
|
|
439
439
|
name="payments_daily_statements",
|
|
@@ -627,7 +627,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
627
627
|
return out
|
|
628
628
|
|
|
629
629
|
# ===== Disputes =====
|
|
630
|
-
@
|
|
630
|
+
@prot.get(
|
|
631
631
|
"/disputes",
|
|
632
632
|
name="payments_list_disputes",
|
|
633
633
|
response_model=Paginated[DisputeOut],
|
|
@@ -644,7 +644,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
644
644
|
)
|
|
645
645
|
return ctx.wrap(items, next_cursor=next_cursor)
|
|
646
646
|
|
|
647
|
-
@
|
|
647
|
+
@prot.get(
|
|
648
648
|
"/disputes/{provider_dispute_id}",
|
|
649
649
|
name="payments_get_dispute",
|
|
650
650
|
response_model=DisputeOut,
|
|
@@ -653,7 +653,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
653
653
|
async def get_dispute(provider_dispute_id: str, svc: PaymentsService = Depends(get_service)):
|
|
654
654
|
return await svc.get_dispute(provider_dispute_id)
|
|
655
655
|
|
|
656
|
-
@
|
|
656
|
+
@prot.post(
|
|
657
657
|
"/disputes/{provider_dispute_id}/submit_evidence",
|
|
658
658
|
name="payments_submit_dispute_evidence",
|
|
659
659
|
dependencies=[Depends(require_idempotency_key)],
|
|
@@ -670,13 +670,13 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
670
670
|
return out
|
|
671
671
|
|
|
672
672
|
# ===== Balance & Payouts =====
|
|
673
|
-
@
|
|
673
|
+
@prot.get(
|
|
674
674
|
"/balance", name="payments_get_balance", response_model=BalanceSnapshotOut, tags=["Balance"]
|
|
675
675
|
)
|
|
676
676
|
async def get_balance(svc: PaymentsService = Depends(get_service)):
|
|
677
677
|
return await svc.get_balance_snapshot()
|
|
678
678
|
|
|
679
|
-
@
|
|
679
|
+
@prot.get(
|
|
680
680
|
"/payouts",
|
|
681
681
|
name="payments_list_payouts",
|
|
682
682
|
response_model=Paginated[PayoutOut],
|
|
@@ -785,7 +785,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
785
785
|
):
|
|
786
786
|
return await svc.get_product(provider_product_id)
|
|
787
787
|
|
|
788
|
-
@
|
|
788
|
+
@prot.get(
|
|
789
789
|
"/products",
|
|
790
790
|
response_model=Paginated[ProductOut],
|
|
791
791
|
name="payments_list_products",
|
|
@@ -819,7 +819,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
819
819
|
return out
|
|
820
820
|
|
|
821
821
|
# ===== Prices: get/list/update (active toggle) =====
|
|
822
|
-
@
|
|
822
|
+
@prot.get(
|
|
823
823
|
"/prices/{provider_price_id}",
|
|
824
824
|
response_model=PriceOut,
|
|
825
825
|
name="payments_get_price",
|
|
@@ -830,7 +830,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
830
830
|
):
|
|
831
831
|
return await svc.get_price(provider_price_id)
|
|
832
832
|
|
|
833
|
-
@
|
|
833
|
+
@prot.get(
|
|
834
834
|
"/prices",
|
|
835
835
|
response_model=Paginated[PriceOut],
|
|
836
836
|
name="payments_list_prices",
|
|
@@ -5,14 +5,14 @@ svc_infra/apf_payments/models.py,sha256=u4U5oszha5uulCIrNoajaFDIc5YmTlh2mtm-yJUv
|
|
|
5
5
|
svc_infra/apf_payments/provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
svc_infra/apf_payments/provider/base.py,sha256=1t5znglpGFhjt4zdbuzE5VlHvGarFwzH2oscK8yyKNY,7678
|
|
7
7
|
svc_infra/apf_payments/provider/registry.py,sha256=NZ4pUkFcbXNtqWEpFeI3NwoKRYGWe9fVQakmlrVLTKE,878
|
|
8
|
-
svc_infra/apf_payments/provider/stripe.py,sha256=
|
|
8
|
+
svc_infra/apf_payments/provider/stripe.py,sha256=Xb_UjdobbBzK-an9cO1jRWiP6OHvki5MDp6JnS6a1-I,34392
|
|
9
9
|
svc_infra/apf_payments/schemas.py,sha256=XfBxx6z_Y6cdHktanafNDhhgWl8JVvag9vp2ORmvn_4,8403
|
|
10
10
|
svc_infra/apf_payments/service.py,sha256=bn3BTOTdfkJ4b0Z9cHuFHvlMcv9B1b2n0v-unveUplA,31060
|
|
11
11
|
svc_infra/apf_payments/settings.py,sha256=VnNQbajbv843buUisqa82xOQ-f5JA8JzHD8o01-yhPQ,1239
|
|
12
12
|
svc_infra/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
svc_infra/api/fastapi/__init__.py,sha256=VVdQjak74_wTDqmvL05_C97vIFugQxPVU-3JQEFBgR8,747
|
|
14
14
|
svc_infra/api/fastapi/apf_payments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
svc_infra/api/fastapi/apf_payments/router.py,sha256=
|
|
15
|
+
svc_infra/api/fastapi/apf_payments/router.py,sha256=cee_MQ7VBlcsc9oMN1L9s-IjdNENvYgjLWwPIeoGPEM,34889
|
|
16
16
|
svc_infra/api/fastapi/apf_payments/setup.py,sha256=PX-LHDiyu2eDuaw2m98VPUkF6EmXXRkbjRqh_gL8Kls,2263
|
|
17
17
|
svc_infra/api/fastapi/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
svc_infra/api/fastapi/auth/_cookies.py,sha256=U4heUmMnLezHx8U6ksuUEpSZ6sNMJcIO0gdLpmZ5FXw,1367
|
|
@@ -228,7 +228,7 @@ svc_infra/obs/templates/sidecars/railway/__init__.py,sha256=47DEQpj8HBSa-_TImW-5
|
|
|
228
228
|
svc_infra/obs/templates/sidecars/railway/agent.yaml,sha256=hYv35yG92XEP_4joMFmMcVTD-4fG_zHitmChjreUJh4,516
|
|
229
229
|
svc_infra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
230
230
|
svc_infra/utils.py,sha256=VX1yjTx61-YvAymyRhGy18DhybiVdPddiYD_FlKTbJU,952
|
|
231
|
-
svc_infra-0.1.
|
|
232
|
-
svc_infra-0.1.
|
|
233
|
-
svc_infra-0.1.
|
|
234
|
-
svc_infra-0.1.
|
|
231
|
+
svc_infra-0.1.587.dist-info/METADATA,sha256=hirPy7fbFdNdvqQWb1ycjYOl7sqPuJHgZPcQO4LDGf8,3487
|
|
232
|
+
svc_infra-0.1.587.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
233
|
+
svc_infra-0.1.587.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
234
|
+
svc_infra-0.1.587.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|