svc-infra 0.1.579__py3-none-any.whl → 0.1.581__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 +91 -3
- svc_infra/apf_payments/schemas.py +58 -0
- svc_infra/apf_payments/service.py +180 -0
- svc_infra/api/fastapi/apf_payments/router.py +318 -0
- {svc_infra-0.1.579.dist-info → svc_infra-0.1.581.dist-info}/METADATA +1 -1
- {svc_infra-0.1.579.dist-info → svc_infra-0.1.581.dist-info}/RECORD +8 -8
- {svc_infra-0.1.579.dist-info → svc_infra-0.1.581.dist-info}/WHEEL +0 -0
- {svc_infra-0.1.579.dist-info → svc_infra-0.1.581.dist-info}/entry_points.txt +0 -0
|
@@ -11,21 +11,27 @@ 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,
|
|
29
35
|
UsageRecordOut,
|
|
30
36
|
)
|
|
31
37
|
|
|
@@ -36,9 +42,6 @@ class ProviderAdapter(Protocol):
|
|
|
36
42
|
async def ensure_customer(self, data: CustomerUpsertIn) -> CustomerOut:
|
|
37
43
|
pass
|
|
38
44
|
|
|
39
|
-
async def get_customer(self, provider_customer_id: str) -> Optional[CustomerOut]:
|
|
40
|
-
pass
|
|
41
|
-
|
|
42
45
|
async def attach_payment_method(self, data: PaymentMethodAttachIn) -> PaymentMethodOut:
|
|
43
46
|
pass
|
|
44
47
|
|
|
@@ -180,3 +183,88 @@ class ProviderAdapter(Protocol):
|
|
|
180
183
|
|
|
181
184
|
async def get_payout(self, provider_payout_id: str) -> PayoutOut:
|
|
182
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
|
|
@@ -268,3 +268,61 @@ class UsageRecordOut(BaseModel):
|
|
|
268
268
|
timestamp: Optional[int] = None
|
|
269
269
|
subscription_item: Optional[str] = None
|
|
270
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,7 @@ from .schemas import (
|
|
|
48
54
|
SubscriptionOut,
|
|
49
55
|
SubscriptionUpdateIn,
|
|
50
56
|
UsageRecordIn,
|
|
57
|
+
UsageRecordListFilter,
|
|
51
58
|
UsageRecordOut,
|
|
52
59
|
)
|
|
53
60
|
from .settings import get_payments_settings
|
|
@@ -624,3 +631,176 @@ class PaymentsService:
|
|
|
624
631
|
await self._dispatch_event(ev.provider, ev.payload_json)
|
|
625
632
|
|
|
626
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,7 @@ from svc_infra.apf_payments.schemas import (
|
|
|
34
40
|
SubscriptionUpdateIn,
|
|
35
41
|
TransactionRow,
|
|
36
42
|
UsageRecordIn,
|
|
43
|
+
UsageRecordListFilter,
|
|
37
44
|
UsageRecordOut,
|
|
38
45
|
WebhookAckOut,
|
|
39
46
|
WebhookReplayOut,
|
|
@@ -657,6 +664,317 @@ def build_payments_routers(prefix: str = "/payments") -> list[DualAPIRouter]:
|
|
|
657
664
|
await svc.session.flush()
|
|
658
665
|
return {"replayed": count}
|
|
659
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
|
+
summary="Delete Invoice Line Item (draft invoices only)",
|
|
924
|
+
response_model=InvoiceOut,
|
|
925
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
926
|
+
)
|
|
927
|
+
async def delete_invoice_line_item_endpoint(
|
|
928
|
+
provider_invoice_id: str,
|
|
929
|
+
provider_line_item_id: str,
|
|
930
|
+
svc: PaymentsService = Depends(get_service),
|
|
931
|
+
):
|
|
932
|
+
"""
|
|
933
|
+
Removes a line item from a DRAFT invoice only. For finalized invoices,
|
|
934
|
+
use `void` or `credit` flows instead.
|
|
935
|
+
"""
|
|
936
|
+
out = await svc.delete_invoice_line_item(provider_invoice_id, provider_line_item_id)
|
|
937
|
+
await svc.session.flush()
|
|
938
|
+
return out
|
|
939
|
+
|
|
940
|
+
# --- Canonical: remove local alias/association (non-destructive) ---
|
|
941
|
+
@prot.delete(
|
|
942
|
+
"/method_aliases/{alias_id}",
|
|
943
|
+
name="payments_delete_method_alias",
|
|
944
|
+
summary="Remove Method Alias (non-destructive)",
|
|
945
|
+
response_model=PaymentMethodOut,
|
|
946
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
947
|
+
)
|
|
948
|
+
async def delete_method_alias(alias_id: str, svc: PaymentsService = Depends(get_service)):
|
|
949
|
+
"""
|
|
950
|
+
Removes the local alias/association to a payment method.
|
|
951
|
+
This does **not** delete the underlying payment method at the provider.
|
|
952
|
+
Equivalent to `detach_payment_method`.
|
|
953
|
+
"""
|
|
954
|
+
out = await svc.detach_payment_method(alias_id)
|
|
955
|
+
await svc.session.flush()
|
|
956
|
+
return out
|
|
957
|
+
|
|
958
|
+
# --- Back-compat shim (deprecated): keep old path but mark deprecated ---
|
|
959
|
+
@prot.delete(
|
|
960
|
+
"/methods/{provider_method_id}",
|
|
961
|
+
name="payments_delete_method_alias_deprecated",
|
|
962
|
+
summary="(Deprecated) Remove Method Alias — use /method_aliases/{alias_id}",
|
|
963
|
+
deprecated=True,
|
|
964
|
+
response_model=PaymentMethodOut,
|
|
965
|
+
dependencies=[Depends(require_idempotency_key)],
|
|
966
|
+
)
|
|
967
|
+
async def delete_method_alias_deprecated(
|
|
968
|
+
provider_method_id: str, svc: PaymentsService = Depends(get_service)
|
|
969
|
+
):
|
|
970
|
+
"""
|
|
971
|
+
DEPRECATED: use DELETE /method_aliases/{alias_id} instead.
|
|
972
|
+
Non-destructive; only removes local alias/association.
|
|
973
|
+
"""
|
|
974
|
+
out = await svc.detach_payment_method(provider_method_id)
|
|
975
|
+
await svc.session.flush()
|
|
976
|
+
return out
|
|
977
|
+
|
|
660
978
|
routers.append(svc)
|
|
661
979
|
routers.append(pub)
|
|
662
980
|
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=GRBdpJpTG05uJhHMj2lDhf15lcn0nas1KFLnLlDVKaI,33722
|
|
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.581.dist-info/METADATA,sha256=R0-tDGtxo9Lfoso3tlUUrYGN0u4sEfEYheTfjYchuF8,3487
|
|
232
|
+
svc_infra-0.1.581.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
233
|
+
svc_infra-0.1.581.dist-info/entry_points.txt,sha256=6x_nZOsjvn6hRZsMgZLgTasaCSKCgAjsGhACe_CiP0U,48
|
|
234
|
+
svc_infra-0.1.581.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|