Skip to content

Commit 6616712

Browse files
committed
chore(payments): tier 4 audit cleanup
- #9: rename InvoiceStatus.FAILED -> CANCELLED. Maps Xendit's payment_session.canceled event onto a distinct status (was lumped under EXPIRED), giving the previously-orphaned enum value a real path. Earnings page references and dropdown label updated. No DB migration needed: the status column is a plain string with no CHECK constraint and no rows carry the old "failed" value (no path ever set it). - #12: get_user_balance now rejects non-gateway wallet types (e.g. MPP) with HTTP 400 instead of silently returning 0.0. Mirrors the wallet-type validation already in create_invoice. - #13: add a TODO on list_for_tenant noting that pagination + date-range filters are wanted before any tenant exceeds ~5k invoices. Defer until there's a concrete scale need. - #17: fix EndpointRepository.get_by_id type hint (was int, column is UUID). Pre-existing nit. Skipped: #11 (endpoint hard-delete clears endpoint_id from ledger via SET NULL cascade) — design call, current behavior is defensible since the archived flag exists for soft-delete.
1 parent c079e17 commit 6616712

6 files changed

Lines changed: 26 additions & 9 deletions

File tree

backend/syft_space/components/endpoints/repository.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ async def get_all(self, tenant_id: UUID) -> list[Endpoint]:
3939
result = await session.exec(statement)
4040
return list(result.all())
4141

42-
async def get_by_id(self, id: int, tenant_id: UUID) -> Endpoint | None:
42+
async def get_by_id(self, id: UUID, tenant_id: UUID) -> Endpoint | None:
4343
"""Get an endpoint by ID within a tenant.
4444
4545
Args:

backend/syft_space/components/payments/gateway/entities.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ class InvoiceStatus(str, Enum):
2525

2626
PENDING = "pending"
2727
PAID = "paid"
28-
EXPIRED = "expired"
29-
FAILED = "failed"
28+
EXPIRED = "expired" # provider session timed out
29+
CANCELLED = "cancelled" # user or admin abandoned the session
3030

3131

3232
class EntryType(str, Enum):

backend/syft_space/components/payments/gateway/handlers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,18 @@ async def get_user_balance(
314314
wallet = await self.wallet_repo.get_by_id(wallet_id, tenant.id)
315315
if not wallet:
316316
raise HTTPException(status_code=404, detail="Wallet not found")
317+
# Only gateway wallets (e.g. xendit) carry a UserBalance row. MPP and
318+
# other non-gateway types would silently return 0.0 here, which reads
319+
# as a successful "you have no money" response — confusing. Reject
320+
# them so the caller sees a clear error instead.
321+
if wallet.wallet_type not in self.gateways:
322+
raise HTTPException(
323+
status_code=400,
324+
detail=(
325+
f"Wallet type '{wallet.wallet_type}' does not support "
326+
f"prepaid balance lookup"
327+
),
328+
)
317329
balance = await self.balance_service.get_balance(
318330
wallet_id=wallet.id, tenant_id=tenant.id, user_email=user_email
319331
)

backend/syft_space/components/payments/gateway/invoice_repository.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,13 @@ async def list_for_tenant(
6060
) -> list[Invoice]:
6161
"""Admin view: all invoices for a tenant, newest first.
6262
63-
Optional status filter (pending|paid|expired|failed). No pagination —
63+
Optional status filter (pending|paid|expired|cancelled). No pagination —
6464
invoice volume is low (a few per user per month).
65+
66+
TODO: paginate when any tenant exceeds ~5k invoices. Likely wants
67+
date-range filters too rather than just cursor (admin reporting use
68+
cases want "last 30 days," not "next 50 rows"). Defer until concrete
69+
scale or product requirement.
6570
"""
6671
statement = select(Invoice).where(Invoice.tenant_id == tenant_id)
6772
if status:
@@ -81,7 +86,7 @@ async def list_for_user_in_wallet(
8186
8287
Used by the satellite-token /invoices/me endpoint so callers can check
8388
whether a pending invoice already exists before creating a new one.
84-
Optional status filter (pending|paid|expired|failed).
89+
Optional status filter (pending|paid|expired|cancelled).
8590
"""
8691
statement = select(Invoice).where(
8792
Invoice.wallet_id == wallet_id,

backend/syft_space/components/payments/gateway/xendit/gateway.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class XenditGateway:
4949
_EVENT_STATUS_MAP = {
5050
"payment_session.completed": InvoiceStatus.PAID,
5151
"payment_session.expired": InvoiceStatus.EXPIRED,
52-
"payment_session.canceled": InvoiceStatus.EXPIRED,
52+
"payment_session.canceled": InvoiceStatus.CANCELLED,
5353
}
5454

5555
def resolve_purchase(

frontend/src/pages/EarningsPage.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
<SelectItem value="pending">Pending</SelectItem>
5252
<SelectItem value="paid">Paid</SelectItem>
5353
<SelectItem value="expired">Expired</SelectItem>
54-
<SelectItem value="failed">Failed</SelectItem>
54+
<SelectItem value="cancelled">Cancelled</SelectItem>
5555
</SelectContent>
5656
</Select>
5757
<Input v-model="emailFilter" placeholder="Filter by email..." class="h-9 max-w-sm flex-1" />
@@ -94,7 +94,7 @@
9494
'text-emerald-600 border-emerald-300': inv.status === 'paid',
9595
'text-amber-600 border-amber-300': inv.status === 'pending',
9696
'text-red-600 border-red-300':
97-
inv.status === 'expired' || inv.status === 'failed',
97+
inv.status === 'expired' || inv.status === 'cancelled',
9898
}"
9999
>
100100
{{ inv.status }}
@@ -192,7 +192,7 @@ const pendingCount = computed(
192192
const expiredCount = computed(
193193
() =>
194194
emailScopedInvoices.value.filter(
195-
(i) => i.status === 'expired' || i.status === 'failed',
195+
(i) => i.status === 'expired' || i.status === 'cancelled',
196196
).length,
197197
)
198198

0 commit comments

Comments
 (0)