diff --git a/src/modules/events/components/CreateEventDialog.vue b/src/modules/events/components/CreateEventDialog.vue index b9ad6c1..ad8d9c5 100644 --- a/src/modules/events/components/CreateEventDialog.vue +++ b/src/modules/events/components/CreateEventDialog.vue @@ -342,12 +342,8 @@ 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 and fiat settings. */ +/** New waves inherit the primary wave's currency. */ 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; @@ -598,28 +594,7 @@ const onSubmit = form.handleSubmit( notification_subject: formValues.notification_subject, notification_body: formValues.notification_body, promo_codes: toWirePromoCodes(promoRows.value), - // Fiat is a per-wave opt-in in the extension, but the webapp - // offers ONE event-level control for it (the Card checkbox), so - // that checkbox governs every wave: ticking Card enables fiat on - // all of them, unticking disables it on all of them. Without this a - // wave added here refuses card payments while the event itself - // reads as fiat-enabled — what organisers hit on demo. - // - // Deliberately simple for now: it does mean a per-wave setting made - // in the LNbits admin is overwritten next time the event is saved - // from here. Acceptable while the webapp has no per-wave control to - // represent one, and better than a control that silently disagrees - // with what gets stored. - ticket_waves: [primaryWave, ...extraWaves.value].map((wave) => ({ - ...wave, - allow_fiat: acceptsFiat, - // The event's fiat currency, unconditionally — not a fallback to - // whatever the wave happened to hold. Waves created before this - // carry the backend's "GBP" default, and a wave settling in a - // different currency than the event advertises is not a state the - // webapp can represent or an organiser can have asked for. - fiat_currency: formValues.fiat_currency, - })), + ticket_waves: [primaryWave, ...extraWaves.value], }; if (isEditMode.value) { @@ -1240,8 +1215,6 @@ 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 3f2dd66..a1737fb 100644 --- a/src/modules/events/components/PurchaseTicketDialog.vue +++ b/src/modules/events/components/PurchaseTicketDialog.vue @@ -298,16 +298,12 @@ const isFiatPending = ref(false); const fiatError = ref(null); const enabledRails = computed(() => - 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, - ), + 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, + }), ); 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 b152176..0812886 100644 --- a/src/modules/events/components/TicketWavesEditor.vue +++ b/src/modules/events/components/TicketWavesEditor.vue @@ -27,12 +27,6 @@ const props = defineProps<{ knownIds?: string[]; /** Currency of the primary wave; new rows inherit it. */ currency?: string; - /** Whether the event accepts fiat, and in what. New rows inherit both so - * the in-memory row is right before saving; on save the dialog applies - * the event-level Card checkbox to every wave anyway, which is why - * there is no per-wave switch here — it would be overwritten. */ - allowFiat?: boolean; - fiatCurrency?: string; disabled?: boolean; }>(); const emit = defineEmits<{ "update:modelValue": [waves: TicketWave[]] }>(); @@ -53,11 +47,7 @@ function update(index: number, patch: Partial) { function add() { emit("update:modelValue", [ ...props.modelValue, - newWaveRow({ - currency: props.currency ?? "sat", - allow_fiat: props.allowFiat ?? false, - fiat_currency: props.fiatCurrency ?? "USD", - }), + newWaveRow({ currency: props.currency ?? "sat" }), ]); } diff --git a/src/modules/events/lib/paymentMethods.spec.ts b/src/modules/events/lib/paymentMethods.spec.ts index 1991ef1..72c80f2 100644 --- a/src/modules/events/lib/paymentMethods.spec.ts +++ b/src/modules/events/lib/paymentMethods.spec.ts @@ -1,79 +1,21 @@ -import { describe, it, expect } from "vitest"; -import { effectivePaymentMethods } from "./paymentMethods"; +import { describe, it, expect } from 'vitest' +import { effectivePaymentMethods } from './paymentMethods' -describe("effectivePaymentMethods", () => { - it("falls back to the legacy allowFiat rule when no list is published", () => { - expect(effectivePaymentMethods({ allowFiat: false })).toEqual([ - "lightning", - ]); - expect(effectivePaymentMethods({ allowFiat: true })).toEqual([ - "lightning", - "fiat", - ]); - expect(effectivePaymentMethods(undefined)).toEqual(["lightning"]); - }); +describe('effectivePaymentMethods', () => { + it('falls back to the legacy allowFiat rule when no list is published', () => { + expect(effectivePaymentMethods({ allowFiat: false })).toEqual(['lightning']) + expect(effectivePaymentMethods({ allowFiat: true })).toEqual(['lightning', 'fiat']) + expect(effectivePaymentMethods(undefined)).toEqual(['lightning']) + }) - it("honours an explicit list, including fiat-only events", () => { - expect( - effectivePaymentMethods({ allowFiat: true, paymentMethods: ["fiat"] }), - ).toEqual(["fiat"]); - expect( - effectivePaymentMethods({ - allowFiat: false, - paymentMethods: ["lightning", "fiat"], - }), - ).toEqual(["lightning", "fiat"]); - }); + it('honours an explicit list, including fiat-only events', () => { + expect(effectivePaymentMethods({ allowFiat: true, paymentMethods: ['fiat'] })).toEqual(['fiat']) + expect(effectivePaymentMethods({ allowFiat: false, paymentMethods: ['lightning', 'fiat'] })) + .toEqual(['lightning', 'fiat']) + }) - it("drops unknown rails and falls back when nothing known remains", () => { - expect( - effectivePaymentMethods({ - allowFiat: false, - paymentMethods: ["onchain"], - }), - ).toEqual(["lightning"]); - expect( - effectivePaymentMethods({ - allowFiat: false, - paymentMethods: ["onchain", "fiat"], - }), - ).toEqual(["fiat"]); - }); -}); - -describe("effectivePaymentMethods — per-wave fiat", () => { - const railsOn = { allowFiat: true, paymentMethods: ["lightning", "fiat"] }; - - it("drops card for a wave that cannot take fiat", () => { - // Reported from aio-demo: event fiat on, Card offered, purchase - // refused with "Fiat payments are not enabled for this ticket wave". - expect(effectivePaymentMethods(railsOn, { allow_fiat: false })).toEqual([ - "lightning", - ]); - }); - - it("keeps card for a wave that can", () => { - expect(effectivePaymentMethods(railsOn, { allow_fiat: true })).toEqual([ - "lightning", - "fiat", - ]); - }); - - it("treats an unset wave flag as no fiat — it is how the backend reads it", () => { - expect(effectivePaymentMethods(railsOn, {})).toEqual(["lightning"]); - }); - - it("leaves the event-level question unfiltered", () => { - expect(effectivePaymentMethods(railsOn)).toEqual(["lightning", "fiat"]); - expect(effectivePaymentMethods(railsOn, null)).toEqual([ - "lightning", - "fiat", - ]); - }); - - it("filters the legacy allowFiat path too", () => { - expect( - effectivePaymentMethods({ allowFiat: true }, { allow_fiat: false }), - ).toEqual(["lightning"]); - }); -}); + it('drops unknown rails and falls back when nothing known remains', () => { + expect(effectivePaymentMethods({ allowFiat: false, paymentMethods: ['onchain'] })).toEqual(['lightning']) + expect(effectivePaymentMethods({ allowFiat: false, paymentMethods: ['onchain', 'fiat'] })).toEqual(['fiat']) + }) +}) diff --git a/src/modules/events/lib/paymentMethods.ts b/src/modules/events/lib/paymentMethods.ts index 1b73b5e..70e08e9 100644 --- a/src/modules/events/lib/paymentMethods.ts +++ b/src/modules/events/lib/paymentMethods.ts @@ -1,6 +1,6 @@ -export type PaymentRailId = "lightning" | "fiat"; +export type PaymentRailId = 'lightning' | 'fiat' -const KNOWN_RAILS: PaymentRailId[] = ["lightning", "fiat"]; +const KNOWN_RAILS: PaymentRailId[] = ['lightning', 'fiat'] /** * Rails a buyer may use for an event. @@ -13,31 +13,10 @@ const KNOWN_RAILS: PaymentRailId[] = ["lightning", "fiat"]; */ export function effectivePaymentMethods( info: { allowFiat?: boolean; paymentMethods?: string[] } | null | undefined, - /** The wave being bought. Omit for the event-level question ("which - * rails did the organiser enable at all"). */ - wave?: { allow_fiat?: boolean } | null, ): PaymentRailId[] { const explicit = (info?.paymentMethods ?? []).filter( (m): m is PaymentRailId => (KNOWN_RAILS as string[]).includes(m), - ); - const rails = - explicit.length > 0 - ? explicit - : info?.allowFiat - ? (["lightning", "fiat"] as PaymentRailId[]) - : (["lightning"] as PaymentRailId[]); - - // Fiat is a PER-WAVE opt-in, while the rail list above is event-level. - // Asking about a specific wave means asking what a buyer can actually - // use for it, so drop a rail that wave cannot honour — otherwise the - // dialog renders a card button that `api_ticket_create` refuses with - // "Fiat payments are not enabled for this ticket wave". - // - // A separate argument rather than reusing `info.allowFiat`: that one is - // the EVENT's legacy flag and means something different. The backend - // keeps the same split (`effective_payment_methods(event, wave)`). - if (wave && wave.allow_fiat !== true) { - return rails.filter((m) => m !== "fiat"); - } - return rails; + ) + if (explicit.length > 0) return explicit + return info?.allowFiat ? ['lightning', 'fiat'] : ['lightning'] } diff --git a/src/modules/events/lib/ticketWaves.spec.ts b/src/modules/events/lib/ticketWaves.spec.ts index c827510..ce2bad2 100644 --- a/src/modules/events/lib/ticketWaves.spec.ts +++ b/src/modules/events/lib/ticketWaves.spec.ts @@ -327,66 +327,3 @@ describe("the primary-wave write-through a create/edit form performs", () => { expect(waves[1]).toEqual(REGULAR); }); }); - -describe("newWaveRow — fiat inheritance", () => { - it("defaults to no fiat, matching what the backend stores", () => { - expect(newWaveRow().allow_fiat).toBe(false); - }); - - it("inherits the event settings when the editor passes them", () => { - // Without this a wave created on a fiat-enabled event silently refuses - // card payments, and the organiser has no way to see why. - const row = newWaveRow({ - allow_fiat: true, - fiat_currency: "EUR", - currency: "EUR", - }); - expect(row.allow_fiat).toBe(true); - expect(row.fiat_currency).toBe("EUR"); - }); -}); - -describe("the Card checkbox governs every wave", () => { - // The webapp has one event-level fiat control, so on save it is applied - // to all waves. Pinned because getting it wrong is silent: the event - // reads as fiat-enabled while a wave refuses card payments, and the - // buyer only finds out at checkout. - const applyEventFiat = ( - waves: TicketWave[], - acceptsFiat: boolean, - cur: string, - ) => - waves.map((w) => ({ ...w, allow_fiat: acceptsFiat, fiat_currency: cur })); - - const waves = [ - wave({ id: "primary", allow_fiat: true, fiat_currency: "EUR" }), - wave({ id: "early", allow_fiat: false, fiat_currency: "GBP" }), - wave({ id: "late", allow_fiat: false, fiat_currency: "GBP" }), - ]; - - it("ticking Card enables fiat on every wave, not just the primary", () => { - const out = applyEventFiat(waves, true, "EUR"); - expect(out.map((w) => w.allow_fiat)).toEqual([true, true, true]); - expect(out.map((w) => w.fiat_currency)).toEqual(["EUR", "EUR", "EUR"]); - }); - - it("unticking Card disables it on every wave", () => { - expect( - applyEventFiat(waves, false, "EUR").map((w) => w.allow_fiat), - ).toEqual([false, false, false]); - }); - - it("overwrites a stale wave fiat currency with the event's", () => { - // Waves created before this carry the backend's "GBP" default even on - // a EUR event, which is what demo showed. - const out = applyEventFiat(waves, true, "EUR"); - expect(out.every((w) => w.fiat_currency === "EUR")).toBe(true); - }); - - it("leaves everything else about each wave alone", () => { - const [, early] = applyEventFiat(waves, true, "EUR"); - expect(early.id).toBe("early"); - expect(early.price_per_ticket).toBe(waves[1].price_per_ticket); - expect(early.amount_tickets).toBe(waves[1].amount_tickets); - }); -}); diff --git a/src/modules/events/lib/ticketWaves.ts b/src/modules/events/lib/ticketWaves.ts index 11ad8b4..9c2e710 100644 --- a/src/modules/events/lib/ticketWaves.ts +++ b/src/modules/events/lib/ticketWaves.ts @@ -289,14 +289,6 @@ export function newWaveRow(defaults: Partial = {}): TicketWave { // unlimited (aiolabs/events#34). amount_tickets: 1, price_per_ticket: 0, - // Fiat is a PER-WAVE opt-in. A wave created without it cannot take - // card payments however the event-level toggle is set, and the - // organiser gets "Fiat payments are not enabled for this ticket wave" - // at checkout with no way to see why. Callers pass the event's - // setting so a new wave inherits it, the way the LNbits admin dialog - // seeds one from the primary wave. - allow_fiat: false, - fiat_currency: "USD", ...defaults, }; }