You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add two read-only, provider-proxied billing endpoints so the SPA can render the prototype's payment-method card and invoice history table with real data — while staying PCI SAQ-A (we never touch PAN/CVC; card entry/update remains hosted Stripe Checkout/Portal redirects).
This is PR2 of the My Account initiative. Depends on PR1 (the billing crate + PaymentProvider port must exist).
Why
The prototype shows a payment-method card (Visa •••• 4242, exp 08/27) and an invoice table. last4, brand, exp_month/year, and invoice records are not PAN/CVC, so reading them back from the provider is outside PCI scope. Today there is no API for them, so the SPA can't render those sections for real. Card entry still goes only through hosted Checkout/Portal — so we add reads, not writes.
Scope
Add to the billing crate, exposed on the USER plane (owner-checked, same auth as the other /v1/tenants/{id}/... routes):
GET /v1/tenants/{id}/billing/payment-method
Returns the default payment method summary or null (e.g. trial with no card):
Backed by a new method on the PaymentProvider port (Stripe adapter: retrieve the customer's default payment method). Returns null when the tenant has no provider customer yet.
status ∈ provider-agnostic set (paid | open | void | uncollectible — map provider values). hosted_url is the provider-hosted invoice/receipt link (the download action). Empty array when no customer/invoices.
Backed by a new PaymentProvider method (Stripe adapter: list invoices for the customer).
Notes
Extend the PaymentProvider port (provider-agnostic DTOs in billing), with the Stripe adapter implementing the two new reads via the existing hand-rolled reqwest client pattern (no new SDK).
Both endpoints are idempotent reads; no DB writes, no webhook involvement.
Keep responses SPA-shaped (provider-agnostic), not raw Stripe objects.
Acceptance criteria
Both endpoints exist on the USER plane, owner-checked; return the shapes above.
payment-method returns null and invoices returns [] for a trial tenant with no provider customer (no 5xx).
PaymentProvider port gains the two reads; Stripe adapter implemented; no PAN/CVC is ever requested, returned, logged, or stored (SAQ-A preserved).
Tests cover both happy-path and no-customer cases, with the provider mocked (wiremock, consistent with the existing Stripe-gateway tests).
utoipa annotations added so the endpoints appear in the generated OpenAPI doc.
References
PaymentProvider port + Stripe adapter (from PR1, formerly crates/tenants/src/stripe.rs).
Prototype billing sections: paymentMethodCard() and billingHistory() in the design HTML.
Summary
Add two read-only, provider-proxied billing endpoints so the SPA can render the prototype's payment-method card and invoice history table with real data — while staying PCI SAQ-A (we never touch PAN/CVC; card entry/update remains hosted Stripe Checkout/Portal redirects).
This is PR2 of the My Account initiative. Depends on PR1 (the
billingcrate +PaymentProviderport must exist).Why
The prototype shows a payment-method card (Visa •••• 4242, exp 08/27) and an invoice table.
last4,brand,exp_month/year, and invoice records are not PAN/CVC, so reading them back from the provider is outside PCI scope. Today there is no API for them, so the SPA can't render those sections for real. Card entry still goes only through hosted Checkout/Portal — so we add reads, not writes.Scope
Add to the
billingcrate, exposed on the USER plane (owner-checked, same auth as the other/v1/tenants/{id}/...routes):GET /v1/tenants/{id}/billing/payment-methodnull(e.g. trial with no card):{ "brand": "visa", "last4": "4242", "exp_month": 8, "exp_year": 2027 }PaymentProviderport (Stripe adapter: retrieve the customer's default payment method). Returnsnullwhen the tenant has no provider customer yet.GET /v1/tenants/{id}/billing/invoices[ { "date": "2026-06-01", "amount": "$8.00", "status": "paid", "hosted_url": "https://..." } ]status∈ provider-agnostic set (paid|open|void|uncollectible— map provider values).hosted_urlis the provider-hosted invoice/receipt link (the download action). Empty array when no customer/invoices.PaymentProvidermethod (Stripe adapter: list invoices for the customer).Notes
PaymentProviderport (provider-agnostic DTOs inbilling), with the Stripe adapter implementing the two new reads via the existing hand-rolledreqwestclient pattern (no new SDK).Acceptance criteria
payment-methodreturnsnullandinvoicesreturns[]for a trial tenant with no provider customer (no 5xx).PaymentProviderport gains the two reads; Stripe adapter implemented; no PAN/CVC is ever requested, returned, logged, or stored (SAQ-A preserved).References
PaymentProviderport + Stripe adapter (from PR1, formerlycrates/tenants/src/stripe.rs).paymentMethodCard()andbillingHistory()in the design HTML.