Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
from lnbits.db import Database
from lnbits.helpers import urlsafe_short_hash

from .models import CreateEvent, Event, Ticket
from .models import CreateEvent, Event, Ticket, TicketExtra

db = Database("ext_events")


async def create_ticket(
payment_hash: str, wallet: str, event: str, name: str, email: str
payment_hash: str, wallet: str, event: str, name: str, email: str, extra: dict
) -> Ticket:
now = datetime.now(timezone.utc)
ticket = Ticket(
Expand All @@ -22,6 +22,7 @@ async def create_ticket(
paid=False,
reg_timestamp=now,
time=now,
extra=TicketExtra(**extra) if extra else TicketExtra(),
)
await db.insert("events.ticket", ticket)
return ticket
Expand Down
18 changes: 18 additions & 0 deletions migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,21 @@ async def m005_add_image_banner(db):
Add a column to allow an image banner for the event
"""
await db.execute("ALTER TABLE events.events ADD COLUMN banner TEXT;")


async def m006_add_extra_fields(db):
"""
Add a canceled and 'extra' column to events and ticket tables
to support promo codes and ticket metadata.
"""
# Add canceled and 'extra' columns to events table
await db.execute(
"""
ALTER TABLE events.events
ADD COLUMN canceled BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN extra TEXT;
"""
)

# Add 'extra' column to ticket table
await db.execute("ALTER TABLE events.ticket ADD COLUMN extra TEXT;")
47 changes: 41 additions & 6 deletions models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
from datetime import datetime

from fastapi import Query
from pydantic import BaseModel, EmailStr
from pydantic import BaseModel, EmailStr, Field, validator


class PromoCode(BaseModel):
code: str
discount_percent: float = 0.0
active: bool = True

# make the promo code uppercase
@validator("code")
def uppercase_code(cls, v):
return v.upper()

@validator("discount_percent")
def validate_discount_percent(cls, v):
assert 0 <= v <= 100, "Discount must be between 0 and 100."
return v


class EventExtra(BaseModel):
promo_codes: list[PromoCode] = Field(default_factory=list)
conditional: bool = False
min_tickets: int = 1


class CreateEvent(BaseModel):
Expand All @@ -15,11 +37,7 @@ class CreateEvent(BaseModel):
amount_tickets: int = Query(..., ge=0)
price_per_ticket: float = Query(..., ge=0)
banner: str | None = None


class CreateTicket(BaseModel):
name: str
email: EmailStr
extra: EventExtra = Field(default_factory=EventExtra)


class Event(BaseModel):
Expand All @@ -28,6 +46,7 @@ class Event(BaseModel):
name: str
info: str
closing_date: str
canceled: bool = False
event_start_date: str
event_end_date: str
currency: str
Expand All @@ -36,6 +55,21 @@ class Event(BaseModel):
time: datetime
sold: int = 0
banner: str | None = None
extra: EventExtra = Field(default_factory=EventExtra)


class TicketExtra(BaseModel):
applied_promo_code: str | None = None
sats_paid: int | None = None
refund_address: str | None = None
refunded: bool = False


class CreateTicket(BaseModel):
name: str
email: EmailStr
promo_code: str | None = None
refund_address: str | None = None


class Ticket(BaseModel):
Expand All @@ -48,3 +82,4 @@ class Ticket(BaseModel):
paid: bool
time: datetime
reg_timestamp: datetime
extra: TicketExtra = Field(default_factory=TicketExtra)
38 changes: 37 additions & 1 deletion services.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
from .crud import get_event, update_event, update_ticket
from lnurl import execute
from loguru import logger

from .crud import (
get_event,
get_event_tickets,
purge_unpaid_tickets,
update_event,
update_ticket,
)
from .models import Ticket


Expand All @@ -16,3 +25,30 @@ async def set_ticket_paid(ticket: Ticket) -> Ticket:
await update_event(event)

return ticket


async def refund_tickets(event_id: str):
"""
Refund tickets for an event that has not met the minimum ticket requirement.
This function should be called when the event is closed and the minimum ticket
condition is not met.
"""
await purge_unpaid_tickets(event_id)
tickets = await get_event_tickets(event_id)

if not tickets:
return

for ticket in tickets:
if ticket.extra.refunded:
continue
if ticket.paid and ticket.extra.refund_address and ticket.extra.sats_paid:
try:
res = await execute(
ticket.extra.refund_address, str(ticket.extra.sats_paid)
)
if res:
ticket.extra.refunded = True
await update_ticket(ticket)
except Exception as e:
logger.error(f"Error refunding ticket {ticket.id}: {e}")
18 changes: 7 additions & 11 deletions static/js/display.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ window.app = Vue.createApp({
show: false,
data: {
name: '',
email: ''
email: '',
refund: ''
}
},
ticketLink: {
Expand All @@ -29,7 +30,8 @@ window.app = Vue.createApp({
this.info = event_info
this.info = this.info.substring(1, this.info.length - 1)
this.banner = event_banner
await this.purgeUnpaidTickets()
this.extra = event_extra
this.hasPromoCodes = has_promoCodes
},
computed: {
formatDescription() {
Expand All @@ -41,6 +43,7 @@ window.app = Vue.createApp({
e.preventDefault()
this.formDialog.data.name = ''
this.formDialog.data.email = ''
this.formDialog.data.refund = ''
},

closeReceiveDialog() {
Expand All @@ -60,12 +63,12 @@ window.app = Vue.createApp({
const regex = /^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$/
return regex.test(val) || 'Please enter valid email.'
},

Invoice() {
axios
.post(`/events/api/v1/tickets/${event_id}`, {
name: this.formDialog.data.name,
email: this.formDialog.data.email
email: this.formDialog.data.email,
promo_code: this.formDialog.data.promo_code || null
})
.then(response => {
this.paymentReq = response.data.payment_request
Expand Down Expand Up @@ -122,13 +125,6 @@ window.app = Vue.createApp({
}, 2000)
})
.catch(LNbits.utils.notifyApiError)
},
async purgeUnpaidTickets() {
try {
await LNbits.api.request('GET', `/events/api/v1/purge/${event_id}`)
} catch (error) {
LNbits.utils.notifyApiError(error)
}
}
}
})
Loading