feat(activities): align types + API service with events v1.6.1
The backend rebase brought in PR #50 (fiat checkout + email/Nostr ticket notifications), v1.6.0 (custom notification subject/body), and PR #51 (resend-email endpoint). The webapp types lagged. Aligns the type surface in src/modules/activities/types/ticket.ts: - EventExtra (with notification toggles + custom subject/body), promo codes, conditional event config. - ActivityTicketExtra mirroring backend's TicketExtra (nostr_identifier, email/nostr notification_sent flags, refund state). - TicketedEvent + CreateEventRequest gain allow_fiat, fiat_currency, extra. - TicketPurchaseInvoice extended for fiat: paymentRequest now optional, fiatPaymentRequest + fiatProvider + isFiat added. **Closes a latent blocker**: a backend response with is_fiat=true would have lost the fiat URL during deserialization (silent crash on QR generation). - New CreateTicketRequest type for the POST /tickets/{id} body, with the v1.6.1 payment_method + fiat_provider + nostr_identifier fields. TicketApiService: - requestTicket() accepts the new optional fields (paymentMethod, fiatProvider, promoCode, refundAddress, nostrIdentifier) and deserializes the full TicketPurchaseInvoice shape including fiat. - fetchUserTickets() / validateTicket() / new resendTicketEmail() thread the extra metadata through. useTicketPurchase composable rejects fiat responses with a clear error (the QR-and-bolt11 path doesn't know fiat); the eventual UI selector will live in PurchaseTicketDialog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c556d28587
commit
620919da58
3 changed files with 178 additions and 10 deletions
|
|
@ -98,16 +98,31 @@ export function useTicketPurchase() {
|
|||
currentUser.value!.id,
|
||||
accessToken
|
||||
)
|
||||
|
||||
// Backend now returns either a Lightning invoice or a fiat
|
||||
// checkout URL (post-events-v1.4.0). This composable only knows
|
||||
// how to drive the Lightning path; fiat would need a separate
|
||||
// redirect-to-provider flow that lives in PurchaseTicketDialog
|
||||
// (it has the user-visible payment-method selector). Reject the
|
||||
// fiat response here so callers get a clear error instead of a
|
||||
// silent broken QR.
|
||||
if (invoice.isFiat || !invoice.paymentRequest) {
|
||||
throw new Error(
|
||||
'This event uses fiat checkout. Use the purchase dialog ' +
|
||||
'to follow the provider link.',
|
||||
)
|
||||
}
|
||||
const bolt11: string = invoice.paymentRequest
|
||||
paymentHash.value = invoice.paymentHash
|
||||
paymentRequest.value = invoice.paymentRequest
|
||||
paymentRequest.value = bolt11
|
||||
|
||||
// Generate QR code for payment
|
||||
await generateQRCode(invoice.paymentRequest)
|
||||
await generateQRCode(bolt11)
|
||||
|
||||
// Try to pay with wallet if available
|
||||
if (hasWalletWithBalance.value) {
|
||||
try {
|
||||
await payWithWallet(invoice.paymentRequest)
|
||||
await payWithWallet(bolt11)
|
||||
await startPaymentStatusCheck(eventId, invoice.paymentHash)
|
||||
} catch (walletError) {
|
||||
console.log('Wallet payment failed, falling back to manual payment:', walletError)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import type {
|
||||
ActivityTicket,
|
||||
ActivityTicketExtra,
|
||||
CreateTicketRequest,
|
||||
PaymentMethod,
|
||||
TicketPurchaseInvoice,
|
||||
TicketPaymentStatus,
|
||||
TicketedEvent,
|
||||
|
|
@ -49,14 +52,38 @@ export class TicketApiService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Request a ticket purchase (creates a Lightning invoice).
|
||||
* Uses POST /tickets/{event_id} with user_id in body (upstream API).
|
||||
* Request a ticket purchase. Returns either a Lightning invoice
|
||||
* (`paymentRequest` = bolt11) or a fiat invoice (`fiatPaymentRequest`
|
||||
* = follow-the-URL string from the configured fiat provider). The
|
||||
* `isFiat` flag is the discriminator.
|
||||
*
|
||||
* `paymentMethod` defaults to "lightning"; pass "fiat" to opt into
|
||||
* the fiat path (requires the event to have `allow_fiat=true`).
|
||||
* `fiatProvider` is optional — backend picks the user's configured
|
||||
* default when omitted.
|
||||
*
|
||||
* Additional ticket metadata (promo code, refund address, nostr
|
||||
* identifier for DM delivery) can be supplied via `options`.
|
||||
*/
|
||||
async requestTicket(
|
||||
eventId: string,
|
||||
userId: string,
|
||||
accessToken: string
|
||||
accessToken: string,
|
||||
options: {
|
||||
paymentMethod?: PaymentMethod
|
||||
fiatProvider?: string
|
||||
promoCode?: string
|
||||
refundAddress?: string
|
||||
nostrIdentifier?: string
|
||||
} = {},
|
||||
): Promise<TicketPurchaseInvoice> {
|
||||
const body: CreateTicketRequest = { user_id: userId }
|
||||
if (options.paymentMethod) body.payment_method = options.paymentMethod
|
||||
if (options.fiatProvider) body.fiat_provider = options.fiatProvider
|
||||
if (options.promoCode) body.promo_code = options.promoCode
|
||||
if (options.refundAddress) body.refund_address = options.refundAddress
|
||||
if (options.nostrIdentifier) body.nostr_identifier = options.nostrIdentifier
|
||||
|
||||
const data = await this.request(
|
||||
`/events/api/v1/tickets/${eventId}`,
|
||||
{
|
||||
|
|
@ -65,13 +92,16 @@ export class TicketApiService {
|
|||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({ user_id: userId }),
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
paymentHash: data.payment_hash,
|
||||
paymentRequest: data.payment_request,
|
||||
paymentRequest: data.payment_request ?? undefined,
|
||||
fiatPaymentRequest: data.fiat_payment_request ?? undefined,
|
||||
fiatProvider: data.fiat_provider ?? undefined,
|
||||
isFiat: Boolean(data.is_fiat),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -121,6 +151,7 @@ export class TicketApiService {
|
|||
paid: t.paid,
|
||||
time: t.time,
|
||||
regTimestamp: t.reg_timestamp,
|
||||
extra: t.extra as ActivityTicketExtra | undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +175,7 @@ export class TicketApiService {
|
|||
paid: t.paid,
|
||||
time: t.time,
|
||||
regTimestamp: t.reg_timestamp,
|
||||
extra: t.extra as ActivityTicketExtra | undefined,
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +215,39 @@ export class TicketApiService {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend the ticket confirmation email for a paid ticket. Requires
|
||||
* the event's wallet admin key (organizer-only). Returns the updated
|
||||
* Ticket with the `email_notification_sent` flag refreshed.
|
||||
*
|
||||
* Endpoint added upstream in v1.6.1 (PR #51).
|
||||
*/
|
||||
async resendTicketEmail(
|
||||
ticketId: string,
|
||||
adminKey: string,
|
||||
): Promise<ActivityTicket> {
|
||||
const t = await this.request(
|
||||
`/events/api/v1/tickets/${ticketId}/resend-email`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'X-API-KEY': adminKey },
|
||||
}
|
||||
)
|
||||
return {
|
||||
id: t.id,
|
||||
wallet: t.wallet,
|
||||
activityId: t.event,
|
||||
name: t.name,
|
||||
email: t.email,
|
||||
userId: t.user_id,
|
||||
registered: t.registered,
|
||||
paid: t.paid,
|
||||
time: t.time,
|
||||
regTimestamp: t.reg_timestamp,
|
||||
extra: t.extra as ActivityTicketExtra | undefined,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe whether the current user has LNbits admin privileges. The
|
||||
* `/all` endpoint is `check_admin`-gated, so a 200 means "admin",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,44 @@
|
|||
/**
|
||||
* Database-backed ticket types (via LNbits events extension)
|
||||
* Database-backed ticket types (via LNbits events extension).
|
||||
*
|
||||
* Wire-format types — names match the snake_case fields the events
|
||||
* extension serves over HTTP. Camel-cased aliases (e.g. ActivityTicket
|
||||
* below) are the webapp-internal view models after adapter conversion.
|
||||
*/
|
||||
|
||||
export interface PromoCode {
|
||||
code: string
|
||||
discount_percent: number
|
||||
active: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* EventExtra mirrors the EventExtra Pydantic model in
|
||||
* `events/models.py`. Carries promo codes, conditional-event config,
|
||||
* and the per-event notification toggles + custom subject/body added
|
||||
* in upstream v1.4.0 (PR #50) and v1.6.0.
|
||||
*/
|
||||
export interface EventExtra {
|
||||
promo_codes: PromoCode[]
|
||||
conditional: boolean
|
||||
min_tickets: number
|
||||
email_notifications: boolean
|
||||
nostr_notifications: boolean
|
||||
notification_subject: string
|
||||
notification_body: string
|
||||
}
|
||||
|
||||
export interface ActivityTicketExtra {
|
||||
applied_promo_code?: string | null
|
||||
sats_paid?: number | null
|
||||
refund_address?: string | null
|
||||
nostr_identifier?: string | null
|
||||
ticket_base_url?: string | null
|
||||
email_notification_sent: boolean
|
||||
nostr_notification_sent: boolean
|
||||
refunded: boolean
|
||||
}
|
||||
|
||||
export interface ActivityTicket {
|
||||
id: string
|
||||
wallet: string
|
||||
|
|
@ -21,19 +58,40 @@ export interface ActivityTicket {
|
|||
time: string
|
||||
/** Registration/scan timestamp */
|
||||
regTimestamp: string
|
||||
/** Optional metadata — promo code applied, sats paid, notification
|
||||
* delivery flags, refund state. May be absent on older tickets. */
|
||||
extra?: ActivityTicketExtra
|
||||
}
|
||||
|
||||
export type TicketStatus = 'pending' | 'paid' | 'registered' | 'cancelled'
|
||||
|
||||
export type PaymentMethod = 'lightning' | 'fiat'
|
||||
|
||||
export interface TicketPurchaseRequest {
|
||||
activityId: string
|
||||
userId: string
|
||||
accessToken: string
|
||||
/** Lightning (default) or fiat. Only meaningful if the event has
|
||||
* `allow_fiat=true` on the backend; otherwise the backend coerces
|
||||
* to lightning. */
|
||||
paymentMethod?: PaymentMethod
|
||||
/** Specific fiat provider id (e.g. "stripe"). Backend picks the
|
||||
* user's default if omitted. */
|
||||
fiatProvider?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Server response from `POST /tickets/{event_id}`. Either Lightning
|
||||
* (`paymentRequest` = bolt11) or fiat (`fiatPaymentRequest` = a URL
|
||||
* the buyer follows to complete payment with `fiatProvider`).
|
||||
* `isFiat` is the discriminator.
|
||||
*/
|
||||
export interface TicketPurchaseInvoice {
|
||||
paymentHash: string
|
||||
paymentRequest: string
|
||||
paymentRequest?: string
|
||||
fiatPaymentRequest?: string
|
||||
fiatProvider?: string
|
||||
isFiat: boolean
|
||||
}
|
||||
|
||||
export interface TicketPaymentStatus {
|
||||
|
|
@ -58,6 +116,10 @@ export interface TicketedEvent {
|
|||
event_start_date: string
|
||||
event_end_date: string | null
|
||||
currency: string
|
||||
/** Whether the event accepts fiat payments. Upstream v1.4.0+. */
|
||||
allow_fiat: boolean
|
||||
/** Fiat currency code (ISO 4217). Defaults to "GBP" upstream. */
|
||||
fiat_currency: string
|
||||
amount_tickets: number
|
||||
price_per_ticket: number
|
||||
time: string
|
||||
|
|
@ -65,6 +127,7 @@ export interface TicketedEvent {
|
|||
banner: string | null
|
||||
location: string | null
|
||||
categories: string[]
|
||||
extra: EventExtra
|
||||
status: string
|
||||
}
|
||||
|
||||
|
|
@ -76,9 +139,34 @@ export interface CreateEventRequest {
|
|||
event_start_date: string
|
||||
event_end_date?: string
|
||||
currency?: string
|
||||
allow_fiat?: boolean
|
||||
fiat_currency?: string
|
||||
amount_tickets?: number
|
||||
price_per_ticket?: number
|
||||
banner?: string | null
|
||||
location?: string | null
|
||||
categories?: string[]
|
||||
/** Optional — notification toggles + custom subject/body, promo
|
||||
* codes, conditional-event config. Backend defaults to a fresh
|
||||
* EventExtra if omitted. */
|
||||
extra?: Partial<EventExtra>
|
||||
}
|
||||
|
||||
/**
|
||||
* Body for `POST /tickets/{event_id}`. Either `user_id` OR the
|
||||
* `name`+`email` pair is required (backend root_validator enforces
|
||||
* mutual exclusion). `nostr_identifier` opts the buyer into Nostr DM
|
||||
* delivery when the event has nostr_notifications enabled. The
|
||||
* `payment_method` + `fiat_provider` pair selects between Lightning
|
||||
* and fiat checkout.
|
||||
*/
|
||||
export interface CreateTicketRequest {
|
||||
name?: string
|
||||
email?: string
|
||||
user_id?: string
|
||||
promo_code?: string
|
||||
refund_address?: string
|
||||
nostr_identifier?: string
|
||||
payment_method?: PaymentMethod
|
||||
fiat_provider?: string
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue