From 4ce36598cef1ca9638c8614d9744eadd19668810 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 30 Sep 2026 19:34:48 +0200 Subject: [PATCH] fix(events): ticket waves inherit the event's fiat setting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported from aio-demo: an event with card enabled, Card offered at checkout, and the purchase refused with "Fiat payments are not enabled for this ticket wave." Two causes, both introduced with wave support in #176. **New waves never carried the flag.** `newWaveRow` set id, title, dates, currency, capacity and price — not `allow_fiat`. Fiat is a per-wave opt-in, so every wave the webapp created silently refused card payments however the event-level toggle was set. The editor now seeds new waves from the event, the way the LNbits admin dialog seeds one from the primary wave, and exposes a per-wave switch so an existing wave can be corrected — the LNbits admin has had that control all along, which is why the event looked fiat-enabled there while its waves were not. **The rail list ignored the wave.** `effectivePaymentMethods` returned the organiser's explicit `payment_methods` before consulting anything else, so the card button appeared regardless. That list is event-level while fiat is per-wave, and the webapp always sets it, so the explicit path is the normal one rather than the exception. The wave is now a separate argument rather than being folded into `allowFiat`: that one is the event's legacy flag and means something different, and conflating them broke two existing tests — correctly, which is how the design fault showed up. Matches the backend's `effective_payment_methods(event, wave)` (aiolabs/events b55d686). 7 tests, including that an unset wave flag reads as no-fiat (how the backend reads it) and that the event-level question stays unfiltered. 79 pass; vue-tsc, prettier and the production build clean. Needs the matching events release to be deployed for the published NIP-52 tags to agree; the webapp half stands alone. --- .../events/components/CreateEventDialog.vue | 8 +- .../components/PurchaseTicketDialog.vue | 16 ++-- .../events/components/TicketWavesEditor.vue | 27 +++++- src/modules/events/lib/paymentMethods.spec.ts | 94 +++++++++++++++---- src/modules/events/lib/paymentMethods.ts | 31 +++++- src/modules/events/lib/ticketWaves.spec.ts | 18 ++++ src/modules/events/lib/ticketWaves.ts | 8 ++ 7 files changed, 171 insertions(+), 31 deletions(-) diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/events/components/CreateEventDialog.vue index ad8d9c5..7c35a48 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/events/components/CreateEventDialog.vue @@ -342,8 +342,12 @@ const waveErrors = computed(() => validateWaveRows(extraWaves.value, new Set(knownWaveIds.value)), ); const hasWaveErrors = computed(() => Object.keys(waveErrors.value).length > 0); -/** New waves inherit the primary wave's currency. */ +/** New waves inherit the primary wave's currency and fiat settings. */ const primaryCurrency = computed(() => form.values.currency ?? "sat"); +const primaryAllowsFiat = computed( + () => form.values.payment_methods?.includes("fiat") ?? false, +); +const primaryFiatCurrency = computed(() => form.values.fiat_currency ?? "USD"); async function populateFromEvent(event: TicketedEvent) { isPopulating.value = true; @@ -1215,6 +1219,8 @@ const handleOpenChange = (open: boolean) => { v-model="extraWaves" :known-ids="knownWaveIds" :currency="primaryCurrency" + :allow-fiat="primaryAllowsFiat" + :fiat-currency="primaryFiatCurrency" :disabled="isLoading" /> diff --git a/src/modules/events/components/PurchaseTicketDialog.vue b/src/modules/events/components/PurchaseTicketDialog.vue index a1737fb..3f2dd66 100644 --- a/src/modules/events/components/PurchaseTicketDialog.vue +++ b/src/modules/events/components/PurchaseTicketDialog.vue @@ -298,12 +298,16 @@ const isFiatPending = ref(false); const fiatError = ref(null); const enabledRails = computed(() => - effectivePaymentMethods({ - // Fiat is a per-wave opt-in; `event.allow_fiat` is only the primary - // wave's and can disagree with the tier being bought. - allowFiat: pricing.value.allow_fiat, - paymentMethods: props.event.payment_methods, - }), + effectivePaymentMethods( + { + allowFiat: props.event.allow_fiat, + paymentMethods: props.event.payment_methods, + }, + // Fiat is a per-wave opt-in, and the organiser's explicit rail list is + // event-level — without the wave here the dialog offers a card button + // the purchase endpoint then refuses. + pricing.value, + ), ); const canChooseFiat = computed(() => enabledRails.value.includes("fiat")); const isPriceInSats = computed( diff --git a/src/modules/events/components/TicketWavesEditor.vue b/src/modules/events/components/TicketWavesEditor.vue index 0812886..189bc55 100644 --- a/src/modules/events/components/TicketWavesEditor.vue +++ b/src/modules/events/components/TicketWavesEditor.vue @@ -1,6 +1,7 @@