svc-infra 0.1.578__py3-none-any.whl → 0.1.580__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/base.py +93 -4
- svc_infra/apf_payments/schemas.py +70 -0
- svc_infra/apf_payments/service.py +182 -1
- svc_infra/api/fastapi/apf_payments/router.py +298 -1
- {svc_infra-0.1.578.dist-info → svc_infra-0.1.580.dist-info}/METADATA +1 -1
- {svc_infra-0.1.578.dist-info → svc_infra-0.1.580.dist-info}/RECORD +8 -8
- {svc_infra-0.1.578.dist-info → svc_infra-0.1.580.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.578.dist-info → svc_infra-0.1.580.dist-info}/entry_points.txt +0 -0
|
@@ -11,21 +11,28 @@ from ..schemas import (
|
|
|
11
11
|
IntentOut,
|
|
12
12
|
InvoiceCreateIn,
|
|
13
13
|
InvoiceLineItemIn,
|
|
14
|
+
InvoiceLineItemOut,
|
|
14
15
|
InvoiceOut,
|
|
15
16
|
PaymentMethodAttachIn,
|
|
16
17
|
PaymentMethodOut,
|
|
18
|
+
PaymentMethodUpdateIn,
|
|
17
19
|
PayoutOut,
|
|
18
20
|
PriceCreateIn,
|
|
19
21
|
PriceOut,
|
|
22
|
+
PriceUpdateIn,
|
|
20
23
|
ProductCreateIn,
|
|
21
24
|
ProductOut,
|
|
25
|
+
ProductUpdateIn,
|
|
22
26
|
RefundIn,
|
|
27
|
+
RefundOut,
|
|
23
28
|
SetupIntentCreateIn,
|
|
24
29
|
SetupIntentOut,
|
|
25
30
|
SubscriptionCreateIn,
|
|
26
31
|
SubscriptionOut,
|
|
27
32
|
SubscriptionUpdateIn,
|
|
28
33
|
UsageRecordIn,
|
|
34
|
+
UsageRecordListFilter,
|
|
35
|
+
UsageRecordOut,
|
|
29
36
|
)
|
|
30
37
|
|
|
31
38
|
|
|
@@ -35,9 +42,6 @@ class ProviderAdapter(Protocol):
|
|
|
35
42
|
async def ensure_customer(self, data: CustomerUpsertIn) -> CustomerOut:
|
|
36
43
|
pass
|
|
37
44
|
|
|
38
|
-
async def get_customer(self, provider_customer_id: str) -> Optional[CustomerOut]:
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
45
|
async def attach_payment_method(self, data: PaymentMethodAttachIn) -> PaymentMethodOut:
|
|
42
46
|
pass
|
|
43
47
|
|
|
@@ -139,7 +143,7 @@ class ProviderAdapter(Protocol):
|
|
|
139
143
|
) -> InvoiceOut:
|
|
140
144
|
pass
|
|
141
145
|
|
|
142
|
-
async def create_usage_record(self, data: UsageRecordIn) ->
|
|
146
|
+
async def create_usage_record(self, data: UsageRecordIn) -> UsageRecordOut:
|
|
143
147
|
pass
|
|
144
148
|
|
|
145
149
|
# --- Setup Intents ---
|
|
@@ -179,3 +183,88 @@ class ProviderAdapter(Protocol):
|
|
|
179
183
|
|
|
180
184
|
async def get_payout(self, provider_payout_id: str) -> PayoutOut:
|
|
181
185
|
pass
|
|
186
|
+
|
|
187
|
+
# --- Customers ---
|
|
188
|
+
async def list_customers(
|
|
189
|
+
self, *, provider: str | None, user_id: str | None, limit: int, cursor: str | None
|
|
190
|
+
) -> tuple[list[CustomerOut], str | None]:
|
|
191
|
+
"""Optional: if not implemented, the service will list from local DB."""
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
async def get_customer(self, provider_customer_id: str) -> Optional[CustomerOut]:
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
# --- Products / Prices ---
|
|
198
|
+
async def get_product(self, provider_product_id: str) -> ProductOut:
|
|
199
|
+
pass
|
|
200
|
+
|
|
201
|
+
async def list_products(
|
|
202
|
+
self, *, active: bool | None, limit: int, cursor: str | None
|
|
203
|
+
) -> tuple[list[ProductOut], str | None]:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
async def update_product(self, provider_product_id: str, data: ProductUpdateIn) -> ProductOut:
|
|
207
|
+
pass
|
|
208
|
+
|
|
209
|
+
async def get_price(self, provider_price_id: str) -> PriceOut:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
async def list_prices(
|
|
213
|
+
self,
|
|
214
|
+
*,
|
|
215
|
+
provider_product_id: str | None,
|
|
216
|
+
active: bool | None,
|
|
217
|
+
limit: int,
|
|
218
|
+
cursor: str | None,
|
|
219
|
+
) -> tuple[list[PriceOut], str | None]:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
async def update_price(self, provider_price_id: str, data: PriceUpdateIn) -> PriceOut:
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
# --- Subscriptions ---
|
|
226
|
+
async def get_subscription(self, provider_subscription_id: str) -> SubscriptionOut:
|
|
227
|
+
pass
|
|
228
|
+
|
|
229
|
+
async def list_subscriptions(
|
|
230
|
+
self,
|
|
231
|
+
*,
|
|
232
|
+
customer_provider_id: str | None,
|
|
233
|
+
status: str | None,
|
|
234
|
+
limit: int,
|
|
235
|
+
cursor: str | None,
|
|
236
|
+
) -> tuple[list[SubscriptionOut], str | None]:
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
# --- Payment Method (single + update) ---
|
|
240
|
+
async def get_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
async def update_payment_method(
|
|
244
|
+
self, provider_method_id: str, data: PaymentMethodUpdateIn
|
|
245
|
+
) -> PaymentMethodOut:
|
|
246
|
+
pass
|
|
247
|
+
|
|
248
|
+
# --- Refunds list/get ---
|
|
249
|
+
async def list_refunds(
|
|
250
|
+
self, *, provider_payment_intent_id: str | None, limit: int, cursor: str | None
|
|
251
|
+
) -> tuple[list[RefundOut], str | None]:
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
async def get_refund(self, provider_refund_id: str) -> RefundOut:
|
|
255
|
+
pass
|
|
256
|
+
|
|
257
|
+
# --- Invoice line items list ---
|
|
258
|
+
async def list_invoice_line_items(
|
|
259
|
+
self, provider_invoice_id: str, *, limit: int, cursor: str | None
|
|
260
|
+
) -> tuple[list[InvoiceLineItemOut], str | None]:
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
# --- Usage records list/get ---
|
|
264
|
+
async def list_usage_records(
|
|
265
|
+
self, f: UsageRecordListFilter
|
|
266
|
+
) -> tuple[list[UsageRecordOut], str | None]:
|
|
267
|
+
pass
|
|
268
|
+
|
|
269
|
+
async def get_usage_record(self, usage_record_id: str) -> UsageRecordOut:
|
|
270
|
+
pass
|
|
@@ -256,3 +256,73 @@ class SetupIntentCreateIn(BaseModel):
|
|
|
256
256
|
|
|
257
257
|
class WebhookReplayOut(BaseModel):
|
|
258
258
|
replayed: int
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class WebhookAckOut(BaseModel):
|
|
262
|
+
ok: bool
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
class UsageRecordOut(BaseModel):
|
|
266
|
+
id: str
|
|
267
|
+
quantity: int
|
|
268
|
+
timestamp: Optional[int] = None
|
|
269
|
+
subscription_item: Optional[str] = None
|
|
270
|
+
provider_price_id: Optional[str] = None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
# -------- Customers list filter ----------
|
|
274
|
+
class CustomersListFilter(BaseModel):
|
|
275
|
+
provider: Optional[str] = None
|
|
276
|
+
user_id: Optional[str] = None
|
|
277
|
+
limit: Optional[int] = Field(default=50, ge=1, le=200)
|
|
278
|
+
cursor: Optional[str] = None # we’ll paginate on provider_customer_id asc
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
# -------- Products / Prices updates ----------
|
|
282
|
+
class ProductUpdateIn(BaseModel):
|
|
283
|
+
name: Optional[str] = None
|
|
284
|
+
active: Optional[bool] = None
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class PriceUpdateIn(BaseModel):
|
|
288
|
+
active: Optional[bool] = None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# -------- Payment Method update ----------
|
|
292
|
+
class PaymentMethodUpdateIn(BaseModel):
|
|
293
|
+
# keep minimal + commonly supported card fields
|
|
294
|
+
name: Optional[str] = None
|
|
295
|
+
exp_month: Optional[int] = None
|
|
296
|
+
exp_year: Optional[int] = None
|
|
297
|
+
# extend here later with address fields (line1, city, etc.)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
# -------- Refunds (list/get) ----------
|
|
301
|
+
class RefundOut(BaseModel):
|
|
302
|
+
id: str
|
|
303
|
+
provider: str
|
|
304
|
+
provider_refund_id: str
|
|
305
|
+
provider_payment_intent_id: Optional[str] = None
|
|
306
|
+
amount: AmountMinor
|
|
307
|
+
currency: Currency
|
|
308
|
+
status: str
|
|
309
|
+
reason: Optional[str] = None
|
|
310
|
+
created_at: Optional[str] = None
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
# -------- Invoice line items (list) ----------
|
|
314
|
+
class InvoiceLineItemOut(BaseModel):
|
|
315
|
+
id: str
|
|
316
|
+
description: Optional[str] = None
|
|
317
|
+
amount: AmountMinor
|
|
318
|
+
currency: Currency
|
|
319
|
+
quantity: Optional[int] = 1
|
|
320
|
+
provider_price_id: Optional[str] = None
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# -------- Usage records list/get ----------
|
|
324
|
+
class UsageRecordListFilter(BaseModel):
|
|
325
|
+
subscription_item: Optional[str] = None
|
|
326
|
+
provider_price_id: Optional[str] = None
|
|
327
|
+
limit: Optional[int] = Field(default=50, ge=1, le=200)
|
|
328
|
+
cursor: Optional[str] = None
|
|
@@ -24,6 +24,7 @@ from .schemas import (
|
|
|
24
24
|
BalanceSnapshotOut,
|
|
25
25
|
CaptureIn,
|
|
26
26
|
CustomerOut,
|
|
27
|
+
CustomersListFilter,
|
|
27
28
|
CustomerUpsertIn,
|
|
28
29
|
DisputeOut,
|
|
29
30
|
IntentCreateIn,
|
|
@@ -31,16 +32,21 @@ from .schemas import (
|
|
|
31
32
|
IntentOut,
|
|
32
33
|
InvoiceCreateIn,
|
|
33
34
|
InvoiceLineItemIn,
|
|
35
|
+
InvoiceLineItemOut,
|
|
34
36
|
InvoiceOut,
|
|
35
37
|
InvoicesListFilter,
|
|
36
38
|
PaymentMethodAttachIn,
|
|
37
39
|
PaymentMethodOut,
|
|
40
|
+
PaymentMethodUpdateIn,
|
|
38
41
|
PayoutOut,
|
|
39
42
|
PriceCreateIn,
|
|
40
43
|
PriceOut,
|
|
44
|
+
PriceUpdateIn,
|
|
41
45
|
ProductCreateIn,
|
|
42
46
|
ProductOut,
|
|
47
|
+
ProductUpdateIn,
|
|
43
48
|
RefundIn,
|
|
49
|
+
RefundOut,
|
|
44
50
|
SetupIntentCreateIn,
|
|
45
51
|
SetupIntentOut,
|
|
46
52
|
StatementRow,
|
|
@@ -48,6 +54,8 @@ from .schemas import (
|
|
|
48
54
|
SubscriptionOut,
|
|
49
55
|
SubscriptionUpdateIn,
|
|
50
56
|
UsageRecordIn,
|
|
57
|
+
UsageRecordListFilter,
|
|
58
|
+
UsageRecordOut,
|
|
51
59
|
)
|
|
52
60
|
from .settings import get_payments_settings
|
|
53
61
|
|
|
@@ -459,7 +467,7 @@ class PaymentsService:
|
|
|
459
467
|
)
|
|
460
468
|
|
|
461
469
|
# ---- Metered usage ----
|
|
462
|
-
async def create_usage_record(self, data: UsageRecordIn) ->
|
|
470
|
+
async def create_usage_record(self, data: UsageRecordIn) -> UsageRecordOut:
|
|
463
471
|
return await self._get_adapter().create_usage_record(data)
|
|
464
472
|
|
|
465
473
|
# --- Setup Intents --------------------------------------------------------
|
|
@@ -623,3 +631,176 @@ class PaymentsService:
|
|
|
623
631
|
await self._dispatch_event(ev.provider, ev.payload_json)
|
|
624
632
|
|
|
625
633
|
return len(rows)
|
|
634
|
+
|
|
635
|
+
# ---- Customers ----
|
|
636
|
+
async def list_customers(self, f: CustomersListFilter) -> tuple[list[CustomerOut], str | None]:
|
|
637
|
+
adapter = self._get_adapter()
|
|
638
|
+
try:
|
|
639
|
+
return await adapter.list_customers(
|
|
640
|
+
provider=f.provider, user_id=f.user_id, limit=f.limit or 50, cursor=f.cursor
|
|
641
|
+
)
|
|
642
|
+
except NotImplementedError:
|
|
643
|
+
# Fallback to local DB listing
|
|
644
|
+
q = select(PayCustomer).order_by(PayCustomer.provider_customer_id.asc())
|
|
645
|
+
if f.provider:
|
|
646
|
+
q = q.where(PayCustomer.provider == f.provider)
|
|
647
|
+
if f.user_id:
|
|
648
|
+
q = q.where(PayCustomer.user_id == f.user_id)
|
|
649
|
+
rows = (await self.session.execute(q)).scalars().all()
|
|
650
|
+
# simple cursor by provider_customer_id; production can optimize
|
|
651
|
+
next_cursor = None
|
|
652
|
+
if f.limit and len(rows) > f.limit:
|
|
653
|
+
rows = rows[: f.limit]
|
|
654
|
+
next_cursor = rows[-1].provider_customer_id
|
|
655
|
+
return (
|
|
656
|
+
[
|
|
657
|
+
CustomerOut(
|
|
658
|
+
id=r.id,
|
|
659
|
+
provider=r.provider,
|
|
660
|
+
provider_customer_id=r.provider_customer_id,
|
|
661
|
+
email=None,
|
|
662
|
+
name=None,
|
|
663
|
+
)
|
|
664
|
+
for r in rows
|
|
665
|
+
],
|
|
666
|
+
next_cursor,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
async def get_customer(self, provider_customer_id: str) -> CustomerOut:
|
|
670
|
+
adapter = self._get_adapter()
|
|
671
|
+
out = await adapter.get_customer(provider_customer_id)
|
|
672
|
+
if out is None:
|
|
673
|
+
raise RuntimeError("Customer not found")
|
|
674
|
+
# upsert locally
|
|
675
|
+
row = await self.session.scalar(
|
|
676
|
+
select(PayCustomer).where(PayCustomer.provider_customer_id == provider_customer_id)
|
|
677
|
+
)
|
|
678
|
+
if not row:
|
|
679
|
+
self.session.add(
|
|
680
|
+
PayCustomer(
|
|
681
|
+
provider=out.provider,
|
|
682
|
+
provider_customer_id=out.provider_customer_id,
|
|
683
|
+
user_id=None,
|
|
684
|
+
tenant_id="",
|
|
685
|
+
)
|
|
686
|
+
)
|
|
687
|
+
return out
|
|
688
|
+
|
|
689
|
+
# ---- Products / Prices ----
|
|
690
|
+
async def get_product(self, provider_product_id: str) -> ProductOut:
|
|
691
|
+
return await self._get_adapter().get_product(provider_product_id)
|
|
692
|
+
|
|
693
|
+
async def list_products(
|
|
694
|
+
self, *, active: bool | None, limit: int, cursor: str | None
|
|
695
|
+
) -> tuple[list[ProductOut], str | None]:
|
|
696
|
+
return await self._get_adapter().list_products(active=active, limit=limit, cursor=cursor)
|
|
697
|
+
|
|
698
|
+
async def update_product(self, provider_product_id: str, data: ProductUpdateIn) -> ProductOut:
|
|
699
|
+
out = await self._get_adapter().update_product(provider_product_id, data)
|
|
700
|
+
# reflect DB
|
|
701
|
+
row = await self.session.scalar(
|
|
702
|
+
select(PayProduct).where(PayProduct.provider_product_id == provider_product_id)
|
|
703
|
+
)
|
|
704
|
+
if row:
|
|
705
|
+
if data.name is not None:
|
|
706
|
+
row.name = data.name
|
|
707
|
+
if data.active is not None:
|
|
708
|
+
row.active = data.active
|
|
709
|
+
return out
|
|
710
|
+
|
|
711
|
+
async def get_price(self, provider_price_id: str) -> PriceOut:
|
|
712
|
+
return await self._get_adapter().get_price(provider_price_id)
|
|
713
|
+
|
|
714
|
+
async def list_prices(
|
|
715
|
+
self,
|
|
716
|
+
*,
|
|
717
|
+
provider_product_id: str | None,
|
|
718
|
+
active: bool | None,
|
|
719
|
+
limit: int,
|
|
720
|
+
cursor: str | None,
|
|
721
|
+
) -> tuple[list[PriceOut], str | None]:
|
|
722
|
+
return await self._get_adapter().list_prices(
|
|
723
|
+
provider_product_id=provider_product_id, active=active, limit=limit, cursor=cursor
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
async def update_price(self, provider_price_id: str, data: PriceUpdateIn) -> PriceOut:
|
|
727
|
+
out = await self._get_adapter().update_price(provider_price_id, data)
|
|
728
|
+
row = await self.session.scalar(
|
|
729
|
+
select(PayPrice).where(PayPrice.provider_price_id == provider_price_id)
|
|
730
|
+
)
|
|
731
|
+
if row and data.active is not None:
|
|
732
|
+
row.active = data.active
|
|
733
|
+
return out
|
|
734
|
+
|
|
735
|
+
# ---- Subscriptions ----
|
|
736
|
+
async def get_subscription(self, provider_subscription_id: str) -> SubscriptionOut:
|
|
737
|
+
return await self._get_adapter().get_subscription(provider_subscription_id)
|
|
738
|
+
|
|
739
|
+
async def list_subscriptions(
|
|
740
|
+
self,
|
|
741
|
+
*,
|
|
742
|
+
customer_provider_id: str | None,
|
|
743
|
+
status: str | None,
|
|
744
|
+
limit: int,
|
|
745
|
+
cursor: str | None,
|
|
746
|
+
) -> tuple[list[SubscriptionOut], str | None]:
|
|
747
|
+
return await self._get_adapter().list_subscriptions(
|
|
748
|
+
customer_provider_id=customer_provider_id, status=status, limit=limit, cursor=cursor
|
|
749
|
+
)
|
|
750
|
+
|
|
751
|
+
# ---- Payment Methods (get/update) ----
|
|
752
|
+
async def get_payment_method(self, provider_method_id: str) -> PaymentMethodOut:
|
|
753
|
+
return await self._get_adapter().get_payment_method(provider_method_id)
|
|
754
|
+
|
|
755
|
+
async def update_payment_method(
|
|
756
|
+
self, provider_method_id: str, data: PaymentMethodUpdateIn
|
|
757
|
+
) -> PaymentMethodOut:
|
|
758
|
+
out = await self._get_adapter().update_payment_method(provider_method_id, data)
|
|
759
|
+
row = await self.session.scalar(
|
|
760
|
+
select(PayPaymentMethod).where(
|
|
761
|
+
PayPaymentMethod.provider_method_id == provider_method_id
|
|
762
|
+
)
|
|
763
|
+
)
|
|
764
|
+
if row:
|
|
765
|
+
if data.name is not None:
|
|
766
|
+
pass # keep local-only if/when you add column
|
|
767
|
+
if data.exp_month is not None:
|
|
768
|
+
row.exp_month = data.exp_month
|
|
769
|
+
if data.exp_year is not None:
|
|
770
|
+
row.exp_year = data.exp_year
|
|
771
|
+
return out
|
|
772
|
+
|
|
773
|
+
# ---- Refunds list/get ----
|
|
774
|
+
async def list_refunds(
|
|
775
|
+
self, *, provider_payment_intent_id: str | None, limit: int, cursor: str | None
|
|
776
|
+
) -> tuple[list[RefundOut], str | None]:
|
|
777
|
+
return await self._get_adapter().list_refunds(
|
|
778
|
+
provider_payment_intent_id=provider_payment_intent_id, limit=limit, cursor=cursor
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
async def get_refund(self, provider_refund_id: str) -> RefundOut:
|
|
782
|
+
return await self._get_adapter().get_refund(provider_refund_id)
|
|
783
|
+
|
|
784
|
+
# ---- Invoice line items list ----
|
|
785
|
+
async def list_invoice_line_items(
|
|
786
|
+
self, provider_invoice_id: str, *, limit: int, cursor: str | None
|
|
787
|
+
) -> tuple[list[InvoiceLineItemOut], str | None]:
|
|
788
|
+
return await self._get_adapter().list_invoice_line_items(
|
|
789
|
+
provider_invoice_id, limit=limit, cursor=cursor
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
# ---- Usage records list/get ----
|
|
793
|
+
async def list_usage_records(
|
|
794
|
+
self, f: UsageRecordListFilter
|
|
795
|
+
) -> tuple[list[UsageRecordOut], str | None]:
|
|
796
|
+
return await self._get_adapter().list_usage_records(f)
|
|
797
|
+
|
|
798
|
+
async def get_usage_record(self, usage_record_id: str) -> UsageRecordOut:
|
|
799
|
+
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
|
+
)
|
|
@@ -9,6 +9,7 @@ from svc_infra.apf_payments.schemas import (
|
|
|
9
9
|
BalanceSnapshotOut,
|
|
10
10
|
CaptureIn,
|
|
11
11
|
CustomerOut,
|
|
12
|
+
CustomersListFilter,
|
|
12
13
|
CustomerUpsertIn,
|
|
13
14
|
DisputeOut,
|
|
14
15
|
IntentCreateIn,
|
|
@@ -16,16 +17,21 @@ from svc_infra.apf_payments.schemas import (
|
|
|
16
17
|
IntentOut,
|
|
17
18
|
InvoiceCreateIn,
|
|
18
19
|
InvoiceLineItemIn,
|
|
20
|
+
InvoiceLineItemOut,
|
|
19
21
|
InvoiceOut,
|
|
20
22
|
InvoicesListFilter,
|
|
21
23
|
PaymentMethodAttachIn,
|
|
22
24
|
PaymentMethodOut,
|
|
25
|
+
PaymentMethodUpdateIn,
|
|
23
26
|
PayoutOut,
|
|
24
27
|
PriceCreateIn,
|
|
25
28
|
PriceOut,
|
|
29
|
+
PriceUpdateIn,
|
|
26
30
|
ProductCreateIn,
|
|
27
31
|
ProductOut,
|
|
32
|
+
ProductUpdateIn,
|
|
28
33
|
RefundIn,
|
|
34
|
+
RefundOut,
|
|
29
35
|
SetupIntentCreateIn,
|
|
30
36
|
SetupIntentOut,
|
|
31
37
|
StatementRow,
|
|
@@ -34,6 +40,9 @@ from svc_infra.apf_payments.schemas import (
|
|
|
34
40
|
SubscriptionUpdateIn,
|
|
35
41
|
TransactionRow,
|
|
36
42
|
UsageRecordIn,
|
|
43
|
+
UsageRecordListFilter,
|
|
44
|
+
UsageRecordOut,
|
|
45
|
+
WebhookAckOut,
|
|
37
46
|
WebhookReplayOut,
|
|
38
47
|
)
|
|
39
48
|
from svc_infra.apf_payments.service import PaymentsService
|
|
@@ -183,7 +192,11 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
183
192
|
# PUBLIC webhooks
|
|
184
193
|
pub = public_router(prefix=prefix, tags=["payments"])
|
|
185
194
|
|
|
186
|
-
@pub.post(
|
|
195
|
+
@pub.post(
|
|
196
|
+
"/webhooks/{provider}",
|
|
197
|
+
name="payments_webhook",
|
|
198
|
+
response_model=WebhookAckOut,
|
|
199
|
+
)
|
|
187
200
|
async def webhooks(
|
|
188
201
|
provider: str,
|
|
189
202
|
request: Request,
|
|
@@ -507,6 +520,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
507
520
|
name="payments_create_usage_record",
|
|
508
521
|
status_code=status.HTTP_201_CREATED,
|
|
509
522
|
dependencies=[Depends(require_idempotency_key)],
|
|
523
|
+
response_model=UsageRecordOut,
|
|
510
524
|
)
|
|
511
525
|
async def create_usage_record_endpoint(
|
|
512
526
|
data: UsageRecordIn, svc: PaymentsService = Depends(get_service)
|
|
@@ -598,6 +612,7 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
598
612
|
"/disputes/{provider_dispute_id}/submit_evidence",
|
|
599
613
|
name="payments_submit_dispute_evidence",
|
|
600
614
|
dependencies=[Depends(require_idempotency_key)],
|
|
615
|
+
response_model=DisputeOut,
|
|
601
616
|
)
|
|
602
617
|
async def submit_dispute_evidence(
|
|
603
618
|
provider_dispute_id: str,
|
|
@@ -649,6 +664,288 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
649
664
|
await svc.session.flush()
|
|
650
665
|
return {"replayed": count}
|
|
651
666
|
|
|
667
|
+
# ===== Customers: list/get =====
|
|
668
|
+
@prot.get(
|
|
669
|
+
"/customers",
|
|
670
|
+
response_model=Paginated[CustomerOut],
|
|
671
|
+
name="payments_list_customers",
|
|
672
|
+
dependencies=[Depends(cursor_pager(default_limit=50, max_limit=200))],
|
|
673
|
+
)
|
|
674
|
+
async def list_customers_endpoint(
|
|
675
|
+
provider: Optional[str] = None,
|
|
676
|
+
user_id: Optional[str] = None,
|
|
677
|
+
svc: PaymentsService = Depends(get_service),
|
|
678
|
+
):
|
|
679
|
+
ctx = use_pagination()
|
|
680
|
+
items, next_cursor = await svc.list_customers(
|
|
681
|
+
CustomersListFilter(
|
|
682
|
+
provider=provider, user_id=user_id, limit=ctx.limit, cursor=ctx.cursor
|
|
683
|
+
)
|
|
684
|
+
)
|
|
685
|
+
return ctx.wrap(items, next_cursor=next_cursor)
|
|
686
|
+
|
|
687
|
+
@prot.get(
|
|
688
|
+
"/customers/{provider_customer_id}",
|
|
689
|
+
response_model=CustomerOut,
|
|
690
|
+
name="payments_get_customer",
|
|
691
|
+
)
|
|
692
|
+
async def get_customer_endpoint(
|
|
693
|
+
provider_customer_id: str, svc: PaymentsService = Depends(get_service)
|
|
694
|
+
):
|
|
695
|
+
return await svc.get_customer(provider_customer_id)
|
|
696
|
+
|
|
697
|
+
# ===== Payment Methods: get/update =====
|
|
698
|
+
@prot.get(
|
|
699
|
+
"/methods/{provider_method_id}",
|
|
700
|
+
response_model=PaymentMethodOut,
|
|
701
|
+
name="payments_get_method",
|
|
702
|
+
)
|
|
703
|
+
async def get_method(provider_method_id: str, svc: PaymentsService = Depends(get_service)):
|
|
704
|
+
return await svc.get_payment_method(provider_method_id)
|
|
705
|
+
|
|
706
|
+
@prot.post(
|
|
707
|
+
"/methods/{provider_method_id}",
|
|
708
|
+
response_model=PaymentMethodOut,
|
|
709
|
+
name="payments_update_method",
|
|
710
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
711
|
+
)
|
|
712
|
+
async def update_method(
|
|
713
|
+
provider_method_id: str,
|
|
714
|
+
data: PaymentMethodUpdateIn,
|
|
715
|
+
svc: PaymentsService = Depends(get_service),
|
|
716
|
+
):
|
|
717
|
+
out = await svc.update_payment_method(provider_method_id, data)
|
|
718
|
+
await svc.session.flush()
|
|
719
|
+
return out
|
|
720
|
+
|
|
721
|
+
# ===== Products: get/list/update (archive via active=False) =====
|
|
722
|
+
@svc.get(
|
|
723
|
+
"/products/{provider_product_id}",
|
|
724
|
+
response_model=ProductOut,
|
|
725
|
+
name="payments_get_product",
|
|
726
|
+
)
|
|
727
|
+
async def get_product_endpoint(
|
|
728
|
+
provider_product_id: str, svc: PaymentsService = Depends(get_service)
|
|
729
|
+
):
|
|
730
|
+
return await svc.get_product(provider_product_id)
|
|
731
|
+
|
|
732
|
+
@svc.get(
|
|
733
|
+
"/products",
|
|
734
|
+
response_model=Paginated[ProductOut],
|
|
735
|
+
name="payments_list_products",
|
|
736
|
+
dependencies=[Depends(cursor_pager(default_limit=50, max_limit=200))],
|
|
737
|
+
)
|
|
738
|
+
async def list_products_endpoint(
|
|
739
|
+
active: Optional[bool] = None,
|
|
740
|
+
svc: PaymentsService = Depends(get_service),
|
|
741
|
+
):
|
|
742
|
+
ctx = use_pagination()
|
|
743
|
+
items, next_cursor = await svc.list_products(
|
|
744
|
+
active=active, limit=ctx.limit, cursor=ctx.cursor
|
|
745
|
+
)
|
|
746
|
+
return ctx.wrap(items, next_cursor=next_cursor)
|
|
747
|
+
|
|
748
|
+
@svc.post(
|
|
749
|
+
"/products/{provider_product_id}",
|
|
750
|
+
response_model=ProductOut,
|
|
751
|
+
name="payments_update_product",
|
|
752
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
753
|
+
)
|
|
754
|
+
async def update_product_endpoint(
|
|
755
|
+
provider_product_id: str,
|
|
756
|
+
data: ProductUpdateIn,
|
|
757
|
+
svc: PaymentsService = Depends(get_service),
|
|
758
|
+
):
|
|
759
|
+
out = await svc.update_product(provider_product_id, data)
|
|
760
|
+
await svc.session.flush()
|
|
761
|
+
return out
|
|
762
|
+
|
|
763
|
+
# ===== Prices: get/list/update (active toggle) =====
|
|
764
|
+
@svc.get(
|
|
765
|
+
"/prices/{provider_price_id}",
|
|
766
|
+
response_model=PriceOut,
|
|
767
|
+
name="payments_get_price",
|
|
768
|
+
)
|
|
769
|
+
async def get_price_endpoint(
|
|
770
|
+
provider_price_id: str, svc: PaymentsService = Depends(get_service)
|
|
771
|
+
):
|
|
772
|
+
return await svc.get_price(provider_price_id)
|
|
773
|
+
|
|
774
|
+
@svc.get(
|
|
775
|
+
"/prices",
|
|
776
|
+
response_model=Paginated[PriceOut],
|
|
777
|
+
name="payments_list_prices",
|
|
778
|
+
dependencies=[Depends(cursor_pager(default_limit=50, max_limit=200))],
|
|
779
|
+
)
|
|
780
|
+
async def list_prices_endpoint(
|
|
781
|
+
provider_product_id: Optional[str] = None,
|
|
782
|
+
active: Optional[bool] = None,
|
|
783
|
+
svc: PaymentsService = Depends(get_service),
|
|
784
|
+
):
|
|
785
|
+
ctx = use_pagination()
|
|
786
|
+
items, next_cursor = await svc.list_prices(
|
|
787
|
+
provider_product_id=provider_product_id,
|
|
788
|
+
active=active,
|
|
789
|
+
limit=ctx.limit,
|
|
790
|
+
cursor=ctx.cursor,
|
|
791
|
+
)
|
|
792
|
+
return ctx.wrap(items, next_cursor=next_cursor)
|
|
793
|
+
|
|
794
|
+
@svc.post(
|
|
795
|
+
"/prices/{provider_price_id}",
|
|
796
|
+
response_model=PriceOut,
|
|
797
|
+
name="payments_update_price",
|
|
798
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
799
|
+
)
|
|
800
|
+
async def update_price_endpoint(
|
|
801
|
+
provider_price_id: str,
|
|
802
|
+
data: PriceUpdateIn,
|
|
803
|
+
svc: PaymentsService = Depends(get_service),
|
|
804
|
+
):
|
|
805
|
+
out = await svc.update_price(provider_price_id, data)
|
|
806
|
+
await svc.session.flush()
|
|
807
|
+
return out
|
|
808
|
+
|
|
809
|
+
# ===== Subscriptions: get/list =====
|
|
810
|
+
@prot.get(
|
|
811
|
+
"/subscriptions/{provider_subscription_id}",
|
|
812
|
+
response_model=SubscriptionOut,
|
|
813
|
+
name="payments_get_subscription",
|
|
814
|
+
)
|
|
815
|
+
async def get_subscription_endpoint(
|
|
816
|
+
provider_subscription_id: str, svc: PaymentsService = Depends(get_service)
|
|
817
|
+
):
|
|
818
|
+
return await svc.get_subscription(provider_subscription_id)
|
|
819
|
+
|
|
820
|
+
@prot.get(
|
|
821
|
+
"/subscriptions",
|
|
822
|
+
response_model=Paginated[SubscriptionOut],
|
|
823
|
+
name="payments_list_subscriptions",
|
|
824
|
+
dependencies=[Depends(cursor_pager(default_limit=50, max_limit=200))],
|
|
825
|
+
)
|
|
826
|
+
async def list_subscriptions_endpoint(
|
|
827
|
+
customer_provider_id: Optional[str] = None,
|
|
828
|
+
status: Optional[str] = None,
|
|
829
|
+
svc: PaymentsService = Depends(get_service),
|
|
830
|
+
):
|
|
831
|
+
ctx = use_pagination()
|
|
832
|
+
items, next_cursor = await svc.list_subscriptions(
|
|
833
|
+
customer_provider_id=customer_provider_id,
|
|
834
|
+
status=status,
|
|
835
|
+
limit=ctx.limit,
|
|
836
|
+
cursor=ctx.cursor,
|
|
837
|
+
)
|
|
838
|
+
return ctx.wrap(items, next_cursor=next_cursor)
|
|
839
|
+
|
|
840
|
+
# ===== Invoices: list line items =====
|
|
841
|
+
@prot.get(
|
|
842
|
+
"/invoices/{provider_invoice_id}/lines",
|
|
843
|
+
response_model=Paginated[InvoiceLineItemOut],
|
|
844
|
+
name="payments_list_invoice_line_items",
|
|
845
|
+
dependencies=[Depends(cursor_pager(default_limit=50, max_limit=200))],
|
|
846
|
+
)
|
|
847
|
+
async def list_invoice_lines_endpoint(
|
|
848
|
+
provider_invoice_id: str,
|
|
849
|
+
svc: PaymentsService = Depends(get_service),
|
|
850
|
+
):
|
|
851
|
+
ctx = use_pagination()
|
|
852
|
+
items, next_cursor = await svc.list_invoice_line_items(
|
|
853
|
+
provider_invoice_id, limit=ctx.limit, cursor=ctx.cursor
|
|
854
|
+
)
|
|
855
|
+
return ctx.wrap(items, next_cursor=next_cursor)
|
|
856
|
+
|
|
857
|
+
# ===== Refunds: list/get =====
|
|
858
|
+
@prot.get(
|
|
859
|
+
"/refunds",
|
|
860
|
+
response_model=Paginated[RefundOut],
|
|
861
|
+
name="payments_list_refunds",
|
|
862
|
+
dependencies=[Depends(cursor_pager(default_limit=50, max_limit=200))],
|
|
863
|
+
)
|
|
864
|
+
async def list_refunds_endpoint(
|
|
865
|
+
provider_payment_intent_id: Optional[str] = None,
|
|
866
|
+
svc: PaymentsService = Depends(get_service),
|
|
867
|
+
):
|
|
868
|
+
ctx = use_pagination()
|
|
869
|
+
items, next_cursor = await svc.list_refunds(
|
|
870
|
+
provider_payment_intent_id=provider_payment_intent_id,
|
|
871
|
+
limit=ctx.limit,
|
|
872
|
+
cursor=ctx.cursor,
|
|
873
|
+
)
|
|
874
|
+
return ctx.wrap(items, next_cursor=next_cursor)
|
|
875
|
+
|
|
876
|
+
@prot.get(
|
|
877
|
+
"/refunds/{provider_refund_id}",
|
|
878
|
+
response_model=RefundOut,
|
|
879
|
+
name="payments_get_refund",
|
|
880
|
+
)
|
|
881
|
+
async def get_refund_endpoint(
|
|
882
|
+
provider_refund_id: str, svc: PaymentsService = Depends(get_service)
|
|
883
|
+
):
|
|
884
|
+
return await svc.get_refund(provider_refund_id)
|
|
885
|
+
|
|
886
|
+
# ===== Usage Records: list/get =====
|
|
887
|
+
@prot.get(
|
|
888
|
+
"/usage_records",
|
|
889
|
+
response_model=Paginated[UsageRecordOut],
|
|
890
|
+
name="payments_list_usage_records",
|
|
891
|
+
dependencies=[Depends(cursor_pager(default_limit=50, max_limit=200))],
|
|
892
|
+
)
|
|
893
|
+
async def list_usage_records_endpoint(
|
|
894
|
+
subscription_item: Optional[str] = None,
|
|
895
|
+
provider_price_id: Optional[str] = None,
|
|
896
|
+
svc: PaymentsService = Depends(get_service),
|
|
897
|
+
):
|
|
898
|
+
ctx = use_pagination()
|
|
899
|
+
items, next_cursor = await svc.list_usage_records(
|
|
900
|
+
UsageRecordListFilter(
|
|
901
|
+
subscription_item=subscription_item,
|
|
902
|
+
provider_price_id=provider_price_id,
|
|
903
|
+
limit=ctx.limit,
|
|
904
|
+
cursor=ctx.cursor,
|
|
905
|
+
)
|
|
906
|
+
)
|
|
907
|
+
return ctx.wrap(items, next_cursor=next_cursor)
|
|
908
|
+
|
|
909
|
+
@prot.get(
|
|
910
|
+
"/usage_records/{usage_record_id}",
|
|
911
|
+
response_model=UsageRecordOut,
|
|
912
|
+
name="payments_get_usage_record",
|
|
913
|
+
)
|
|
914
|
+
async def get_usage_record_endpoint(
|
|
915
|
+
usage_record_id: str, svc: PaymentsService = Depends(get_service)
|
|
916
|
+
):
|
|
917
|
+
return await svc.get_usage_record(usage_record_id)
|
|
918
|
+
|
|
919
|
+
# --- Invoices: delete line item ---
|
|
920
|
+
@prot.delete(
|
|
921
|
+
"/invoices/{provider_invoice_id}/lines/{provider_line_item_id}",
|
|
922
|
+
name="payments_delete_invoice_line_item",
|
|
923
|
+
response_model=InvoiceOut,
|
|
924
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
925
|
+
)
|
|
926
|
+
async def delete_invoice_line_item_endpoint(
|
|
927
|
+
provider_invoice_id: str,
|
|
928
|
+
provider_line_item_id: str,
|
|
929
|
+
svc: PaymentsService = Depends(get_service),
|
|
930
|
+
):
|
|
931
|
+
out = await svc.delete_invoice_line_item(provider_invoice_id, provider_line_item_id)
|
|
932
|
+
await svc.session.flush()
|
|
933
|
+
return out
|
|
934
|
+
|
|
935
|
+
# --- Optional REST alias for detach (calls your existing logic) ---
|
|
936
|
+
@prot.delete(
|
|
937
|
+
"/methods/{provider_method_id}",
|
|
938
|
+
name="payments_delete_method_alias",
|
|
939
|
+
response_model=PaymentMethodOut,
|
|
940
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
941
|
+
)
|
|
942
|
+
async def delete_method_alias(
|
|
943
|
+
provider_method_id: str, svc: PaymentsService = Depends(get_service)
|
|
944
|
+
):
|
|
945
|
+
out = await svc.detach_payment_method(provider_method_id)
|
|
946
|
+
await svc.session.flush()
|
|
947
|
+
return out
|
|
948
|
+
|
|
652
949
|
routers.append(svc)
|
|
653
950
|
routers.append(pub)
|
|
654
951
|
return routers
|
|
@@ -3,16 +3,16 @@ svc_infra/apf_payments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
|
3
3
|
svc_infra/apf_payments/alembic.py,sha256=XJIcCDOoaO1EeJbSx_qK9o4cBi430qyo5gECtjHIojw,299
|
|
4
4
|
svc_infra/apf_payments/models.py,sha256=u4U5oszha5uulCIrNoajaFDIc5YmTlh2mtm-yJUvr9I,14251
|
|
5
5
|
svc_infra/apf_payments/provider/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
svc_infra/apf_payments/provider/base.py,sha256=
|
|
6
|
+
svc_infra/apf_payments/provider/base.py,sha256=1t5znglpGFhjt4zdbuzE5VlHvGarFwzH2oscK8yyKNY,7678
|
|
7
7
|
svc_infra/apf_payments/provider/registry.py,sha256=NZ4pUkFcbXNtqWEpFeI3NwoKRYGWe9fVQakmlrVLTKE,878
|
|
8
8
|
svc_infra/apf_payments/provider/stripe.py,sha256=rZlQe1BBLBF1aMOqw98aDRZkA7pOgTqkGvXzSG8qE5c,11298
|
|
9
|
-
svc_infra/apf_payments/schemas.py,sha256=
|
|
10
|
-
svc_infra/apf_payments/service.py,sha256=
|
|
9
|
+
svc_infra/apf_payments/schemas.py,sha256=XfBxx6z_Y6cdHktanafNDhhgWl8JVvag9vp2ORmvn_4,8403
|
|
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=zwdAVaUYGT3RjIp-3WtWUMsuBWGPhjt6fAMGt2tZXlg,32456
|
|
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.580.dist-info/METADATA,sha256=IHm9XHfQc8WdbmNpAyFDv0H1fLOIX2LMX0D7KRsmXe0,3487
|
|
232
|
+
svc_infra-0.1.580.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
233
|
+
svc_infra-0.1.580.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
234
|
+
svc_infra-0.1.580.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|