feat(chatelet): booking wire types + requestBooking / payment-status client
Mirror the extension's BookingRequestData / Booking / BookingQuote
models and add the two calls slice 2 needs: POST /bookings (hold +
invoice) and an anonymous settlement poll on LNbits core's
GET /api/v1/payments/{hash}. Chatelet's own booking read needs a
wallet key, so the invoice is what the guest can watch until a public
booking-status endpoint lands on the extension.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
718d6e76e1
commit
3e55decfad
3 changed files with 100 additions and 3 deletions
|
|
@ -8,7 +8,7 @@ export interface ChateletModuleConfig {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chatelet module — guest booking UI for the LNbits `chatelet` room-rentals
|
* Chatelet module — guest booking UI for the LNbits `chatelet` room-rentals
|
||||||
* extension. Slice 1: read-only discovery + availability over HTTP.
|
* extension: discovery, availability and Lightning-paid booking over HTTP.
|
||||||
*/
|
*/
|
||||||
export const chateletModule = createModulePlugin({
|
export const chateletModule = createModulePlugin({
|
||||||
name: 'chatelet',
|
name: 'chatelet',
|
||||||
|
|
@ -52,3 +52,4 @@ export const chateletModule = createModulePlugin({
|
||||||
export default chateletModule
|
export default chateletModule
|
||||||
|
|
||||||
export type { Room, AvailabilityResult } from './types/room'
|
export type { Room, AvailabilityResult } from './types/room'
|
||||||
|
export type { Booking, BookingQuote, BookingRequest, BookingStatus } from './types/booking'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import type { AvailabilityResult, Room } from '../types/room'
|
import type { AvailabilityResult, Room } from '../types/room'
|
||||||
|
import type { BookingQuote, BookingRequest, PaymentStatus } from '../types/booking'
|
||||||
|
|
||||||
export interface ChateletApiConfig {
|
export interface ChateletApiConfig {
|
||||||
baseUrl: string
|
baseUrl: string
|
||||||
|
|
@ -9,8 +10,8 @@ export interface ChateletApiConfig {
|
||||||
* HTTP client for the LNbits `chatelet` extension's guest surface.
|
* HTTP client for the LNbits `chatelet` extension's guest surface.
|
||||||
*
|
*
|
||||||
* Plain config-wrapper (same pattern as events' TicketApiService) — not a
|
* Plain config-wrapper (same pattern as events' TicketApiService) — not a
|
||||||
* BaseService. Slice 1 is read-only guest browsing over public endpoints;
|
* BaseService. Guest browsing, availability and the booking request are all
|
||||||
* booking (POST /bookings) + status polling arrive in slice 2.
|
* public endpoints; settlement is polled on the invoice via LNbits core.
|
||||||
*
|
*
|
||||||
* Transport note: this is HTTP-for-now. The extension also exposes a
|
* Transport note: this is HTTP-for-now. The extension also exposes a
|
||||||
* kind-21000 Nostr-RPC surface (chatelet_room_list / _availability / …), but
|
* kind-21000 Nostr-RPC surface (chatelet_room_list / _availability / …), but
|
||||||
|
|
@ -41,6 +42,30 @@ export class ChateletApiService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hold the dates and get the invoice that confirms them. The backend
|
||||||
|
* quotes the amount (canonical `booking.deposit_sat`) — the guest never
|
||||||
|
* recomputes what they owe. 502 = invoice creation failed server-side.
|
||||||
|
*/
|
||||||
|
async requestBooking(data: BookingRequest): Promise<BookingQuote> {
|
||||||
|
return this.request('/chatelet/api/v1/bookings', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'content-type': 'application/json' },
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anonymous settlement check on LNbits core. Chatelet's own
|
||||||
|
* `GET /bookings/{id}` needs a wallet key, so until a public booking
|
||||||
|
* status endpoint lands the guest polls the invoice instead — the
|
||||||
|
* extension's paid-invoice listener is what flips the booking to
|
||||||
|
* `confirmed`.
|
||||||
|
*/
|
||||||
|
async getPaymentStatus(paymentHash: string): Promise<PaymentStatus> {
|
||||||
|
return this.request(`/api/v1/payments/${paymentHash}`, { method: 'GET' })
|
||||||
|
}
|
||||||
|
|
||||||
private async request(path: string, init: RequestInit = {}): Promise<any> {
|
private async request(path: string, init: RequestInit = {}): Promise<any> {
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
accept: 'application/json',
|
accept: 'application/json',
|
||||||
|
|
|
||||||
71
src/modules/chatelet/types/booking.ts
Normal file
71
src/modules/chatelet/types/booking.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/**
|
||||||
|
* Booking wire types — mirror `models.py` in the LNbits `chatelet`
|
||||||
|
* extension (BookingRequestData / Booking / BookingQuote). Field names are
|
||||||
|
* kept as the backend spells them so the JSON maps 1:1.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type BookingStatus =
|
||||||
|
| 'held'
|
||||||
|
| 'awaiting_payment'
|
||||||
|
| 'confirmed'
|
||||||
|
| 'declined'
|
||||||
|
| 'cancelled'
|
||||||
|
| 'expired'
|
||||||
|
| (string & {})
|
||||||
|
|
||||||
|
/** What a guest sends to `POST /chatelet/api/v1/bookings`. */
|
||||||
|
export interface BookingRequest {
|
||||||
|
room_id: string
|
||||||
|
/** Hex pubkey of the guest — the identity the check-in DM goes to. */
|
||||||
|
guest_pubkey: string
|
||||||
|
/** YYYY-MM-DD, inclusive. */
|
||||||
|
check_in: string
|
||||||
|
/** YYYY-MM-DD, exclusive. */
|
||||||
|
check_out: string
|
||||||
|
num_guests: number
|
||||||
|
guest_contact?: string
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Booking {
|
||||||
|
id: string
|
||||||
|
room_id: string
|
||||||
|
guest_pubkey: string
|
||||||
|
guest_contact: string | null
|
||||||
|
check_in: string
|
||||||
|
check_out: string
|
||||||
|
nights: number
|
||||||
|
num_guests: number
|
||||||
|
/** Snapshot of the room's price currency. */
|
||||||
|
currency: string
|
||||||
|
/** nights × room price, display only. */
|
||||||
|
price_fiat: number
|
||||||
|
/** Canonical total due, in sats. */
|
||||||
|
amount_sat: number
|
||||||
|
/** Portion invoiced now (== amount_sat at 100 % prepay). */
|
||||||
|
deposit_sat: number
|
||||||
|
status: BookingStatus
|
||||||
|
payment_hash: string | null
|
||||||
|
/** Hold expiry while held / awaiting_payment; null once confirmed. */
|
||||||
|
expires_at: string | null
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Response to a booking request: the held booking + the bolt11 that confirms it. */
|
||||||
|
export interface BookingQuote {
|
||||||
|
booking: Booking
|
||||||
|
payment_request: string
|
||||||
|
payment_hash: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Anonymous read of LNbits core `GET /api/v1/payments/{hash}` — the guest
|
||||||
|
* cannot read their booking over HTTP yet (that endpoint needs a wallet
|
||||||
|
* key), so settlement is polled on the invoice instead. `status` is only
|
||||||
|
* present on a failed payment.
|
||||||
|
*/
|
||||||
|
export interface PaymentStatus {
|
||||||
|
paid: boolean
|
||||||
|
status?: string
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue