feat(base): payment-rails composables + components shared across modules
Activities is the first module to mix Lightning + fiat rails; restaurant
and marketplace will follow. Extract the cross-cutting bits to the base
module so the next adoption is a wiring exercise:
- useFiatProviders: reactive `User.fiat_providers` (today the same list
for organizer + buyer because LNbits configures providers globally),
plus `providerMeta()` for label/icon hints.
- usePriceConversion: `convert()` + reactive `useLivePreview()` over
the existing `/api/v1/conversion` endpoint, 60s cache, null on
transient failure.
- PaymentMethodSelector: buyer-side rail picker. `PaymentMethod.id`
enumerates rails (`lightning | fiat | cash | internal | …`) with
`provider` for the fiat case so a multi-provider instance shows one
button per provider instead of a bare "Fiat" catch-all.
- FiatToggleField: organizer-side switch + conditional fiat-currency
dropdown. Auto-disables with a setup-instructions tooltip when the
user has no providers; silently mirrors fiat_currency to a non-sat
price denomination to keep the backend payload consistent.
- PriceConversionPreview: muted "≈ X.XX USD" line for surfaces where
the price denomination differs from the chosen rail's currency.
LnbitsAPI.getConversion wraps the conversion endpoint so the composable
goes through the existing API service rather than raw fetch. CLAUDE.md
gains a "Payment rails pattern" section documenting the canonical
vocabulary ("Price currency" / "Fiat currency" / "Payment method" /
"Also accept fiat" — bare "Currency" and "Pay in fiat" are banned in
payment-context UI labels) and the fiat-providers-are-global note.
The pre-existing `prvkey` comment on User picks up an inline allowlist
marker so the secret scanner stops flagging this file on every commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ec0dbf727b
commit
caec8eddcc
7 changed files with 496 additions and 1 deletions
84
CLAUDE.md
84
CLAUDE.md
|
|
@ -714,6 +714,90 @@ VITE_ADMIN_PUBKEYS='["pubkey1","pubkey2"]'
|
|||
VITE_WEBSOCKET_ENABLED=true
|
||||
```
|
||||
|
||||
## Payment Rails Pattern
|
||||
|
||||
Shared primitives for modules that mix Lightning + fiat (and, future,
|
||||
cash / internal-wallet) payment rails. Activities is the first
|
||||
consumer; restaurant + marketplace will adopt the same primitives as
|
||||
their backends gain fiat support.
|
||||
|
||||
### Vocabulary (canonical — used in code AND UI labels)
|
||||
|
||||
| Term | Meaning | Field |
|
||||
|---|---|---|
|
||||
| **Price currency** | unit the price is quoted in | `currency` |
|
||||
| **Payment method** | the rail used to pay (Lightning / Fiat / Cash / Internal) | `payment_method` |
|
||||
| **Fiat currency** | currency the fiat provider settles in | `fiat_currency` |
|
||||
| **Also accept fiat** | the user-facing label for the `allow_fiat` toggle | `allow_fiat` |
|
||||
|
||||
The bare word `Currency` is **banned** in payment-context UI labels —
|
||||
it always carries a `Price` or `Fiat` qualifier. The literal string
|
||||
`Pay in fiat` is also banned on buyer-side buttons — each fiat rail
|
||||
shows as a button labeled with its provider name (`Stripe`, `PayPal`,
|
||||
`Square`, `SEPA`). Only the degenerate "providers unknown" fallback
|
||||
shows a generic `Card`.
|
||||
|
||||
### Fiat-provider architecture (LNbits today)
|
||||
|
||||
Fiat providers are configured **globally** by the LNbits admin
|
||||
(`lnbits/settings.py`). Each provider has an optional `allowed_users`
|
||||
whitelist; the per-session filtered list is exposed as
|
||||
`User.fiat_providers: string[]` on `GET /api/v1/auth` (which the
|
||||
webapp already reads as `currentUser.fiat_providers`). Both organizer
|
||||
and buyer on the same instance see the same list.
|
||||
|
||||
Per-user provider configuration is a deferred backend feature. Until
|
||||
then, `useFiatProviders` reads the same `currentUser.fiat_providers`
|
||||
for both sides.
|
||||
|
||||
### Shared primitives (live in base module)
|
||||
|
||||
```
|
||||
src/modules/base/
|
||||
├── composables/
|
||||
│ ├── useFiatProviders.ts // providers + hasAnyProvider + refresh + providerMeta()
|
||||
│ └── usePriceConversion.ts // convert() + useLivePreview(); 60s cache; null on failure
|
||||
└── components/payments/
|
||||
├── PaymentMethodSelector.vue // buyer-side rail picker
|
||||
├── FiatToggleField.vue // organizer-side toggle + conditional fiat-currency dropdown
|
||||
└── PriceConversionPreview.vue // muted "≈ X.XX USD" line
|
||||
```
|
||||
|
||||
All three components consume services via DI — never import them
|
||||
directly across module boundaries.
|
||||
|
||||
### `PaymentMethodSelector` data shape
|
||||
|
||||
```ts
|
||||
type PaymentRail = 'lightning' | 'fiat' | 'cash' | 'internal' | (string & {})
|
||||
|
||||
type PaymentMethod = {
|
||||
id: string // unique v-for key, e.g. 'fiat:stripe'
|
||||
rail: PaymentRail // sent as payment_method
|
||||
provider?: string // sent as fiat_provider when present
|
||||
label: string // 'Lightning' | 'Stripe' | 'SEPA' | 'Cash' | …
|
||||
icon: Component // lucide icon
|
||||
available: boolean // false ⇒ rendered disabled with tooltip
|
||||
unavailableReason?: string // tooltip when disabled
|
||||
badge?: string // optional secondary hint, e.g. '≈ 1000 sats'
|
||||
}
|
||||
```
|
||||
|
||||
Module usage:
|
||||
- **Activities** passes `[lightning, ...one entry per organizer provider]`.
|
||||
- **Restaurant** (future) passes the subset of
|
||||
`[lightning, cash, internal, ...fiat providers]` enabled by the
|
||||
restaurant's `accepts_*` flags.
|
||||
|
||||
### Adding a new fiat provider
|
||||
|
||||
1. Backend exposes the provider id in `User.fiat_providers`.
|
||||
2. Add the id to `KNOWN_PROVIDERS` in `useFiatProviders.ts` with its
|
||||
display label and icon hint (`'card' | 'bank' | 'wallet'`).
|
||||
3. Unknown ids fall back to a `Capitalized` label with a `'card'`
|
||||
icon hint — no code change required just for the buttons to
|
||||
render, only for nice branding.
|
||||
|
||||
## Mobile Browser File Input & Form Refresh Issues
|
||||
|
||||
### **Problem Overview**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue